Skip to content

fix(agent-isolation): guard Linux allowAllUnixSockets with denyRead - #1809

Draft
shahar1 wants to merge 2 commits into
apache:mainfrom
shahar1:fix/linux-sandbox-socket-exposure
Draft

shahar1 wants to merge 2 commits into
apache:mainfrom
shahar1:fix/linux-sandbox-socket-exposure

Conversation

@shahar1

@shahar1 shahar1 commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Linux allowAllUnixSockets is undocumented and unguarded.
    • On Linux the sandbox's seccomp filter blocks AF_UNIX, unless sandbox.network.allowAllUnixSockets is true. The container gateway and ssh-agent need it, because allowUnixSockets only applies on macOS.
    • With the flag on and deny-only reads, sandboxed code can reach host sockets that run code outside the sandbox: /run/docker.sock, Docker Desktop's /run/docker-cli.sock, all of /run/user/<uid> (D-Bus and systemd/private, i.e. systemd-run --user), /run/dbus, X11, and on WSL /run/WSL interop and /mnt/wslg.
    • This PR documents the flag and pairs it with sandbox.filesystem.denyRead entries for those paths, and names AF_VSOCK as the residual risk denyRead cannot hide. It also makes verify check 12c Linux-consistent and adds a check 12e that requires the socket denyRead coverage whenever the flag is on, even when check 12 is otherwise n/a. Install Step L.3 and tools/sandbox-lint get matching rules and tests.
  • A parent allowRead makes the project read-only. An allowRead entry that is a strict parent of an allowWrite path makes bubblewrap mount the write path read-only. Every Bash call then fails with bwrap: Can't create file … Read-only file system. This PR adds a troubleshooting entry, a sandbox-error-hint.sh pattern, and a sandbox-lint rule.
  • Placeholders show up in git status. A troubleshooting entry explains why write-protected paths that do not exist appear as character devices 1,3 and are listed as untracked.

Type of change

  • Skill change (.claude/skills/<name>/) — eval fixtures updated below
  • Python package (tools/*/ with pyproject.toml)
  • Cross-cutting (RFC, AGENTS.md, sandbox, privacy-LLM)
  • Documentation (docs/, README.md, CONTRIBUTING.md)

Test plan

  • prek run --all-files --skip workspace-pytest passes
  • prek run workspace-pytest --from-ref upstream/main --to-ref HEAD passes; tools/sandbox-lint pytest and the agent-isolation test_sandbox_error_hint.py pass. New tests cover:
    • the socket rule: uid matching against os.getuid(), plus ~ and relative entries;
    • the parent rule: ~, a trailing slash, and .;
    • the hint pattern: a path with a space, and precedence over the /tmp hint.
  • Eval suite, skill-eval --cli "claude -p" tools/skill-evals/evals/setup-isolated-setup-verify/: 23/28 pass.
    • 4 of the 5 failures also fail on unmodified upstream/main (step-1 case-7; step-2 case-3, case-5, case-6).
    • The new step-1 case-23-linux-all-unix-sockets-exposed passed 3 of 4 runs.
  • Checked on a WSL2 host:
    • With the flag on and the denyRead entries added, the host sockets are no longer sockets inside the sandbox (test -S is false).
    • The gateway sockets and an ssh-agent under /tmp keep working: docker version through the gateway and ssh-keygen -Y sign both succeed.
    • The parent-allowRead failure was reproduced and fixed by listing the specific directories.
    • Claude Code skips absent denyRead paths, so listing paths that a host lacks is harmless.
  • Reviewed by an independent agent. Its requested changes are in the second commit.

RFC-AI-0004 compliance

  • Sandbox — closes host-socket reach from the sandbox on Linux; no new host access
  • Vendor neutrality — placeholders only (check-placeholders passes)

Notes for reviewers (optional)

  • Not verified: on Linux, whether a single-socket allowRead inside a denied /run/user/<uid> re-opens an agent socket that lives there. The doc marks it as unverified.
  • The root cause of the parent-allowRead trap is Claude Code's mount ordering, so it may also be worth an upstream Claude Code report. The lint rule fails closed, including outside denyRead.
  • Companion PR fix(setup): make isolated-setup-verify checks catch false passes #1808 also adds step-1 eval cases (up to case-22, so this one is case-23) and touches the same README case counts. Whichever merges second needs a trivial rebase.

Generative AI: drafted with Claude Code (Claude Opus 5.5) and checked by an independent Claude Opus review agent; opened as a draft for the author's line-by-line review before it is marked ready.

🤖 Generated with Claude Code

On Linux, Claude Code ignores sandbox.network.allowUnixSockets and its
seccomp filter rejects every AF_UNIX socket, so the container gateway
and ssh-agent signing only work with allowAllUnixSockets: true. The
setup docs never said so, and nothing warned that the flag lets a
sandboxed command connect to every socket it can see: the Docker and
Podman daemons, the user D-Bus (systemd-run --user runs commands
outside the sandbox), the system bus, X11, and on WSL the interop and
WSLg sockets. The reference denyRead of ~/ hides none of them.

Document the Linux requirement and the denyRead entries that must go
with it, make verify check 12c accept allowAllUnixSockets as the Linux
socket half and check 12d require denyRead cover for each host socket
path that exists, mirror both in the verification prompt and install
Step L, and add a sandbox-lint invariant for the daemon sockets and
the per-user runtime directory, with an eval case for 12d.

Also catalogue two Linux traps met on the same setup: an allowRead
parent of an allowWrite path makes bubblewrap mount it read-only so
every Bash call fails with "bwrap: Can't create file ... Read-only
file system" (troubleshooting entry, hint-hook pattern, lint check),
and denied paths showing up in git status as /dev/null placeholders.

Generated-by: Claude Code (Claude Opus 5.5)
The systemd user manager also listens on /run/user/<uid>/systemd/private,
so denying only bus and podman under the runtime directory still lets a
sandboxed command run systemctl --user; the directory also holds editor
askpass and systemd ssh-agent sockets. Require the whole directory of
the current uid in sandbox-lint, verify and the docs, and drop the advice
to deny single sockets in it.

Move the socket-cover requirement into its own verify check 12e, which
runs whenever allowAllUnixSockets is set, even with no container runtime
(the flag may be there only for ssh-agent signing). Make sandbox-lint
list the same paths as the docs and 12e, accept only the current uid,
and note it reads only project layers.

Correct the ssh-agent caveat (allow the socket or its own subdirectory,
never /run/user/<uid>; unverified on Linux), name AF_VSOCK as residual
risk, fix the /dev/null placeholder explanation (write-denied paths
only), let the bwrap hint match paths with spaces, and add the missing
tests. The verify eval case moves to case-23 to avoid a numbering clash.

Generated-by: Claude Code (Claude Opus 5.5)
@github-actions github-actions Bot added family:setup setup-* skills family:tools tools/* family:docs Docs, MISSION.md, READMEs capability:platform Framework / agent substrate skills (install, verify, doctor, override, status, setup bootstrap) substrate:sandbox Tool substrate: agent isolation, egress control, settings audit substrate:framework-dev Tool substrate: build / validate / eval the framework itself labels Oct 25, 2026
@shahar1
shahar1 requested review from andreahlert and potiuk October 25, 2026 06:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

capability:platform Framework / agent substrate skills (install, verify, doctor, override, status, setup bootstrap) family:docs Docs, MISSION.md, READMEs family:setup setup-* skills family:tools tools/* substrate:framework-dev Tool substrate: build / validate / eval the framework itself substrate:sandbox Tool substrate: agent isolation, egress control, settings audit

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant