CLAUDE LABJP
2.1.273 — A round of connection work landed together: five opt-in headers for LLM gateways, and a notice when Claude Code stops trying to reconnect an MCP server09/29 — The date beside claude-sonnet-4-5 is 12 days out, but it is an earliest-possible estimate. The model is still Active, and public retirements get at least 60 days noticeMCP — People keep asking to reconnect a dropped server without ending the session. The disconnect is now announced, but reattaching is still something you do by handNEW — A scheduled task ran some days and not others. The cause was that only one folder had been bound to itWINDOWS — When Cowork fails on its very first task, check developer mode and the setup state before going looking for a causeHANDOFF — Before a long draft gets too heavy for one chat, decide on the three things the summary must carry into the next one2.1.273 — A round of connection work landed together: five opt-in headers for LLM gateways, and a notice when Claude Code stops trying to reconnect an MCP server09/29 — The date beside claude-sonnet-4-5 is 12 days out, but it is an earliest-possible estimate. The model is still Active, and public retirements get at least 60 days noticeMCP — People keep asking to reconnect a dropped server without ending the session. The disconnect is now announced, but reattaching is still something you do by handNEW — A scheduled task ran some days and not others. The cause was that only one folder had been bound to itWINDOWS — When Cowork fails on its very first task, check developer mode and the setup state before going looking for a causeHANDOFF — Before a long draft gets too heavy for one chat, decide on the three things the summary must carry into the next one
Articles/Claude Code
Claude Code/2026-07-19Advanced

A Committed Symlink That Points Outside the Worktree — Auditing Repos Before You Let AI Spin Up Parallel Trees

Claude Code 2.1.212 fixed a bug where a committed symlink under .claude/worktrees could be followed during worktree creation and write outside the repo. The patch closes the following side. Here is an audit script for the committed side, plus a quarantine workflow.

Claude Code253worktree3security18automation110

Premium Article

One line in the Claude Code 2.1.212 changelog stopped me. It fixed a bug where a symlink committed under .claude/worktrees could be followed during worktree creation and end up writing a file outside the repository. It is already fixed.

But before feeling relieved, I thought about what I actually hand to Claude Code. In my own automation as an indie developer, I clone several repositories every day, cut worktrees, and run work in parallel on top of them. I had never once looked at the contents of those repositories through the lens of symlinks.

The patch closes the following side — it stops the tool from traversing a malicious link. But whether a dangerous link is committed in the first place is something only the person handling the repo can inspect. A patch does not guard every AI tool and every operation, and worktree creation is not the only path that can follow a link. This is a record of how I run that inspection on my own machine.

Git commits a symlink as "the target string"

One premise first. Git does not treat symlinks specially — it stores them as a file whose content is the link's target string. Only the file mode differs from a normal file (100644): it becomes 120000.

Here is what that looks like in a real repo.

# Deliberately create a dangerous example (for testing; we delete it right after)
ln -s /etc/hosts ./link-to-hosts
git add ./link-to-hosts
 
# See how git recorded it
git ls-files -s ./link-to-hosts
# It enters as 120000, not 100644:
# 120000 a1b2c3...   0   link-to-hosts
 
# The blob content is literally the target string
git cat-file blob :./link-to-hosts
# /etc/hosts

A 120000 entry has, as its blob content, the entire link target. Whether it is /etc/hosts or ../../../../home/user/.ssh/authorized_keys, as long as it is a string, git commits it without complaint. There is no validation here. So a link pointing outside the repository can slip in looking exactly like any ordinary change.

core.symlinks makes this trickier. If it is true (the default in most environments), checkout restores the link as a real symlink. If false, the target string is written out as a normal file and nothing follows it. The same commit becomes either "a link that gets followed" or "plain text" depending on config. That gap is what makes inspection hard.

Why worktree creation could escape

Writing across a symlink to reach outside is not specific to worktrees. In general, if a write path passes through a symlink, the OS writes to wherever the link resolves. If b in a/b/c is a link to /tmp/evil, a write to a/b/c lands in /tmp/evil/c.

During worktree creation, the tool writes bookkeeping files under .claude/worktrees. If part of that path is a committed link pointing outside the repo, the write leaks out of the repository. Depending on checkout order, the link may already exist as a concrete entry at the moment of the write — the classic TOCTOU (time-of-check to time-of-use) gap.

The 2.1.212 fix stops that traversal on the tool side. It deserves credit. But the question I had to own was a different one: "Am I letting AI touch a repository that contains such a link in the first place?" The clone source is not always under my control. If I open forks, templates, or vendored external repos as worktrees, the surface to inspect grows.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
An audit script that surfaces only the dangerous symlinks in a repo — absolute paths, links escaping the root, links into managed directories (copy-paste ready)
How git commits a symlink as its target string, and the following mechanism that let worktree creation escape the repo
Even on a patched version: the clone → audit → quarantine → worktree order, and the minimal check to drop into CI
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

Related Articles

Claude Code2026-08-05
Passing the Request, Not the Secret — Where Sandbox Credential Masking Works and Where Substitution Breaks
Claude Code's sandbox credential masking lets processes read sentinel values while a proxy swaps in the real secret at send time. I rebuilt it as a minimal proxy and measured which auth schemes survive the swap, which break, what happens when the secret rides in the body, and where chunked framing makes the substitution miss entirely.
Claude Code2026-08-04
Tightening Filesystem Isolation Separately from the Network — Collect the Paths, Then Squeeze the Write Surface
Claude Code v2.1.216 lets you control filesystem isolation independently from network isolation. Before tightening anything, I traced what a real job actually touches, split reads from writes, and measured how stable the path set is across repeated runs. The numbers changed how I wrote the allowlist.
Claude Code2026-09-02
Two hooks that record every model switch and stop the ones you never agreed to
Build a PostModelSwitch hook that logs every model change and a PreModelSwitch hook that blocks unapproved switches during unattended runs. Complete scripts, measured timings, and the reason the two jobs must stay separate.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links