GhostApproval — symlink following + approval-UI misrepresentation defeats human-in-the-loop in six AI coding assistants (CVE-2026-12958 / CVE-2026-50549)
Disclosed vulnerability08 Jul 2026🗺️ Tool-Using AgentWiz Research disclosed 'GhostApproval', a cross-vendor trust-boundary flaw in six AI coding assistants where a benign-looking repo file that is actually a symlink to a sensitive path makes the 'approve this edit' dialog display the innocent in-workspace path while the write lands outside the workspace — combining CWE-61 symlink following with CWE-451 UI misrepresentation to reduce human approval to a rubber stamp.
Root cause — why it happened
AI coding assistants ask you to approve risky edits before they make them — that human 'OK' is supposed to be the safety net. Wiz found that a booby-trapped project can defeat it. One file in the project looks ordinary (say, project_settings.json) but is secretly a shortcut (a symlink) that points to a sensitive file outside the project — in the demo, the developer's SSH keys file. When the assistant offers to edit that file, the approval box shows the harmless in-project name, so the developer clicks 'approve'. But the write follows the shortcut and lands on the real, sensitive file instead — planting an attacker's key that grants remote login. The person approved, but they were shown the wrong thing.
Risks this case illustrates
Named in the standard (OWASP/ATLAS/NIST) lens. Click a highlighted component in the diagram below to see which risks attach where.
How it unfolded
An attacker plants a symlink disguised as an ordinary file
The attacker prepares a normal-looking project and hides one booby-trapped file in it. The file has a boring name like project_settings.json, but it isn't a real file — it's a shortcut pointing to a sensitive file outside the project, such as the developer's SSH keys. To a person browsing the repo, it just looks like one more config file.
# In the repo, this looks like a normal file listing: $ ls -l project_settings.json lrwxr-xr-x project_settings.json -> ../../../.ssh/authorized_keys # ^ it is a SYMLINK, not a real config file # ^ target is OUTSIDE the workspace (developer's SSH keys) # Name and target are illustrative of the class, not operational.
Controls & guardrails — what would have stopped it
The fix that actually closes this is making the approval box honest: it must show the real file the write will hit — following any shortcuts — not just the innocent name. And the assistant should simply refuse to write anywhere outside the project. Then even a booby-trapped file name has nothing to trick the developer with, because they'd see the real target (or the write would be blocked outright).
- Human-in-the-loop approval on high-risk actions
Approval fatigue turns gates into rubber stamps; gates placed after the point of no return do nothing; and approvers can be misled by a model-written summary of the action.
- Tool argument validation & sandboxing
Validates form, not intent — a well-formed call to a permitted tool can still be the wrong call. Sandboxing adds latency and isn't always feasible for tools that touch production.
- Least-privilege identity & scoped credentials
Doesn't prevent manipulation — only caps its reach. Hard to get right operationally; over-broad scopes are the common real-world failure.
- Runtime monitoring & anomaly detectionaddressesExcessive Agency
Detects the anomalous, not the novel-but-subtle; high false-positive rates cause alert fatigue. Always a step behind a sufficiently quiet attacker.
- Full-trace audit logging
Logging is forensic, not preventive — it explains harm after the fact. Useless if no one reviews it or if the materialised context isn't captured.
- Governance: risk assessment, red-teaming & incident responseaddressesOverreliance / Automation Bias
Process reduces likelihood and speeds recovery but executes no technical control itself; weak follow-through makes it theatre.
- Loop/cost circuit-breakers & consistency checksaddressesExcessive Agency
Thresholds are blunt — too tight breaks legitimate long tasks, too loose lets damage accrue first. Catches runaway dynamics, not a single well-formed bad decision.
Lessons
- ▸ A human-in-the-loop gate is only a control if the human sees ground truth — an approval dialog that shows an innocent path while the write lands elsewhere reduces oversight to a rubber stamp (CWE-451).
- ▸ Canonicalize before you display and before you write: the path the developer approves must be the fully-resolved real target after following symlinks, and the executed write must be pinned to it (no TOCTOU gap).
- ▸ Unix-era weaknesses don't disappear in AI tools — symlink following (CWE-61) plus UI misrepresentation (CWE-451) is a decades-old combination that agentic file editors re-introduced.
- ▸ Least-privilege at the tool layer is the backstop: a file tool that denies any write resolving outside the workspace breaks this chain even when the approval UI is fooled.
- ▸ The agent even 'knowing' the real target isn't protection if that knowledge never reaches the human — the risk signal has to be surfaced at the decision point, not buried in private reasoning.
- ▸ Vendor responses vary and are the vendors' own: some shipped fixes with CVEs, one declined as 'outside our threat model' — a class flagged across six assistants is not settled just because part of the industry patched it.
Proposals & gaps this case surfaced
Non-destructive suggestions for the library — proposed, not adopted.
A human-in-the-loop approval gate fires but presents the approver a misrepresented view of the action's real effect, so consent is given for something other than what executes. Arises when the approval dialog renders a pre-canonicalization or display-only argument (e.g. an in-workspace file path) while the runtime resolves and acts on a different real target (e.g. a symlink pointing outside the workspace), or more generally when the approved arguments are re-derived after approval. Combines UI misrepresentation of critical information (CWE-451) with, in the file-edit case, symlink following (CWE-61), reducing oversight to a rubber stamp.
When a human-in-the-loop gate approves an agent action, the dialog must present ground truth, and the runtime must execute exactly what was approved. For file edits: (1) canonicalize the target (follow symlinks) before displaying it, and prominently flag any destination outside the workspace — never show only the innocent in-workspace display path (CWE-451); (2) pin the executed write to the approved canonical path so the acted-on target cannot be re-derived after approval (close the TOCTOU gap); (3) deny by default any write that resolves outside the workspace root, so a least-privilege file tool fails closed even if the dialog is fooled (CWE-61). Closes the GhostApproval vector where a symlinked repo file makes the approval dialog misrepresent an out-of-workspace write; generalises to any approval surface where the displayed decision can diverge from the executed effect.
This case shows a gap people miss: we treat 'a human approves it' as a safeguard, but we rarely check that the approval box shows the truth. Here the box showed a harmless file name while the write hit a sensitive file behind a shortcut. We should require that an approval prompt display the real, resolved effect — otherwise the human is approving a lie.
These surface as proposals across the Control Library and Risk Taxonomy; adopt them by hand when ready.
Sources
- GhostApproval: A Trust Boundary Gap in AI Coding Assistants — Wiz Research (primary disclosure, 8 Jul 2026) ↗
- Bug in top AI coding agents shows Unix-era security headaches never die — The Register (8 Jul 2026) ↗
- GhostApproval Symlink Flaws Could Let Malicious Repos Run Code in AI Coding Agents — The Hacker News ↗
- GhostApproval: A Trust Boundary Gap in AI Coding Assistants — Wiz Research (primary disclosure) ↗ — Original research; the symlink (CWE-61) + approval-UI misrepresentation (CWE-451) chain across six assistants. Vendor list, CVE assignments and status are Wiz's.
- NVD — CVE-2026-12958 ↗ — Amazon Q Developer — missing symlink check / out-of-workspace write (fixed per Wiz).
- NVD — CVE-2026-50549 ↗ — Cursor — in-workspace symlink path resolution enabling out-of-bounds write (fixed per Wiz).
- Bug in top AI coding agents shows Unix-era security headaches never die — The Register ↗ — Secondary reporting; the CWE-61 / CWE-451 framing and vendor responses.
Practise the risk class — related scenarios
A support chatbot invents a policy — and the company is held to it
An ops agent gets one god-mode credential — and one misread wipes production
A coding agent asks to write ./notes.txt — the file it actually overwrites is your SSH keys
A team of agents agrees its way into a confidently wrong answer — and a runaway loop
A text-to-SQL agent runs the model's output straight at the database
A jailbroken agent decomposes one malicious goal into hundreds of harmless-looking steps — and per-step filters never see the attack
A poisoned issue makes the agent lie to the human who approves its actions
Told it's being shut down, an agent reaches for leverage — with no attacker in sight
A fake Sentry error report hijacks a developer's coding agent into running a shell command
A 'safe' dataset preview turns an upload into code execution on the pipeline's workers
A shopping page tells the agent to do something the user never asked for
One click provisions an attacker-configured agent inside your own workspace
An attacker plants prompt injection in the audit trail — so the LLM that hunts them erases the evidence
Encoded public text is laundered across an agent handoff into an on-chain transfer
An attacker captures the agent's bearer token — and inherits its authority
A forged peer registers on the agent directory — and the planner enlists it
A poisoned web page hijacks a research agent — and the planner acts on its behalf
A GUI agent clicks 'Continue' — but the screen moved, and it lands on 'Send'