Sandbox crashes when auto-denied file paths traverse symlinks

Resolved 💬 3 comments Opened Apr 8, 2026 by krame505 Closed May 24, 2026

Summary

Claude Code's sandbox (bwrap) becomes completely non-functional when auto-detected "secret" files exist behind a symlink. bwrap cannot mkdir -p through symlinks to set up deny bind mounts, so all sandboxed commands fail — not just those accessing denied paths.

Environment

  • Claude Code CLI on Linux (Debian 13, kernel 6.12.74)
  • Working directory: a Bazel monorepo at /home/lucas/matx
  • Bazel creates a convenience symlink: bazel-matx/home/lucas/.cache/bazel/_bazel_lucas/.../execroot/_main

Reproduction

  1. Have a Bazel project (or any project with a symlink in the workspace root)
  2. Behind the symlink, have any file with "secret" or "credential" in the name (e.g., secrets.tf, secrets.py, gcredentials.h)
  3. Run any sandboxed command — even echo "hello"
$ echo "hello"
bwrap: Can't mkdir parents for /home/lucas/matx/bazel-matx/ops/terraform/buildbarn-db/secrets.tf: No such file or directory

Root cause

Two issues compound:

1. Auto-deny heuristic adds paths through symlinks

The sandbox auto-detects files with "secret" or "credential" in the name and adds them to the filesystem deny list. It finds these files through the bazel-matx symlink and adds the symlink-relative path (e.g., /home/lucas/matx/bazel-matx/.../secrets.tf) rather than the resolved real path.

2. bwrap can't create deny bind mounts through symlinks

bwrap tries to mkdir -p the parent directories of each deny path to set up bind mounts. It cannot create directories through a symlink, so it fails. Since this happens during sandbox setup, every sandboxed command fails, not just commands that would touch the denied files.

Additional issue: over-broad auto-deny heuristic

The auto-deny heuristic matches any file with "secret" or "credential" in the name. In practice this catches many non-sensitive files:

  • secrets.py — Python's stdlib module for generating cryptographically strong random numbers
  • gcredentials.h / gunixcredentialsmessage.h — GLib/GIO system headers
  • BUILD.gix-credentials-0.27.0.bazel — Rust crate universe build file
  • secrets.tf — matched through a Bazel symlink to a cached copy, not the original

In this repo, the deny list had 70+ entries, almost all false positives from Python stdlib, system headers, and Bazel runfiles/output directories containing copies of secrets.py.

Workaround

Use dangerouslyDisableSandbox: true for all commands. This defeats the purpose of sandboxing entirely.

Suggested fixes

  1. Resolve symlinks before adding paths to the deny list, or skip paths that traverse symlinks.
  2. Scope the heuristic more narrowly — e.g., only match .env, .env.*, and files in well-known secret locations, rather than any filename containing "secret" or "credential" as a substring.
  3. Make auto-deny configurable — allow users to see and override the auto-generated deny list.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗