Skip to content

Preserve ENOENT for missing mandatory deny paths#227

Open
n01e0 wants to merge 1 commit into
anthropic-experimental:mainfrom
n01e0:fix/linux-mandatory-deny-enoent
Open

Preserve ENOENT for missing mandatory deny paths#227
n01e0 wants to merge 1 commit into
anthropic-experimental:mainfrom
n01e0:fix/linux-mandatory-deny-enoent

Conversation

@n01e0

@n01e0 n01e0 commented Apr 30, 2026

Copy link
Copy Markdown

Summary

  • Distinguish user-specified Linux deny paths from mandatory deny paths.
  • Preserve ENOENT for missing mandatory deny directories instead of mounting /dev/null or an empty directory over them.
  • Keep placeholder mounts for explicit user denyWrite paths, where they are still needed to block creation.
  • Add regression coverage for missing mandatory directories such as .claude/commands.

Context

Linux bwrap cannot express “this path should remain missing, but must not be creatable” with bind mounts alone. The previous implementation used placeholder mounts for missing deny paths. That is still appropriate for explicit user denyWrite paths, but applying it to mandatory auto-protection changed normal filesystem semantics: missing mandatory directories could appear inside the sandbox as /dev/null or as an empty directory instead of returning ENOENT.

This PR skips placeholder mounts for missing mandatory deny paths, matching the documented Linux limitation that mandatory deny protection only applies to paths that already exist.

Tests

  • npm run typecheck
  • npm run lint:check
  • npx prettier --check src/sandbox/linux-sandbox-utils.ts test/sandbox/mandatory-deny-paths.test.ts
  • bun test test/sandbox/mandatory-deny-paths.test.ts
  • bun test test/sandbox/wrap-with-sandbox.test.ts test/sandbox/allow-read.test.ts

@vitaliyslion

Copy link
Copy Markdown

Confirming this resolves the symptom from #139 on my setup.

Environment

  • @anthropic-ai/sandbox-runtime 0.0.56
  • bubblewrap 0.11.2
  • Linux 7.0.11 (CachyOS, x86_64)
  • Used via the pi agent's sandbox extension (default config: allowWrite: [".", "/tmp"], no project sandbox.json)

Symptom before

Every sandboxed command bind-mounted /dev/null over the hardcoded DANGEROUS_FILES/DANGEROUS_DIRECTORIES resolved against cwd (the repo root). Because those paths don't exist in a normal project, the leaf-deny branch created character-device placeholders for .bashrc, .gitconfig, .gitmodules, .zshrc, .idea, .vscode, etc. Since git runs inside the same sandbox, it saw char devices in the working tree:

$ git add .
error: .bash_profile: can only add regular files, symbolic links or git-directories
fatal: adding files failed

git status was also polluted with all of those as untracked entries. They only existed inside the sandbox during command execution (a clean shell never saw them), which matches #139.

After applying this PR's logic

I backported the diff onto the compiled 0.0.56 dist/ (the source: 'user' | 'mandatory' tagging + skipping placeholder mounts for missing mandatory paths). Missing mandatory paths now stay ENOENT instead of becoming devices/empty dirs:

$ ls -la .bashrc .gitconfig
ls: cannot access '.bashrc': No such file or directory
ls: cannot access '.gitconfig': No such file or directory
$ git status      # clean
$ git add .       # succeeds

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants