DEV Community

TerminalBlog
TerminalBlog

Posted on • Originally published at terminalblog.com

Beware: Claude Code's bgIsolation Guard Can Be Bypassed With a Single Symlink

You set up Claude Code background sessions with bgIsolation precisely so an autonomous agent can't scribble outside its sandbox and into the shared checkout. It's the guardrail that lets you trust a claude --bg run. But a freshly filed, reproducible bug shows that guardrail can be walked around with one ln -s — and the file lands in your checkout anyway.

The Issue

GitHub issue #77287 reports that the bgIsolation containment check in Claude Code is a literal string prefix match against the checkout root — not a realpath-resolved path comparison. In a background session, a Write, Edit, or NotebookEdit addressed via a symlink alias path slips past bgIsolation, while the exact same file addressed by its canonical path is correctly blocked.

The reporter's reproduction is clean and minimal. They create a git checkout, make a symlink alias pointing at it, then ask a background session (Claude Code 2.1.207, no worktree.bgIsolation override) to write two files: one via the canonical path and one via the alias. The result: canon.txt is blocked by bgIsolation, but alias.txt is written straight into the checkout. The containment boundary effectively depends on which string you type, not on where the bytes land.

The bug carries the labels bug, has repro, area:security, and area:sandbox — so maintainers have a confirmed, reproducible path, and it lives in the sandbox/containment code, not a cosmetic corner.

What makes it worth a Beware post is the blast radius: bgIsolation is the primary mechanism operators rely on to run background agents safely against a shared checkout. If that boundary is string-based rather than path-based, every team that pins agents to a symlinked directory, a macOS /var-aliased temp, or a WSL mount point is silently running without the isolation they configured.

Are You Affected?

If you run claude --bg or claude agents background sessions against a repo that contains symlinks — or whose cwd is reached through one — your isolation guarantee may be weaker than you think. Quick self-diagnosis:

# 1. Does your working tree sit behind any symlink path?
pwd -P            # canonical path
pwd              # path as typed — if these differ, you're behind a symlink
readlink -f .     # another way to see the real target

# 2. Are there alias symlinks pointing INTO your checkout?
find . -maxdepth 2 -type l -exec sh -c 'echo "{} -> $(readlink -f {})"' \;

# 3. Any background/agent sessions currently running?
claude agents --json --all 2>/dev/null | jq -r '.[].id' 2>/dev/null

# 4. Check for unexpected files written under an alias path
#    (compare mtimes against when a background run was active)
find /path/to/checkout -newermt "-6 hours" -type f 2>/dev/null
Enter fullscreen mode Exit fullscreen mode

If pwd and pwd -P disagree, or you find symlink aliases into your checkout, treat bgIsolation as not fully enforced until a patch ships.

The Fix

There is no official patch yet (issue is open as of this writing), so harden your setup today:

  • Use real worktrees, not symlink aliases. Run background work in a genuine git worktree so the session's root is a distinct, canonical path. Avoid pointing an agent at a checkout through a soft link.
  • Set an explicit worktree.bgIsolation override to the resolved absolute path, so there's no ambiguity about the boundary the agent should honor.
  • Pin agent cwd to a realpath-canonical path before launching: cd "$(readlink -f "$PWD")" && claude --bg ....
  • Audit post-run file writes with the find command above; a background task should only ever touch files inside its own isolated tree.
  • Grant minimal allowedTools. For read-only background jobs, pass --allowedTools "Read,Grep" instead of Write, so a containment slip can't mutate disk even if it occurs.

Why It Happened

The root cause is a classic path-canonicalization omission. bgIsolation answers the question "is this path inside the shared checkout?" by checking whether the tool call's path string starts with the checkout-root string. That works until the filesystem hands you two different strings for the same inode — which is exactly what a symlink does. The canonical path (/real/checkout) and the alias (/real/alias) both resolve to the same bytes, but only one spells like the checkout root, so only one trips the prefix guard.

Correct enforcement means realpath()-resolving both the target path and the checkout root before comparing — so the boundary is defined by where the file physically lives, not by the spelling of the request. Until that lands, remember: with symlinks in play, bgIsolation is a string match, not a security boundary.

The asymmetry is easiest to see as a table. The same physical file behaves differently depending only on the path spelling used in the tool call:

Tool call target Path string bgIsolation result File written?
Write $CHECKOUT/canon.txt canonical blocked no
Write $ALIAS/alias.txt ($ALIAS$CHECKOUT) symlink alias bypassed yes
Edit $ALIAS/file.txt symlink alias bypassed yes
NotebookEdit $ALIAS/notebook.ipynb symlink alias bypassed yes

The takeaway: the enforcement decision is made on the spelling, so any path that resolves into the checkout but doesn't visibly start with the checkout root walks through.

FAQ

Is this a remote-code-execution or data-exfiltration bug?
No. It's a containment-bypass in bgIsolation: a background session that should be confined can write through a symlink alias into the shared checkout. Impact is local to your filesystem; it doesn't by itself grant network access or code execution.

Which Claude Code versions are affected?
The reporter reproduced it on 2.1.207 (Linux/Anthropic API). The logic is a string-prefix check, so any version using the same un-canonicalized comparison is likely vulnerable until an upstream fix is released.

Will switching to git worktree fully protect me?
Yes, for this specific bypass. A genuine worktree gives the background session its own canonical root path, so there's no alias string to exploit. Pair it with the realpath-pinned cwd and minimal allowedTools for defense in depth.

Want to save money on AI coding tools? Check out the best deals and discounts at aiFiesta.

Top comments (0)