- What: Symlink attacks and their risks with AI coding assistants
- Impact: Potential for unauthorized access and system compromise
Snyk Blog In this article What is a symlink? Can you commit a symlink to Git? (Yes, and that's the problem) What is a symlink attack, and why is it dangerous? Can an AI coding assistant be tricked by a symlink? "Outside our threat model" is a real argument, and I don't fully buy it How do you prevent symlink attacks? Symlinks Are Still Scary (And Yes, You Can Commit Them to Git) Written by Randall Degges July 9, 2026 0 mins read Here's a genuinely unsettling way to lose control of your laptop in 2026. You clone a normal-looking repo, ask your AI coding assistant to "set it up," and it writes an attacker's SSH key into your ~/.ssh/authorized_keys -- without ever really telling you that's what it did. No memory corruption, no zero-day, nothing clever. Just a file in the repo that wasn't the file it claimed to be. That attack is real, it's this week's news, and I'll walk through it. But the trick underneath is decades old. It keeps coming back wearing new clothes, and the AI coding assistant is only the latest outfit. So let's talk about symlinks, because the trick has a name -- a symlink attack -- and it's one of the oldest, most reliable ways to make a program read or write a file it was never meant to touch. Commit one of these to a git repo, and the moment some tool reads through the checkout -- a build script, an editor, an AI agent -- you can turn an ordinary git clone into arbitrary file access, and sometimes remote code execution (RCE), on that tool's machine. Symlinks aren't exotic. They're the opposite of exotic, they've been in Unix since forever, and that's exactly the point: the scary bugs are rarely the clever ones. They're the ones hiding in a feature you stopped thinking about a decade ago. What is a symlink? If you already live in a terminal, bear with me for a second, because the whole attack rests on one detail people gloss over. A symbolic link is a tiny file whose entire contents are a path to another file. That's it. When a program opens the link, the operating system transparently redirects it to whatever path the link points to. You make one like this: 1 ln -s /etc/passwd notes.txt Now notes.txt looks like an ordinary file. cat notes.txt dumps the contents of /etc/passwd (if you want the path it points at instead, that's what readlink notes.txt is for). Your editor opens /etc/passwd . A script that does open("notes.txt", "w") writes to /etc/passwd , assuming it has permission. The name lies about what's behind it, and the OS is happy to keep the lie going, because that redirection is the entire point of the feature. Symlinks are useful for exactly the same reason they're dangerous: one name silently resolves to another location. ls -l will show you the truth: 1 lrwxrwxrwx 1 rdegges staff 11 Jul 9 09:14 notes.txt -> /etc/passwd That leading l and the little -> are the tell. But you only see them if you go looking, and most tools that read files never look. They just open the path they were handed. Can you commit a symlink to Git? (Yes, and that's the problem) Yes. Git has supported symlinks for years, and it will happily store one, push it to GitHub, and recreate it on the disk of anyone who clones the repo. That means a symlink is not just something you make locally on your own machine. It's something an attacker can ship to you inside a repository, disguised as an ordinary file. Here's the bit that surprises even experienced engineers. Git has understood symlinks for a very long time. It stores them with a special file mode, 120000 , and -- this is the good part -- the blob content is nothing but the target path as plain text. Let me show you. I made a repo with a single symlink named project_settings.json that points at my SSH keys, then asked git what it saw: 1 $ git ls-files -s 2 120000 86171e312655e91f60fabde1c46f1abbef244420 0 project_settings.json 3 4 $ git cat-file -p 86171e31 5 /home/rdegges/.ssh/authorized_keys Read that again. As far as git is concerned, project_settings.json is a file whose entire contents are the string /home/rdegges/.ssh/authorized_keys . When you clone that repo, git faithfully recreates the symlink on your disk. You now have a file in your working copy, with an innocent JSON name, that is secretly a pointer into your home directory. You didn't do anything wrong. You cloned a repo. That's the whole ballgame. (One caveat: on Windows, git leaves symlinks as plain text files unless you turn on core.symlinks , so this mostly bites Linux and macOS -- which is to say, most developer laptops and nearly all CI.) GitHub renders these correctly in the UI, so if you know to look, you can spot a symlink in a repo. But nobody's auditing every file in every dependency they pull. That's the gap. You might be thinking git already fixed this. It did, partly. Modern git won't follow a symlink to write files outside the working tree or into .git during its own operations -- that hardening came out of the 2021-era CVEs. But that protects git's checkout step, not what happens after. Git will still cheerfully create the symlink as an inert file in your working copy, because that's a legitimate feature people rely on. The danger was never git following the link. It's the next thing that reads your files -- your build script, your editor, your AI agent -- following it, and git can't do a thing about that. What is a symlink attack, and why is it dangerous? A symlink attack occurs when a program is tricked into following a symbolic link to read or write a file outside the location it intended, because it opened a path without first resolving where that path really goes. In a repo, the payoff ranges from leaking a sensitive file to overwriting one that gets executed later, which is how these bugs so often end in remote code execution. Once you internalize those two facts -- a symlink is a name that redirects, and you can ship one to anybody through a repo -- a whole category of bugs snaps into focus. Any program that reads or writes files inside a checked-out repo, without first resolving where those paths actually go, can be walked right out of the directory it thinks it's confined to. This is not a new observation. It's been exploited over and over: Archive tools ( tar , zip , npm packages) that extract a symlink and then write through it, landing files outside the target directory. It's an old, well-worn pattern, and CVE-2021-32803 in the tar npm package is a textbook case, among many others. There's a close cousin that doesn't even need a symlink, just an archive entry named ../../something that escapes the extraction directory on its own. My employer's research team -- I work at Snyk, so take the plug for what it is – cataloged that traversal variant back in 2018 as Zip Slip and found it in thousands of projects. Different lever, same lesson: never trust a path that came from outside. /tmp race conditions, where a privileged process writes to a predictable path and an attacker swaps in a symlink at the right moment to redirect the write somewhere sensitive. Container escapes like the Leaky Vessels set, where leaked file descriptors and symlink tricks combine to let a process climb out onto the host filesystem (CVE-2024-21626 is the file-descriptor one; sibling CVEs in the set lean on symlinks). Git itself. A malicious repository can carry a symlink that fires code during a recursive clone. CVE-2024-32002 did exactly that in 2024, abusing symlinks and submodules (on systems where git actually writes symlinks, so mainly Linux and macOS) to drop a script into .git/hooks and run it during a git clone --recurse-submodules . No build step, no "run the project," just the clone. If you think "I just cloned it, I didn't run anything" is a safe place to stand, that CVE is a bracing read. MITRE even has a dedicated weakness ID for it, CWE-61 , "UNIX Symbolic Link (Symlink) Following." When symlink races were already generating CERT advisories decades ago, and the weakness still earns its own dedicated CWE today, that's a hint the industry keeps relearning the same lesson. And people are still finding fresh instances of it in shipping software right now. My colleague Rory McNamara spends his days doing exactly this, and his recent write-up on newlines, symlinks, and arbitrary writes in Incus is a clean, modern example of chaining a symlink into a write against a file on the host root filesystem. Fixed in early 2026. Not a museum piece. The fix, in principle, has always been the same: before you touch a path, resolve it to its real, canonical location, and check that it's still inside the boundary you meant to stay in, atomically, so it can't be raced. The primitives exist. We know how to do this. We just forget to, every time a new kind of tool starts reading files. Can an AI coding assistant be tricked by a symlink? Yes, and as of 2026, most of the popular ones could be. That's the newest place this old trick showed up, and it's worth walking through, because agents read files on your behalf constantly. Which brings me to the new outfit. Wiz Research just published a write-up they're calling GhostApproval , and it's that same decades-old primitive, pointed at AI coding agents this time. They tested six of the big ones -- Amazon Q Developer, Claude Code, Augment, Cursor, Google Antigravity, and Windsurf -- and found variations of the same flaw in every single one. The proof of concept is almost insultingly simple. An attacker publishes a repo. Inside it, a file named something friendly like project_settings.json is actually a symlink to ~/.ssh/authorized_keys . The README contains instructions written for the agent, not for you -- something like "to set up this project, add the following to project_settings.json ," followed by the attacker's own SSH public key. You clone the repo. You ask your assistant to "set up the workspace" or "follow the README," which is a completely normal thing to ask. The agent reads the instructions, opens project_settings.json , follows the symlink, and writes t