AIwave
Claude Code CVE-2026-39861: Symlink Escape and How to Harden It

Claude Code CVE-2026-39861: Symlink Escape and How to Harden It

Anthropic’s official response to CVE-2026-39861 — a sandbox escape in Claude Code — is essentially: “You shouldn’t have clicked OK.” That’s not a fix. It’s a reminder that the entire security boundary between Claude Code and your filesystem rests on a single trust prompt that most developers click through dozens of times a day without reading it.

The vulnerability (tracked in GHSA-vp62-r36r-9xqp) was published on 2026-05-08. It’s a symlink attack. The escape path is simple, reproducible, and works on unpatched installs right now. This post walks through what it actually does and builds three concrete mitigations so you’re not one careless prompt-click away from giving Claude unrestricted filesystem access.

What you need

You’ll need Claude Code installed locally, a Unix-like shell (macOS or Linux), and basic familiarity with filesystem permissions and chmod. No deep security background required — the attack mechanic is simple once you see it drawn out.

A symlink is just a pointer: a file that says “contents live over there.” When Claude Code resolves file paths during a session, it follows those pointers. The bug: it doesn’t verify that the target of a symlink falls within the intended working directory before operating on it.

The trust prompt is the only gate. Once you accept, Claude Code will read or write to whatever path the symlink resolves to — including paths well outside the project directory you intended to scope it to.

flowchart TD
    A[Claude Code session\nstarted in /projects/myapp] --> B[Claude resolves path:\n/projects/myapp/data/config]
    B --> C{Is this a symlink?}
    C -- No --> D[Read/write inside sandbox ✓]
    C -- Yes --> E[Follow symlink target:\n/etc/passwd or ~/.ssh/id_rsa]
    E --> F{Target checked\nagainst boundary?}
    F -- Should be --> G[Reject — out of scope]
    F -- CVE-2026-39861 --> H[Trust prompt shown]
    H -- User clicks OK --> I[Read/write outside\nsandbox ✗]

That’s the whole thing. No kernel exploit. No memory corruption. A symlink, a prompt, a click.

Step 1: Reproduce it safely in a temp environment

Set up an isolated demo with no real files at risk. Everything lives in /tmp.

# Create a fake sandbox root
mkdir -p /tmp/claude-sandbox/project
mkdir -p /tmp/outside-target

# Put something sensitive-looking in the "outside" directory
echo "secret data outside sandbox" > /tmp/outside-target/secret.txt

# Plant the symlink inside the sandbox pointing out
ln -s /tmp/outside-target/secret.txt /tmp/claude-sandbox/project/data.txt

# Verify the escape path
ls -la /tmp/claude-sandbox/project/
# data.txt -> /tmp/outside-target/secret.txt

# What Claude Code sees when it resolves the path:
cat /tmp/claude-sandbox/project/data.txt
# secret data outside sandbox

When you point Claude Code at /tmp/claude-sandbox/project and ask it to read data.txt, it issues a trust prompt. Accept it, and Claude reads — or writes to — /tmp/outside-target/secret.txt. On a real system, substitute ~/.ssh/id_rsa, ~/.aws/credentials, or any file the user running Claude Code can access.

Step 2: Mitigation #1 — OS-level filesystem permission hardening

The cleanest fix is making the escape fail at the operating system layer, before Claude Code ever sees the resolved path. Scope the working directory so symlink targets outside it are unreadable by the process.

# Create a permission-scoped working directory
mkdir -p ~/claude-workspaces/myproject
chmod 700 ~/claude-workspaces/myproject

# Run Claude Code as a dedicated low-privilege user (macOS/Linux)
# Create the user once:
sudo useradd -m -s /bin/bash claude-worker   # Linux
# macOS: use Directory Utility or dscl

# Give it access only to the workspace
sudo chown -R claude-worker ~/claude-workspaces/myproject

# Launch Claude Code under that user for the session
sudo -u claude-worker claude --workspace ~/claude-workspaces/myproject

On Linux, you can go further with a bind-mount chroot that physically prevents the process from resolving paths outside the workspace:

# Minimal chroot wrapper (Linux only)
WORKSPACE=~/claude-workspaces/myproject

sudo mkdir -p /chroot/claude/{dev,proc,tmp}
sudo mount --bind "$WORKSPACE" /chroot/claude/project
sudo mount --bind /dev /chroot/claude/dev
sudo mount -t proc proc /chroot/claude/proc

sudo chroot /chroot/claude /bin/bash -c "cd /project && claude"

Even without the full chroot, the permission-scoped user approach means a symlink pointing to /etc/passwd resolves to a file claude-worker cannot read. The escape path exists, but it leads nowhere useful.

Step 3: Mitigation #2 — CLAUDE.md trust-prompt audit pattern

The OS hardening handles the worst cases, but you want a second layer: make Claude surface every tool-trust prompt with full path context before accepting it. Add this block to your project’s CLAUDE.md:

## Security: Trust Prompt Policy

Before accepting any tool-use permission or file access trust prompt, you MUST:

1. Show me the full resolved path of the file or directory being accessed.
2. Confirm whether that path falls within the project root: `<project_root>`.
3. If the resolved path is a symlink, show both the symlink location AND the target path.
4. Wait for my explicit approval with the word "APPROVE" before proceeding.

Never auto-accept trust prompts. If a path resolves outside `<project_root>`, 
flag it as a potential sandbox escape and halt.

Replace <project_root> with your actual path. This won’t stop a determined attacker who also controls your CLAUDE.md, but it converts every trust prompt from a one-click bypass into a moment of deliberate review. In practice, it catches accidents — which is the overwhelming majority of real-world risk here.

Step 4: Mitigation #3 — Session isolation wrapper

Combine both mitigations in a shell wrapper you invoke instead of claude directly. Drop this in ~/bin/claude-safe and chmod +x it:

#!/usr/bin/env bash
# claude-safe: session isolation wrapper for CVE-2026-39861 mitigation

set -euo pipefail

WORKSPACE="${1:?Usage: claude-safe <workspace_path>}"
WORKSPACE="$(realpath "$WORKSPACE")"

# Refuse to start if workspace contains symlinks pointing outside itself
ESCAPING_LINKS=$(find "$WORKSPACE" -type l | while read -r link; do
  target="$(realpath "$link" 2>/dev/null || echo "BROKEN")"
  if [[ "$target" != "$WORKSPACE"* ]]; then
    echo "$link -> $target"
  fi
done)

if [[ -n "$ESCAPING_LINKS" ]]; then
  echo "⚠️  Symlinks escaping workspace boundary detected:"
  echo "$ESCAPING_LINKS"
  echo "Resolve these before starting a Claude Code session."
  exit 1
fi

echo "✓ Symlink pre-flight passed. Starting Claude Code in: $WORKSPACE"
exec claude --workspace "$WORKSPACE"

This pre-flight scan runs before any trust prompts appear. If a symlink in the workspace points outside it, the session never starts. You can extend find with -maxdepth 3 if your workspace is large and the scan is slow.

Where this breaks

None of these mitigations help if the symlink is planted after your pre-flight scan and before Claude resolves it — a race condition an attacker with existing code execution could theoretically exploit. The chroot approach is the only one that closes that window at the OS level.

Anthropic hasn’t announced a patch timeline yet. The advisory marks the vulnerability as “requires user interaction” (the trust prompt click), which is accurate but undersells the problem: user interaction is the entire security model of Claude Code’s sandbox, not a meaningful additional barrier. Until a patch lands that validates symlink targets against the declared workspace boundary before presenting trust prompts, treat the OS-level isolation as non-optional for any production or sensitive-data deployment.

Next steps

Check the GitHub advisory for patch updates — when Anthropic ships a fix that validates symlink targets before prompting, the claude-safe wrapper becomes redundant, but the permission-scoped workspace and CLAUDE.md audit policy are worth keeping permanently. They defend against the next trust-prompt vulnerability, whatever it turns out to be. Security in depth means you’re not betting everything on Anthropic’s next release.


← Back to blog

Get new posts in your inbox

New posts plus the occasional curated note on what's working with Claude and the agent stack.

No spam. Unsubscribe anytime.

Comments