PERMISSION/PROTOCOL
Back to incident tracker

2026-07-09

HighPrimary

GhostApproval: Symlink + UI Misrepresentation Bypasses Human-in-the-Loop in Claude Code, Amazon Q, Cursor, Google Antigravity, and Windsurf — Claude Code Knew the File Resolved to ~/.ssh/authorized_keys, Showed Decoy Name in Approval Dialog

Wiz Research disclosed GhostApproval: symlink-following + UI misrepresentation bypasses HITL in Claude Code, Amazon Q, Cursor, Antigravity, Windsurf. Claude Code internally detected the risk but showed only the decoy filename in its approval dialog.

Claude Code / Amazon Q / Cursor / Google Antigravity / WindsurfGovernance bypassHITL approval bypass via symlink following and UI path misrepresentation — SSH backdoor installationDeveloper workstations running AI coding agents that process untrusted or partially-trusted repositories

What happened

Attacker places a project config file in a repository — the file is a symlink pointing to ~/.ssh/authorized_keys or ~/.bashrc. When a developer opens the repository in their AI coding agent and the agent proposes writing to the config file, the approval dialog shows only the decoy symlink name. The user approves. The agent writes attacker-controlled SSH public keys or shell commands to the resolved sensitive path, installing a persistent backdoor without credentials.

Why it matters

Persistent SSH backdoor (unauthorized authorized_keys entry) or persistent shell command injection (.bashrc/.zshrc) on the developer's machine. An attacker with SSH access to a developer's workstation can pivot to any system reachable from it — production infrastructure, CI runners, cloud credentials, internal networks. The backdoor survives agent session termination and system restarts.

Missing authorization check

The HITL approval dialog must resolve and display the real destination path, not the symlink source. The approval surface should never show a path that differs from the actual write target — and when the agent's own internal reasoning has already identified the real path as sensitive, that information must be surfaced in the user-facing decision, not filtered out before it reaches the dialog.

Would PP block it?

GhostApproval's attack chain depends entirely on the approval surface displaying the symlink filename instead of the resolved path. PP's enforcement layer resolves the actual write target before issuing the receipt — the receipt names the real canonical destination, not the agent's display string. Even in the Claude Code case where the model's internal reasoning correctly identified the resolved path, PP would have exposed the gap: the receipt would reference the real write target, making the mismatch between what the user was shown and what they were actually approving visible in the receipt audit trail. PP works regardless of whether the agent's UI misrepresented the path intentionally or by omission.

Incident analysis

Timeline and technical read

Timeline

  1. 2026-07-09

    Wiz Research publishes GhostApproval disclosure. Five AI coding agents confirmed vulnerable: Claude Code, Amazon Q Developer, Cursor, Google Antigravity, Windsurf. Attack exploits CWE-61 (symlink following) + CWE-451 (UI misrepresentation) to bypass HITL approval dialogs.

  2. 2026-07-09

    Wiz notes Claude Code's internal reasoning chain correctly identified the write target as ~/.ssh/authorized_keys — flagging it as a sensitive shell configuration path — but the approval dialog displayed only the harmless symlink filename. Anthropic declined to patch, stating the scenario is outside their threat model: 'users explicitly trust project directories.'

  3. 2026-07-09

    Amazon assigned CVE-2026-12958 to the Amazon Q Developer variant and patched. Cursor assigned CVE-2026-50549 for the symlink HITL bypass (separate from CVE-2026-50548, a zero-click RCE in the same Wiz disclosure) and patched.

  4. 2026-07-09

    GBHackers and SecurityPointBreak report on GhostApproval. Google Antigravity and Windsurf patch status not publicly confirmed at time of disclosure.

Technical breakdown

  • CWE-61 (Improper Link Resolution Before File Access): the AI coding agent resolves the write target at open time but passes the symlink source path — not the resolved destination — to the HITL approval UI. The path shown in the dialog and the path actually written are two different filesystem locations.
  • CWE-451 (UI Misrepresentation of Critical Information): the agent's approval dialog presents the symlink filename as if it were the write destination. A developer who approves 'write to .cursor/config' does not know they are approving a write to ~/.ssh/authorized_keys.
  • Claude Code's internal reasoning — visible in the agent's scratchpad — explicitly identified the resolved path as a shell configuration file and flagged the write as potentially sensitive. This risk signal was present in the model's reasoning but was not surfaced in the user-facing approval dialog, creating a documented split between what the model knew and what the user was shown.
  • Anthropic's decision to decline patching GhostApproval means Claude Code remains permanently vulnerable on the current threat model framing. Any developer who opens a repository containing a malicious symlink in Claude Code is exposed — there is no user-side mitigation beyond auditing every project config file in every repository before opening it.
  • The resulting backdoor is persistent and durable: SSH authorized_keys entries and shell RC file injections survive agent session termination, system restarts, and credential rotations. The attacker retains access until the developer manually discovers and removes the injected entry.

Authorization boundary

Where the authorization boundary should have been

This incident is categorized as Governance bypass. The relevant Permission Protocol gate is Tool-Call Gate. The read is conditional: the block only applies where the real action boundary is routed through a gate.

If enforced at
Tool-Call Gate — all agent-issued file write operations requiring a receipt that names the cryptographically resolved canonical destination path, independent of the display string in the agent's approval dialog
Still needs
Agent-native HITL dialog display of resolved symlink paths; model-to-UI transparency enforcement (preventing the agent's internal risk assessment from being suppressed before reaching the user); repository trust-level configuration
Receipt required for
Any file write operation where the write target could be a symlink — receipts must be generated against the resolved canonical path as confirmed by the PP enforcement layer, not the path string from the agent's approval UI

PP's Tool-Call Gate requires approval through an independent channel that the symlink chain cannot reach. PP generates a receipt that names the cryptographically confirmed resolved path — not the display string from the agent's UI. A receipt for 'write to .cursor/config' cannot authorize a write to ~/.ssh/authorized_keys, regardless of what the agent's approval dialog showed the user.

Start small

Put the relevant gate at this action boundary.

This incident maps to Tool-Call Gate. Start with the boundary that controls the actual action, then require a signed receipt before execution.

Replay this incident with a signer in the loop