|
| 1 | +--- |
| 2 | +description: Verify Claude's mount-namespace sandbox is intact — env canaries, masked credentials, gitconfig bind, and the four VS Code IPC sockets from the Demmel writeup. |
| 3 | +--- |
| 4 | + |
| 5 | +# Verify sandbox |
| 6 | + |
| 7 | +Run the full sandbox verification described in `README-CLAUDE.md` and |
| 8 | +report a PASS/FAIL table. The threat model these checks defend against |
| 9 | +is documented in: |
| 10 | + |
| 11 | +- `README-CLAUDE.md` (this repo) — sections **What's locked down** and |
| 12 | + **Verifying the sandbox**. |
| 13 | +- Daniel Demmel, *Coding agents in secured VS Code dev containers* — |
| 14 | + <https://www.danieldemmel.me/blog/coding-agents-in-secured-vscode-dev-containers> |
| 15 | + — describes the `vscode-ipc-*.sock`, `vscode-git-*.sock`, |
| 16 | + `vscode-ssh-auth-*.sock`, and `vscode-remote-containers-ipc-*.sock` |
| 17 | + bridges in `/tmp` that re-appear up to ~60s after window attach. Our |
| 18 | + defence is the private mount namespace set up by `just claude`, not a |
| 19 | + one-shot sweep. |
| 20 | + |
| 21 | +## How to run |
| 22 | + |
| 23 | +Execute every check below in a single Bash invocation where practical |
| 24 | +(parallel them when independent). For each item, report PASS or FAIL |
| 25 | +with a one-line reason. Do not skip a check because an earlier one |
| 26 | +failed — collect everything, then summarise. |
| 27 | + |
| 28 | +If any check FAILs, end the report with: "Sandbox is leaking — do not |
| 29 | +trust `--dangerously-skip-permissions` until fixed. Open an issue |
| 30 | +against `gilesknap/python-copier-template`." |
| 31 | + |
| 32 | +## Checks |
| 33 | + |
| 34 | +### 1. Namespace markers |
| 35 | + |
| 36 | +- `IS_SANDBOX` env var must be `1` (set by `claude-sandbox.sh` after |
| 37 | + `unshare -m`). If unset, Claude was not launched via `just claude`. |
| 38 | +- `IN_DEVCONTAINER` env var must be set. |
| 39 | + |
| 40 | +### 2. Host bridge env vars (must all be unset) |
| 41 | + |
| 42 | +`SSH_AUTH_SOCK`, `GIT_ASKPASS`, `VSCODE_GIT_IPC_HANDLE`, |
| 43 | +`VSCODE_GIT_ASKPASS_NODE`, `VSCODE_GIT_ASKPASS_MAIN`, |
| 44 | +`VSCODE_IPC_HOOK_CLI`, `BROWSER`. |
| 45 | + |
| 46 | +### 3. SSH agent unreachable |
| 47 | + |
| 48 | +`ssh-add -l` must fail with "Could not open a connection to your |
| 49 | +authentication agent." Anything that lists keys is a FAIL. |
| 50 | + |
| 51 | +### 4. `/tmp` and `/run/user` are private tmpfs |
| 52 | + |
| 53 | +- `mount | grep ' on /tmp '` must show a `tmpfs` entry (this confirms |
| 54 | + the mount namespace is active for `/tmp`). |
| 55 | +- `ls /tmp` must NOT contain any of the four Demmel sockets: |
| 56 | + `vscode-ipc-*.sock`, `vscode-git-*.sock`, `vscode-ssh-auth-*.sock`, |
| 57 | + `vscode-remote-containers-ipc-*.sock`. Glob each one explicitly. |
| 58 | +- `ls /run/user/*/` must NOT contain `vscode-*` entries. |
| 59 | + |
| 60 | +### 5. Host credential dirs masked |
| 61 | + |
| 62 | +Each of these must be empty or absent: |
| 63 | +`/root/.ssh`, `/root/.gnupg`, `/root/.aws`, `/root/.azure`, |
| 64 | +`/root/.gcloud`, `/root/.docker`, `/root/.netrc`. |
| 65 | + |
| 66 | +A non-empty `/root/.ssh` (containing `id_*` or `authorized_keys`) is a |
| 67 | +critical FAIL — the host SSH keys are reachable. |
| 68 | + |
| 69 | +### 6. Gitconfig bind-mount |
| 70 | + |
| 71 | +- `mount | grep '/root/.gitconfig'` must show a bind mount (typically |
| 72 | + `fuse-overlayfs` or `bind` from `/etc/claude-gitconfig`). |
| 73 | +- `git config --global --list` must contain ONLY: |
| 74 | + - `user.name` / `user.email` (host identity, copied through), |
| 75 | + - `safe.directory=*`, |
| 76 | + - `url.https://github.com/.insteadof=git@github.com:`, |
| 77 | + - `url.https://gitlab.diamond.ac.uk/.insteadof=git@gitlab.diamond.ac.uk:`, |
| 78 | + - `credential.https://github.com.helper=` then `!/usr/bin/gh auth git-credential`, |
| 79 | + - `credential.https://gitlab.diamond.ac.uk.helper=` then `!/usr/local/bin/glab auth git-credential`. |
| 80 | +- Any other `credential.*.helper` (especially one pointing at |
| 81 | + `/tmp/vscode-remote-containers-*.js` or `/.vscode-server/...`) is a |
| 82 | + FAIL. |
| 83 | +- No system-scope helper: `git config --system --get credential.helper` |
| 84 | + must exit non-zero. |
| 85 | + |
| 86 | +### 7. Credential source is gh, not a host bridge |
| 87 | + |
| 88 | +`printf 'protocol=https\nhost=github.com\n\n' | git credential fill` |
| 89 | +must return a `password=` line. The token prefix tells you the source: |
| 90 | + |
| 91 | +- `gho_…` or `github_pat_…` from `gh auth git-credential` → PASS. |
| 92 | +- Anything else (e.g. a token from a `vscode-git-*.sock` bridge) → FAIL. |
| 93 | + |
| 94 | +Do NOT print the token. Redact with `sed 's/password=.*/password=<REDACTED>/'`. |
| 95 | +Skip this check (mark N/A, not FAIL) if `just gh-auth` has not been run |
| 96 | +for this repo — the README explicitly carves that out. |
| 97 | + |
| 98 | +## Output format |
| 99 | + |
| 100 | +Print a single table: |
| 101 | + |
| 102 | +``` |
| 103 | +CHECK STATUS DETAIL |
| 104 | +1. IS_SANDBOX=1 PASS/FAIL ... |
| 105 | +2. Host bridge env vars unset PASS/FAIL ... |
| 106 | +3. ssh-add -l fails PASS/FAIL ... |
| 107 | +4a. /tmp is tmpfs PASS/FAIL ... |
| 108 | +4b. No vscode-*.sock in /tmp PASS/FAIL ... |
| 109 | +4c. No vscode-* in /run/user PASS/FAIL ... |
| 110 | +5. Host credential dirs masked PASS/FAIL ... |
| 111 | +6a. /root/.gitconfig bind-mounted PASS/FAIL ... |
| 112 | +6b. Gitconfig contents are sandbox-only PASS/FAIL ... |
| 113 | +6c. No system-scope credential.helper PASS/FAIL ... |
| 114 | +7. git credential fill source is gh PASS/FAIL/N/A ... |
| 115 | +``` |
| 116 | + |
| 117 | +End with one line: `RESULT: SANDBOX OK` if every check is PASS or N/A, |
| 118 | +otherwise `RESULT: SANDBOX LEAKING — see failures above` and the issue |
| 119 | +pointer. |
0 commit comments