claude-sandbox: document SANDBOX_SSH_KEY in Security notes; warn on default-looking personal keys
Second-round review follow-up. Security notes was written as the canonical list of residual risks but never mentioned the new SANDBOX_SSH_KEY mechanism, which has the same "signs for any repo it can reach" shape as the forwarded agent (already documented) - add a bullet covering it and the /root-only mount. Also nothing enforced that SANDBOX_SSH_KEY actually be a dedicated low-privilege key rather than a real personal identity, which is the whole basis for accepting it as passphrase-less and left loaded for an entire unattended session. bin/claude-sandbox now warns (non-fatal) if it looks like a default personal keyfile (id_rsa, id_ed25519, etc).
This commit is contained in:
@@ -37,6 +37,19 @@ if [ -n "$SANDBOX_SSH_KEY" ]; then
|
||||
echo "Warning: $SANDBOX_SSH_KEY is readable by group/other (mode $key_perms)." >&2
|
||||
echo "Recommended: chmod 600 $SANDBOX_SSH_KEY" >&2
|
||||
fi
|
||||
# SANDBOX_SSH_KEY is meant to be a dedicated, low-privilege deploy key
|
||||
# (see README "Headless git push"), not your real identity - it stays
|
||||
# loaded in an in-container agent for the whole session, unattended.
|
||||
# Nothing enforces that convention, so at least catch the common case
|
||||
# of pointing it at a default personal keyfile by mistake.
|
||||
case "$(basename -- "$SANDBOX_SSH_KEY")" in
|
||||
id_rsa|id_ecdsa|id_ed25519|id_dsa|id_ecdsa_sk|id_ed25519_sk)
|
||||
echo "Warning: $SANDBOX_SSH_KEY looks like a default personal SSH key," \
|
||||
"not a dedicated one." >&2
|
||||
echo "It will be loaded into the sandbox for the whole unattended session -" \
|
||||
"see README 'Headless git push'." >&2
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
have_forwarded_agent=1
|
||||
|
||||
@@ -440,6 +440,20 @@ one can't happen.
|
||||
push to) other repos on that host you have access to. This doesn't extend
|
||||
to GitHub: port 22 there is blocked outright, so the forwarded agent has
|
||||
no transport to use for it at all (see "GitHub is read-only" above).
|
||||
- `SANDBOX_SSH_KEY` (see "Headless git push without agent forwarding") is a
|
||||
second, independent credential-loading path with the same "signs for any
|
||||
Gitea repo it has access to" shape as the forwarded agent above - the
|
||||
mitigation is that it's meant to be a dedicated deploy key scoped to only
|
||||
the repos that actually need push access, not your personal identity.
|
||||
That scoping is a convention, not something enforced here: nothing checks
|
||||
what `SANDBOX_SSH_KEY` actually points at, so pointing it at your real
|
||||
`~/.ssh/id_ed25519`/`id_rsa` instead of a dedicated key would load your
|
||||
full personal identity into the container for the whole session (`bin/
|
||||
claude-sandbox` warns if this looks like it might be happening - see
|
||||
below - but doesn't block it). The key source itself is mounted read-only
|
||||
at `/root/...`, not under the sandbox user's own home, specifically so
|
||||
that user (which the session actually runs as) has no path to read the
|
||||
raw key material - only the agent socket it's loaded into.
|
||||
- The sandbox's own user (matching your host user — see "Config parity"
|
||||
above) has no `sudo`/setuid path back to root once the container is up:
|
||||
`entrypoint.sh` runs as root, sets up the firewall directly, then drops to
|
||||
|
||||
@@ -216,3 +216,9 @@ setup() {
|
||||
grep -q 'SANDBOX_SSH_KEY' "$REPO_ROOT/bin/claude-sandbox"
|
||||
grep -q 'export SANDBOX_SSH_KEY_FILE' "$REPO_ROOT/bin/claude-sandbox"
|
||||
}
|
||||
|
||||
@test "bin/claude-sandbox warns when SANDBOX_SSH_KEY looks like a default personal key" {
|
||||
grep -q 'looks like a default personal SSH key' "$REPO_ROOT/bin/claude-sandbox"
|
||||
run grep -E '^\s*id_rsa\|id_ecdsa\|id_ed25519\|id_dsa\|id_ecdsa_sk\|id_ed25519_sk\)\s*$' "$REPO_ROOT/bin/claude-sandbox"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user