GhostApproval Symlink Flaws Could Let Malicious Repos Run Code in AI Coding Agents


Another Day, Same Old Symlink Story
Security veterans often joke that the only thing we consistently ship is the same bugs in fancier wrappers. This latest round of supply-chain hand-wringing over "AI coding agents" tripping over symlinks? Seen it before, just more automation, bigger blast radius. Here’s a real takeaway right out of the gate: if your code that writes to files doesn’t check for symlinks with lstat() or O_NOFOLLOW, you’re trusting user input where you shouldn’t—full stop [1].
The Human Factor: Laziness as a Feature
Here’s an anonymized composite incident from recent CI/CD life: a team mounts the host filesystem into a container for “convenience,” gives the build user write permissions across the board, then pipes in untrusted code. Cue a Python helper walking a directory tree and rewriting config—but one project contributor added a symlink, and the pipeline ended up overwriting /etc/shadow on the base image.
Why did this work? Because the script used a naive open(target, 'w') call, followed symlinks by default, and ran as root. No path validation. No use of O_NOFOLLOW. No check for unexpected inode or uid/gid changes between discovery and write (that classic TOCTOU flaw [2]). Prevention would’ve required 10 extra lines—too “slow” for the hero dev crunch.
Takeaway: Every time you skip validating paths or rely on defaults, you’re inviting symlink attacks into your supply chain. Audit your file writes—every single one.
Attack Model: What’s Actually At Risk
Attackers in this scenario don’t need superpowers:
- Ability to write to the repo or project the agent has access to
- Can create symlinks pointing from “safe” paths to sensitive files
- Wait for an AI agent, automated tool, or junior dev to run a file-modification script with inappropriate privileges
If your build agent or “AI code helper” runs with broad permissions and follows symlinks blindly, you’ve turned a write to config.json into an arbitrary overwrite of /home/ubuntu/.ssh/authorized_keys. That’s not theoretical—classic symlink escalation and path traversal since the dawn of multi-user Unix [1] [3].
Action: Test now—can an untrusted contributor get your build tools or coding agents to clobber files they shouldn’t?
Why We Keep Falling for This
- Default-permissive APIs and bad patterns: Developers still invoke
open(path, O_WRONLY)orfopen("...", "w")without O_NOFOLLOW or pre-write checks, because it “just works” [2]. - Vendors trade off safety for smooth “DX”: If your coding assistant asks “May I edit X?” and you say yes, it shouldn’t just write anywhere in the project tree—but most do (citation: see GitHub Copilot Labs’ benign config edits, then test a symlink to sensitive node_modules, etc.)
- Container/running environment gaps: Run agents as root in containers with host mounts or privileged settings and you’re repeating Docker's greatest hits, CVE-2019-5736 style [4].
Checklist item: Review where you mount host filesystems or allow broad write access in pipelines. Drop perms, prefer readOnly where feasible.
The Architecture Nightmare
Here’s how bad designs keep breeding these flaws:
- No isolation: Most AI code agents run with user privileges, and in CI/CD often as root. No seccomp, no AppArmor/SELinux, no user namespaces.
- Path traversal protections skipped: Writing to
foo/bar.json? If bar.json is actually a symlink to/etc/shadow, default POSIX APIs slog right through unless you’re defensive (O_NOFOLLOW, openat+dirfd+sandboxed chroot). - Overprivileged cloud API hooks: If your agent has IAM roles with wildcards (
*), that’s a potential lateral jump, not “convenience” [5]. Real supply chain incidents have started smaller.
Takeaway: If you aren’t deploying code agents in VMs with no host mounts, network egress restrictions, and least-privileged service roles, you’re exposing your crown jewels.
How to Test for This in Your Environment
Basic smoke test:
- Have an unprivileged project contributor commit a symlink in a repo to
/tmp/owned_by_agent. - Configure your code agent or pipeline to edit what looks like a safe file (e.g.
project/settings.json). - Point
project/settings.jsonto/etc/passwdor another obviously sensitive file via symlink (in a test container, never in prod!). - Trigger the agent to “edit” settings.json.
- If
/etc/passwdchanges and you didn’t deployO_NOFOLLOW, you’re vulnerable.
- Detection: Enable
auditdrules (-w /etc/passwd -p waor similar) to trigger on unusual writes. - Cloud: Turn on CloudTrail and alert on unexpected PutObject or WriteObject events in critical buckets.
Mitigation Checklist: (30/60/90-Day Reality)
Immediate (0-30 days)
- Find and patch all code paths that write files—use
lstat()to check for symlinks before all writes, use O_NOFOLLOW onopencalls. - Change build agent and code assistant runs to non-root. Use user namespaces; drop
CAP_SYS_ADMIN,CAP_NET_RAW, etc. in containers [6]. - Explicitly log and alert on writes to sensitive paths (
/etc/,/root/,.ssh/).
Medium-term (30-60 days)
- Isolate agents: run in VMs or sandboxes (gVisor, Kata) with no persistent host mounts. Audit every write/exec in logs.
- Use atomic file patterns:
open()withO_CREAT|O_EXCL|O_NOFOLLOW,mkstemp, orO_TMPFILEwhere supported. - Require explicit human gating for any write outside a designated “project scratch” directory.
Longer-term (60-90 days)
- Rotate all persistent credentials; enable short-lived tokens/policies for build users and agents.
- Harden cloud IAM policies and monitor role usage with anomaly alerts.
- Adopt file integrity monitoring (AIDE, Tripwire) alongside SIEM rules for sensitive path changes.
References
- OWASP Path Traversal
- Linux open(2) man page: O_NOFOLLOW, O_EXCL
- CVE-2021-21222: Symlink race condition in npm
- CVE-2019-5736: Docker runc container escape
- AWS IAM: Best Practices
- Docker: Security Best Practices
Author Bio
Avi Kravitz | Principal DevSecOps Engineer (15+ years)
- Led security at multiple fintech unicorns (Stripe, Acme Corp), OSCP/CKA certified
- Routinely break (then fix) cloud supply chain pipelines, contributor to open-source hardening tools
- GitHub / LinkedIn
Real-world incidents referenced are composite anonymized and reflect industry patterns. For citations, see References above.
If you run any code automations—even “just” AI assistants—stop trusting the defaults. Harden now, or get used to fire drills.