All posts
Scope Drift

GhostApproval: Symlink Drift in Six AI Coding Agents

Wiz shows six AI coding assistants — Amazon Q, Claude Code, Cursor, Antigravity, Augment, Windsurf — display one filename while writing to another

Securityv0 Intelligence Team OWASP: ASI03 sv0 finding: scope_drift
ghostapproval symlink scope-drift ai-coding-agents wiz-research approval-gate

The Incident

On 2026-07-08, Wiz Research disclosed “GhostApproval,” a cross-vendor trust-boundary defect in six AI coding assistants — Amazon Q Developer, Anthropic Claude Code, Augment, Cursor, Google Antigravity, and Windsurf (Devin Desktop) — that lets a hostile repository trick the agent’s per-write approval dialog into confirming edits the operator never actually authorized. A malicious repo contains a file such as project_settings.json that is a symlink pointing outside the workspace — for example, ~/.ssh/authorized_keys or a shell rc file. When the developer asks the assistant to edit the file, the agent resolves the symlink internally, but the approval dialog still displays the harmless in-repo name. Approving the write lands attacker-controlled bytes at the resolved target — SSH keys, shell startup files, git config, or any file the developer’s shell identity can reach. Wiz observed Claude Code, in particular, reasoning internally that a target was “a symbolic link to the Claude settings file” while its approval prompt still asked the user to authorize an edit to project_settings.json. The agent knew; the human could not.

Two of the six vendors shipped CVEs and fixes: AWS patched Amazon Q Developer in Language Servers for AWS 1.69.0 as CVE-2026-12958 (CVSS 7.8), and Cursor patched version 3.0 as CVE-2026-50549 (CVSS 9.8) — the same CVE SecurityV0 covered in DuneSlide three weeks ago, when Cato AI Labs used a zero-click prompt injection to reach the same fail-open canonicalization path. Google Antigravity patched separately in version 1.19.6 without a public CVE. Anthropic declined to treat GhostApproval as a vulnerability, calling it “outside our current threat model” because the user must trust the directory before starting a session; separately, Anthropic did patch a mechanically distinct symlink sandbox escape as CVE-2026-39861 in Claude Code 2.1.64, but the GhostApproval class remains open. Augment and Windsurf/Devin Desktop acknowledged the report and remained unpatched at disclosure. Wiz discovered the pattern on 2026-02-10 and coordinated disclosure through 2026-03-05 before going public after the 90-day window elapsed. No in-the-wild exploitation has been reported.

MITRE ATT&CK coverage: T1098.004 Account Manipulation: SSH Authorized Keys — the canonical exploitation target — and T1547 Boot or Logon Autostart Execution for the shell-rc and startup-file variants.

The Authority Path That Failed

The identity carrying execution authority at the moment of failure is the AI coding agent process, running as the developer’s local user. The scope it holds is the developer’s full local filesystem, shell, and every credential that identity can reach. The scope the operator intended it to exercise is expressed structurally through two composed gates: a directory-trust decision at session start (“trust this directory”) and a per-write approval dialog for individual edits. The scope the agent actually exercises under GhostApproval is arbitrary filesystem writes anywhere the developer’s shell can reach — because the write policy resolves the symlink using ordinary file operations and then asks for approval based on the pre-resolution path, not the resolved target. The human’s consent is anchored to a fictional target.

Two trust anchors fail in sequence. The policy permits a write based on the apparent in-workspace path even though the filesystem resolves it elsewhere. The approval dialog then presents that apparent path, so the human is asked to approve a write that is not the write the runtime is about to perform. The gap between held authority (arbitrary local filesystem) and exercised authority (a single named in-repo file) collapses at the moment the dialog renders. Six independent implementations arriving at the same defect is evidence of a control-class failure: approval must bind to the resolved target, not the path supplied by an untrusted repository.

SecurityV0 Perspective

This is scope_drift. The write tool was authorized; what drifted was the perimeter around it, once the approval gate — the control meant to bound exercised scope — displayed a different target than the one the write actually landed on. This is the same shape as the Meta rogue agent (asked to analyze, published) and DuneSlide (asked to summarize a page, executed code), applied to the moment a human clicks Approve.

The governance requirement is concrete: bind the agent identity, trusted workspace, resolved write target, and approval string into one enforceable decision and one audit record. Before an incident, that record shows which agents can reach sensitive paths from an untrusted repository. After an incident, it shows which identity issued the write and what the operator was shown. If those facts are not captured at execution time, they cannot be reconstructed reliably later.

What To Do

  • Diff the approval-dialog string against the resolved write target. For every AI coding agent your team has deployed, verify that the path shown in the approval prompt matches the fully resolved filesystem destination — not the pre-resolution path. If the tool exposes no such log, wrap its write tool in a shim (or an MCP proxy) that logs both strings for each approved action and blocks any mismatch.
  • Pin AWS Language Servers ≥ 1.69.0 and Cursor ≥ 3.0. Both received CVEs for this exact defect (CVE-2026-12958, CVE-2026-50549); earlier versions leave the approval dialog anchored to the pre-symlink path. Enforce the pin in your endpoint-management layer and refuse older builds at package fetch time, not just at first launch.
  • Treat “trust this directory” as a coarse permission, not the whole trust decision. The directory-trust checkbox at session start is not consent for arbitrary writes outside the trusted directory. Configure your policies so a session that trusts a directory cannot write to a resolved target outside it without a separate acknowledgment naming the resolved target.
  • Open third-party repos in Claude Code, Augment, or Windsurf on a scratch host with no credentials. Anthropic marks GhostApproval “outside our current threat model,” and Augment and Windsurf have not shipped fixes. If your workflow requires opening untrusted repos in any of the three, do the initial checkout in a container or VM whose filesystem contains no ~/.ssh, no ~/.aws, no ~/.config, and no git config — nothing the exploit could reach.
  • Alert on target mismatches, not model reasoning. Capture the requested path and fully resolved destination at the write boundary. Block and alert whenever they cross the trusted workspace boundary or the approval prompt does not name the resolved target.

Sources