macOS sandbox: injected GIT_SSH_COMMAND omits the proxy credentials (the Linux branch passes them), so git-over-SSH always fails
Sandbox: injected GIT_SSH_COMMAND uses SOCKS5 without the proxy credentials, so git-over-SSH always fails
Version: Claude Code 2.1.217
Platform: macOS (Darwin 25.3.0), OpenSSH_10.2p1
Sandbox: enabled (sandbox.enabled: true, allowUnsandboxedCommands: false)
Summary
With the sandbox enabled, Claude Code injects
GIT_SSH_COMMAND=ssh -o ControlMaster=no -o ControlPath=none \
-o ProxyCommand='nc -X 5 -x localhost:<port> %h %p'
-X 5 selects SOCKS5, but no credentials are passed. The sandbox proxy requires
authentication — every other proxy variable in the same environment carries auser:password pair:
HTTPS_PROXY=http://srt:<hex>@localhost:<port>
ALL_PROXY=http://srt:<hex>@localhost:<port>
FTP_PROXY=socks5h://srt:<hex>@localhost:<port>
macOS nc cannot supply SOCKS5 credentials at all: its -P user flag applies
only to HTTP CONNECT proxies, and it prompts for the password interactively,
so it is unusable from a ProxyCommand even for HTTP. The result is that
every git-over-SSH operation fails inside the sandbox.
Reproduction
$ ssh -o ProxyCommand='nc -X 5 -x localhost:64193 %h %p' -T git@github.com
nc: authentication method negotiation failed
Connection closed by UNKNOWN port 65535
Evidence that the transport itself is fine
An HTTP CONNECT through $HTTPS_PROXY with Basic auth reaches GitHub's SSH
endpoints and returns a genuine banner:
('github.com', 22) -> HTTP/1.1 200 Connection Established banner: SSH-2.0-5645aa6
('ssh.github.com', 443) -> HTTP/1.1 200 Connection Established banner: SSH-2.0-5645aa6
Substituting a CONNECT-speaking ProxyCommand takes ssh -T git@github.com
all the way to Permission denied (publickey) — i.e. the connection succeeds
and only key material is missing. So this is purely a bug in how theProxyCommand is constructed, not a network-policy decision.
The Linux branch gets this right — macOS is the outlier
The same function builds the variable for both platforms, and only the macOS
arm omits the credentials:
if (u === "macos")
s.push(`GIT_SSH_COMMAND=ssh … -o ProxyCommand='nc -X 5 -x localhost:${t} %h %p'`);
else if (u === "linux" && e) {
let d = n ? `,proxyauth=srt:${n}` : "";
s.push(`GIT_SSH_COMMAND=ssh … -o ProxyCommand='socat - PROXY:localhost:%h:%p,proxyport=${e}${d}'`);
}
Linux uses socat with an HTTP CONNECT proxy and passes proxyauth=srt:<token>.
macOS uses nc with SOCKS5 and passes nothing. The proxy token (n) is plainly
in scope in both arms — it is simply not used on macOS. This looks like a
straightforward omission rather than a platform limitation.
Two further sandbox interactions worth noting
Even with a working ProxyCommand, two more failures follow from~/.ssh being in sandbox.filesystem.denyRead:
- No identity is loadable — every candidate key logs
identity file … type -1
— and SSH_AUTH_SOCK is unreachable too
(ssh_get_authentication_socket: Operation not permitted), so the agent
fallback fails as well.
~/.ssh/known_hostsis unreadable, so host verification hard-fails with
hostkeys_foreach failed … Operation not permitted /
Host key verification failed — even when the connection itself succeeds.
Neither is fatal to a determined user (a forwarded agent socket viaallowUnixSockets, plus UserKnownHostsFile pointed outside ~/.ssh), but
both mean git-over-SSH cannot work out of the box on macOS regardless of theProxyCommand fix.
Suggested fixes (any one)
- Emit the SOCKS5 credentials the proxy expects, and use a client that can
actually send them — nc cannot.
- Emit an HTTP CONNECT
ProxyCommandcarryingProxy-Authorization: Basic,
consistent with HTTPS_PROXY.
- Ship a small first-party CONNECT helper and point
ProxyCommandat it,
rather than depending on the host's nc, whose proxy support is
platform-dependent (socat, corkscrew, ncat, proxytunnel were all
absent on this machine).
Secondary observation
~/.ssh being in sandbox.filesystem.denyRead is a sensible default, but note
it is undermined in the default configuration: gh auth token succeeds inside
the sandbox and returns a live token whose scopes include repo andadmin:public_key — the latter allows adding SSH keys to the account. A
sandboxed session therefore already holds authority comparable to what the~/.ssh denial is protecting. Worth documenting, at least.
Workaround
sandbox.network.allowUnixSockets (undocumented as far as I can tell) allows a
path-scoped ssh-agent socket to be forwarded in, which solves key access
without exposing ~/.ssh. Combined with a custom CONNECT ProxyCommand incore.sshCommand, git-over-SSH works. Both halves are user-supplied
workarounds for what looks like a shipping defect.