Skip to content

Commit 2ad3a5b

Browse files
gilesknapclaude
andcommitted
sandbox: enforce VS Code git credential lockdown via devcontainer settings
Pin three Dev Containers settings in devcontainer.json so VS Code never forwards host git credentials into the container in the first place, rather than relying on env-var clearing in `just claude`: git.terminalAuthentication: false -> stops VS Code's Git extension from setting GIT_ASKPASS / VSCODE_GIT_IPC_HANDLE in integrated terminals (source: vscode/extensions/git/src/askpass.ts) dev.containers.gitCredentialHelperConfigLocation: "none" -> stops Dev Containers from writing a credential.helper line into /etc/gitconfig dev.containers.copyGitConfig: false -> stops the host ~/.gitconfig (with its url.insteadOf rewrites and per-host helpers) from being copied into the container A compromised model can no longer reach a leaked VS Code IPC socket by editing the hook or recipe, because the socket is never wired up. Knock-on cleanup: - devcontainer.json: drop the now-redundant remoteEnv blanks for GIT_ASKPASS / VSCODE_GIT_IPC_HANDLE / VSCODE_GIT_ASKPASS_{MAIN,NODE, EXTRA_ARGS}. The new settings prevent VS Code from setting them at all; sandbox-check.sh catches any regression. SSH_AUTH_SOCK= stays in remoteEnv — there is no VS Code setting alternative for it. - justfile: drop VSCODE_GIT_IPC_HANDLE= / GIT_ASKPASS= from `just claude` for the same reason; SSH_AUTH_SOCK= stays. - sandbox-check.sh: env-var failures for the credential bridge now point at "rebuild the devcontainer" since `just claude` no longer fixes them. - README-CLAUDE.md: rewrite the credential-injection section to lead with the settings as the primary defence; postStart.sh and the sandbox-check.sh hook are belt-and-braces verifications, not the only enforcement. Requires devcontainer rebuild for the settings to take effect. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5be632f commit 2ad3a5b

4 files changed

Lines changed: 50 additions & 36 deletions

File tree

template/.devcontainer/devcontainer.json.jinja

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,11 @@
1212
"remoteEnv": {
1313
// Allow X11 apps to run inside the container
1414
"DISPLAY": "${localEnv:DISPLAY}",{% if add_claude %}
15-
// Disable SSH agent forwarding — prevents Claude from using host SSH keys
16-
"SSH_AUTH_SOCK": "",
17-
// Disable VS Code git credential injection — prevents askpass from
18-
// relaying host GitHub credentials into the container over the IPC socket
19-
"GIT_ASKPASS": "",
20-
"VSCODE_GIT_IPC_HANDLE": "",
21-
"VSCODE_GIT_ASKPASS_MAIN": "",
22-
"VSCODE_GIT_ASKPASS_NODE": "",
23-
"VSCODE_GIT_ASKPASS_EXTRA_ARGS": "",{% endif %}
15+
// Disable SSH agent forwarding — prevents Claude from using host SSH keys.
16+
// No VS Code setting disables this, so the remoteEnv blank is the actual
17+
// defence (the GIT_ASKPASS / VSCODE_GIT_IPC_HANDLE bridge is closed at
18+
// the customizations.vscode.settings level instead — see below).
19+
"SSH_AUTH_SOCK": "",{% endif %}
2420
// Mark this shell as running inside the devcontainer
2521
"IN_DEVCONTAINER": "1",
2622
// Put things that allow it in the persistent cache
@@ -44,7 +40,18 @@
4440
"python.terminal.activateEnvironment": false,
4541
// Workaround to prevent garbled python REPL in the terminal
4642
// https://github.com/microsoft/vscode-python/issues/25505
47-
"python.terminal.shellIntegration.enabled": false
43+
"python.terminal.shellIntegration.enabled": false{% if add_claude %},
44+
// Close every VS Code channel that would otherwise forward host git
45+
// credentials into the container. Together these stop the integrated
46+
// terminal from inheriting GIT_ASKPASS / VSCODE_GIT_IPC_HANDLE, stop
47+
// the Dev Containers extension from writing a credential.helper line
48+
// into /etc/gitconfig, and stop the host ~/.gitconfig (with its SSH
49+
// url.insteadOf rewrites and per-host helpers) being copied in.
50+
// postStart.sh and the sandbox-check.sh hook are belt-and-braces
51+
// verifications that these settings actually took effect.
52+
"git.terminalAuthentication": false,
53+
"dev.containers.gitCredentialHelperConfigLocation": "none",
54+
"dev.containers.copyGitConfig": false{% endif %}
4855
},
4956
// Add the IDs of extensions you want installed when the container is created.
5057
"extensions": [

template/{% if add_claude %}.claude{% endif %}/hooks/sandbox-check.sh

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@ fail() { echo "BLOCKED: $1" >&2; exit 2; }
99
[ -n "${IN_DEVCONTAINER:-}" ] || \
1010
fail "not in the devcontainer (IN_DEVCONTAINER unset). Reopen the project in the devcontainer."
1111

12-
# Host SSH agent must not be reachable.
12+
# Host SSH agent must not be reachable. remoteEnv blanks SSH_AUTH_SOCK and
13+
# `just claude` re-blanks it; if it is set, neither layer applied.
1314
[ -z "${SSH_AUTH_SOCK:-}" ] || \
14-
fail "SSH_AUTH_SOCK is set ($SSH_AUTH_SOCK) — host SSH agent is reachable. run \"just claude\""
15+
fail "SSH_AUTH_SOCK is set ($SSH_AUTH_SOCK) — host SSH agent is reachable. run \"just claude\" or rebuild the devcontainer."
1516

16-
# VS Code git credential bridge must be silenced.
17+
# VS Code git credential bridge must be silenced. With
18+
# git.terminalAuthentication=false in devcontainer.json these env vars
19+
# should never be set — if they are, the setting was not applied.
1720
[ -z "${VSCODE_GIT_IPC_HANDLE:-}" ] || \
18-
fail "VSCODE_GIT_IPC_HANDLE is set — VS Code credential bridge is reachable. run \"just claude\""
21+
fail "VSCODE_GIT_IPC_HANDLE is set — VS Code credential bridge is reachable. Rebuild the devcontainer (git.terminalAuthentication should be false)."
1922
[ -z "${GIT_ASKPASS:-}" ] || \
20-
fail "GIT_ASKPASS is set — VS Code askpass is injected. run \"just claude\""
23+
fail "GIT_ASKPASS is set — VS Code askpass is injected. Rebuild the devcontainer (git.terminalAuthentication should be false)."
2124

2225
# The /tmp credential helper script VS Code drops in must have been removed.
2326
if compgen -G '/tmp/vscode-remote-containers-*.js' >/dev/null; then

template/{% if add_claude %}README-CLAUDE.md{% endif %}.jinja

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,25 @@ and how to verify the sandbox is intact.
1414
- **No host SSH keys.** `SSH_AUTH_SOCK` is unset in `remoteEnv`, so any
1515
SSH-agent forwarded by the host is invisible inside the container. No
1616
private keys are mounted into `/root/.ssh` either — only `known_hosts`.
17-
- **No VS Code git credential injection.** `GIT_ASKPASS`,
18-
`VSCODE_GIT_IPC_HANDLE`, `VSCODE_GIT_ASKPASS_*` are blanked in
19-
`devcontainer.json`'s `remoteEnv`, but that only sets the *initial*
20-
environment of the remote server — VS Code's built-in Git extension
21-
re-injects `GIT_ASKPASS` and `VSCODE_GIT_IPC_HANDLE` per child-process
22-
spawn so any extension can prompt for credentials through the UI. The
23-
`just claude` recipe therefore re-blanks both vars on the `claude`
24-
command line itself, which is the only point at which they can be
25-
reliably stripped before `claude` inherits them.
26-
`VSCODE_GIT_ASKPASS_NODE`/`_MAIN` may remain populated; they are just
27-
paths to the askpass script and are inert without `IPC_HANDLE` (the
28-
socket the script phones home through), so they don't need blanking.
29-
Separately, `postStart.sh` aggressively unsets `credential.helper` and
30-
per-host helpers in BOTH `--system` (`/etc/gitconfig`) and `--global`
31-
scopes — VS Code writes the helper into the *system* gitconfig, so a
32-
global-only cleanup leaves the leak open. The script also removes the
33-
`/tmp/vscode-remote-containers-*.js` bridge that VS Code drops in.
34-
The cleanup re-runs on `postAttachCommand` because VS Code re-injects
35-
the helper after `postStartCommand`.
17+
- **No VS Code git credential injection.** Three Dev Containers settings
18+
pinned in `devcontainer.json` close every channel at the boundary:
19+
- `git.terminalAuthentication: false` — VS Code's Git extension never
20+
sets `GIT_ASKPASS` / `VSCODE_GIT_IPC_HANDLE` in the integrated
21+
terminal, so there is no IPC socket path for a child process to
22+
find. Source-confirmed in `vscode/extensions/git/src/askpass.ts`.
23+
- `dev.containers.gitCredentialHelperConfigLocation: "none"` — the
24+
Dev Containers extension does not write a `credential.helper` line
25+
into `/etc/gitconfig`, so nothing in-container references the
26+
`/tmp/vscode-remote-containers-*.js` bridge.
27+
- `dev.containers.copyGitConfig: false` — the host's `~/.gitconfig`
28+
is not copied into the container, so any `url.ssh://...insteadOf`
29+
rewrites or per-host helpers stay on the host.
30+
31+
These settings are the primary defence. Two layers of belt-and-braces
32+
sit on top: `postStart.sh` re-asserts `credential.helper` cleanup at
33+
attach (and removes the `/tmp/vscode-remote-containers-*.js` shim if
34+
VS Code still drops it), and `.claude/hooks/sandbox-check.sh` verifies
35+
the state on every prompt submit.
3636
- **Per-host helpers point at the in-container CLI.** The host gitconfig
3737
often references `/usr/local/bin/gh`; here `gh` is at `/usr/bin/gh`. We
3838
rewrite the helper to `command -v gh` / `command -v glab` so it doesn't

template/{% if add_claude %}justfile{% endif %}.jinja

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
# Start Claude Code in sandbox mode (no SSH agent, skip permission prompts)
1+
# Start Claude Code in sandbox mode (no SSH agent, skip permission prompts).
2+
# VSCODE_GIT_IPC_HANDLE / GIT_ASKPASS are no longer cleared here — the
3+
# devcontainer pins git.terminalAuthentication=false so VS Code never sets
4+
# them. SSH_AUTH_SOCK still gets blanked because there is no VS Code setting
5+
# to disable host SSH agent forwarding.
26
claude:
3-
SSH_AUTH_SOCK= IS_SANDBOX=1 VSCODE_GIT_IPC_HANDLE= GIT_ASKPASS= claude --dangerously-skip-permissions
7+
SSH_AUTH_SOCK= IS_SANDBOX=1 claude --dangerously-skip-permissions
48

59

610
# Authenticate gh CLI with a GitHub PAT (token not stored in shell history)

0 commit comments

Comments
 (0)