Silent Replacement of Trusted macOS App Executables

title
Silent Replacement of Trusted macOS App Executables
type
summary
summary
Mysk: archive-and-restore lets user-level code swap a downloaded app's binary; Apple declined to fix
tags
macos, security, code-signing, gatekeeper
created
2026-07-29
updated
2026-07-29

Talal Haj Bakry and Tommy Mysk found that any code running as the current user can replace the main executable of an application downloaded from the web, and that the replaced app relaunches with no warning and can then impersonate the original in system permission prompts. Apple reproduced the report and closed it as not requiring a security fix. That last part is not incidental to the writeup; it's the reason the writeup exists.

Confirmed on macOS Tahoe 26.0.0 through 26.5.2 and Golden Gate 27 betas 1 through 4. Earlier versions are likely affected but were not tested.

The protection that's supposed to hold

macOS ships apps as bundles: directories that Finder renders as a single .app. On first launch of a downloaded app, macOS checks the code signature and notarization and applies Gatekeeper policy. Once the app is installed and opened, the bundle's contents become read-only to everything else, and macOS raises an alert when something tries. Not even root gets through:

% sudo touch /Applications/Signal.app/Contents/MacOS/Signal
touch: /Applications/Signal.app/Contents/MacOS/Signal: Operation not permitted

The three commands that undo it

% cd /Applications/
% tar cf .Signal.tar Signal.app
% rm -rf Signal.app
% tar xf .Signal.tar -C /Applications

The restored bundle launches normally. It is also writable:

% touch /Applications/Signal.app/Contents/MacOS/Signal
%

At that point replacing the executable is a cp. The authors' demo compiles a Swift program that opens a window titled "Hi! I'm Signal!", copies it over Signal.app/Contents/MacOS/Signal, and runs open /Applications/Signal.app. No Gatekeeper warning, no indication the bundle changed.

Three preconditions. The app must have come from the web rather than the Mac App Store, because App Store apps are owned by root while web-downloaded apps are typically owned by the current user. The app must have been launched at least once, so Gatekeeper's initial verification is behind it. And the attacker must already have code execution as that user — no privilege escalation, which puts this squarely downstream of the kinds of compromise covered in supply-chain-security, where a malicious dependency or install script gets exactly that much and no more.

Signal is the demonstration target because it's widely trusted and distributed outside the App Store; the authors are explicit that it is not a Signal bug. The same behavior affects Brave Browser, Cursor, Mullvad Browser, Proton Mail, Slack, Visual Studio Code, and Xcode among others.

Why it works

The bundle's resources are sealed by hashes recorded in _CodeSignature/CodeResources. The main executable is not one of those resources; it carries its own embedded signature. Archiving and restoring leaves every resource byte-identical, so the seal still validates. The replacement executable is ad hoc signed, and macOS permits ad hoc signed executables to run. The bundle therefore passes the checks that are performed while carrying a binary nobody's developer certificate vouches for.

The first-launch requirement falls out of the same structure. Replace the executable before first launch and initial validation fails, with macOS reporting the app as damaged and offering the Trash. Replace it after, and the bundle keeps launching under the original app's identity.

What the attacker gets

Less than a full compromise, and the authors say so plainly: this bypasses neither TCC, nor Keychain protection, nor code signing. The ad hoc signed replacement inherits none of the original app's entitlements and none of its previously granted permissions, so every access to a protected resource still raises a real authorization prompt.

The prompts are the attack. Because the replacement executable lives inside Signal.app, macOS labels them with Signal's name and icon, and they are visually indistinguishable from prompts the real app would produce. The proof of concept requests Signal's Keychain item holding its encryption key, then the TCC-protected ~/Desktop and ~/Documents, then terminates itself and relaunches the genuine Signal executable so the user sees nothing out of place. A user who trusts Signal approves a prompt that says Signal.

Apple's assessment

Apple's reasoning, as relayed in the post:

  • The proof of concept replaces the entire application bundle rather than modifying an existing signed executable.
  • The attack requires code execution as the current user and affects only applications owned by that user.
  • The replacement does not inherit entitlements or previously granted TCC permissions, so the user must approve new prompts.
  • Getting a user to approve those prompts is social engineering, not a TCC bypass.
  • Gatekeeper is designed to evaluate downloaded applications before first launch, and is not intended to protect files already owned and modified by the current user.

Neither Gatekeeper nor TCC was bypassed, therefore no security fix. Every one of those points is individually true, and together they define the threat model such that the observed behavior falls outside it.

Mysk's counter-proposals target the two places where the design and the user's mental model diverge. Revalidate an app's code signature before launch when its bundle has changed — the brief validation macOS displays after the modification suggests the change is already being detected. And show the requesting executable's code-signing identity, the developer or Team ID, in authorization prompts, instead of only the application's name and icon. The second would defeat the impersonation without touching Gatekeeper's remit at all.

Timeline

Reported June 4, 2026. Apple asked for more information June 9; proof-of-concept source sent June 11. Mysk asked for an update on July 13, mentioning planned disclosure to Proton, Signal, Brave, and Mullvad. On July 14 Apple replied "We've taken an initial pass at reproducing this report," and closed the report the same day.

The pattern is familiar from macos-tcp-time-bomb, where outside researchers characterized a macOS defect in more detail than the vendor's own response acknowledged. The difference is that the 49-day tcp_now overflow is unambiguously a bug, while this one is a disagreement about what the security boundary is: Apple's position is that an attacker already running as you is outside it, and Mysk's is that the prompt showing Signal's icon is inside it.