[BUG] sandbox: SOCKS5 proxy requires authentication that BSD nc cannot negotiate, breaking SSH git operations
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
When sandbox.enabled: true is set (via managed settings), Claude Code injects GIT_SSH_COMMAND into the shell to route SSH traffic through its SOCKS5 proxy:
GIT_SSH_COMMAND=ssh -o ControlMaster=no -o ControlPath=none -o ProxyCommand='nc -X 5 -x localhost:<PORT> %h %p'
The sandbox's SOCKS5 proxy requires authentication, but BSD netcat (/usr/bin/nc on macOS) only supports unauthenticated SOCKS5. No credentials are provided in the injected GIT_SSH_COMMAND. The authentication negotiation fails and every SSH git operation (git pull, git push, git fetch) fails.
What Should Happen?
SSH git operations should succeed through the sandbox proxy. Either the proxy should not require SOCKS5 authentication, or credentials should be embedded in the injected GIT_SSH_COMMAND.
Error Messages/Logs
nc: authentication method negotiation failed
Connection closed by UNKNOWN port 65535
fatal: Could not read from remote repository.
Please make sure you have the correct access rights and the repository exists.
Steps to Reproduce
- Enable sandbox via managed settings (
sandbox.enabled: true) - Open Claude Code
- Run
git pullon any repo with an SSH remote (git@github.com:org/repo.git) - Observe the error above
To confirm the injected proxy is the cause:
# Inside a Claude Code bash session:
echo $GIT_SSH_COMMAND
# Shows: ssh ... -o ProxyCommand='nc -X 5 -x localhost:PORT %h %p'
# Workaround that confirms root cause:
env -u GIT_SSH_COMMAND git pull # succeeds
Claude Model
Not applicable (sandbox/tooling bug)
Is this a regression?
Yes — previously worked, broke with a recent sandbox update
Claude Code Version
2.1.190
Platform
Anthropic API
Operating System
macOS
Terminal
iTerm2
Additional Information
/usr/bin/nc on macOS does support SOCKS5 (-X 5) but only without authentication — there are no flags for SOCKS5 credentials. The proxy is requiring an auth method nc cannot satisfy.
Workaround: export GIT_SSH_COMMAND="" before launching Claude Code bypasses the proxy injection.
11 Comments
I think this is an issue that exists in 2.1.186 and higher. I am still seeing it in 2.1.191.
The authentication token is injected into
HTTP_PROXY,ALL_PROXYbut it is not set intoGIT_SSH_COMMAND(and probably doesn't get supported as pointed out in the issue above).The root cause is in
sandbox-utils.tsgenerateProxyEnvVars(). On macOS,GIT_SSH_COMMANDis set unconditionally withnc -X 5, even whenproxyAuthTokenis set:The comment documents the known incompatibility but the code still sets the broken value. This regressed when PR #310 (merged June 15) added proxy authentication. The Linux path handles auth correctly via
socat --proxyauth; the macOS path does not.The simplest fix would be to skip
GIT_SSH_COMMANDon macOS whenproxyAuthTokenis set:Workaround: Add a SessionStart hook to override the injected variable via
CLAUDE_ENV_FILE:I tried the workaround suggested by @cblecker , but it did not work for me. ~Instead, I found I needed to unset the env var:~ Actually, this is also not a viable workaround.
Confirming the same root cause on Claude Code 2.1.241, macOS, and adding one more data point that may be relevant to the fix.
Repro was via plain
ssh, notgit— so this bug isn't limited to git-over-SSH:ssh user@host(noGIT_SSH_COMMANDinvolved at all) fails outright withOperation not permittedon the raw connect, even to a host already present insandbox.network.allowedDomains. Non-HTTP(S) traffic apparently never gets a path through the sandbox unless something explicitly routes it via the SOCKS proxy.GIT_SSH_COMMANDwrapper (ssh -o ProxyCommand='nc -X 5 -x localhost:<port> %h %p' ...) against a plainsshtarget hits the identicalThis proxy requires authentication, and this client did not offer an authentication methodfailure @cflee and @cblecker already diagnosed.$HTTPS_PROXY(http://srt.<token>:<password>@localhost:<port>), then issues theCONNECTfor the SSH target on port 22 and relays stdin/stdout. Auth succeeded (no more auth-negotiation error) and theCONNECTreply came back success — but no bytes ever arrived from the far end; the SSH client just hung toConnection timed out during banner exchange.That last point suggests @cblecker's proposed fix (skip injecting
GIT_SSH_COMMANDon macOS whenproxyAuthTokenis set) may only get git-over-SSH back to failing cleanly rather than actually working, since the SOCKS proxy's data-plane doesn't appear to forward arbitrary TCP (port 22) even once the auth handshake itself succeeds — worth checking whether the proxy's SOCKS5CONNECThandler actually opens/relays a real upstream socket for non-HTTP(S) destinations, or only accepts the handshake and drops the stream. Happy to share the throwaway repro script if useful.Workaround in use meanwhile:
dangerouslyDisableSandbox: trueper SSH call (same as the thread's conclusion) — no config-level fix found that restores sandboxed SSH.Disclaimer: this comment is all-Opus
Extracting code from Claude Code was made by Opus in auto mode. Sorry, if this breaks TOS, but it was autonomous Opus.
---
Adding a data point that contradicts the thread's current conclusion: there
is a config-level workaround that restores git-over-SSH inside the
sandbox. The difference is the protocol — HTTP CONNECT works where SOCKS5
does not.
Tested on Claude Code 2.1.252, macOS 26.6.2 (Darwin 25.6.0, arm64), with
sandbox.enabled: truein~/.claude/settings.json. Note that config alsosets
enableWeakerNetworkIsolation: true; the raw-TCP and DNS denials belowstill apply, but flagging it since it is a network-policy setting and may
affect what others reproduce.
HTTP CONNECT to the same port forwards port 22 fine
The 2026-08-23 comment reports that a hand-rolled SOCKS5 client with RFC 1929
auth completes the handshake but never relays data (
Connection timed out), and concludes the proxy's data plane may notduring banner exchange
forward arbitrary TCP.
That does not hold for the HTTP CONNECT path on the same port. Using a
ProxyCommand that issues
CONNECT <host>:22with aProxy-Authorization: Basicheader built from the credentials alreadypresent in
$HTTP_PROXY, SSH connects and authenticates end to end:(
command gitbecause the shell function shown at the end of this commentis what I now run day to day; without it, plain
gitproduces the firstresult.)
A full
git fetchover the same tunnel also succeeds. So the proxy'sCONNECThandler does open and relay a real upstream socket to port 22 —whatever fails in the SOCKS5 path is specific to SOCKS5, not to the
destination port.
I did not retest authenticated SOCKS5 myself, so I am not claiming it is
broken — only that HTTP CONNECT is a working alternative.
The macOS and Linux branches differ in both protocol and authentication
This is visible in the shipped 2.1.252 binary. Extracted with
grep -aandline-broken for readability, otherwise verbatim:
The identifiers are minified, but reading
${t}/${e}as the proxy port and${x}/${o}as the proxy credentials: Linux gets an HTTP CONNECT proxy(socat's
PROXY:address) withproxyauth, while macOS gets SOCKS5with no credentials at all. That asymmetry lines up exactly with the
observed behaviour, and it is not something the macOS branch could fix by
passing credentials to
nc— Apple'snconly has-X protoand-x addr[:port], with no proxy-authentication option.Why the two workarounds proposed earlier cannot work
export GIT_SSH_COMMAND=sshandunset GIT_SSH_COMMANDboth leave sshattempting a direct connection, and the sandbox blocks that two ways
independently:
DNS does not resolve inside the sandbox, and raw TCP to any non-localhost
address is denied at the OS level. Everything must go through the local
proxy. This also means the proposed fix of skipping the
GIT_SSH_COMMANDinjection when
proxyAuthTokenis set would only make git fail differently,as the 2026-08-23 comment anticipated.
Also worth noting: no ssh-side configuration can override it
ProxyCommandin~/.ssh/configis ignored — the injected-o ProxyCommandon the command line takes precedence. Confirmed withssh -G -o ProxyCommand='CLI-WINS-TEST', which reports the CLI value evenwhen a matching
Hostblock sets its own.git config core.sshCommandis ignored — theGIT_SSH_COMMANDenvironment variable wins.
So the override has to happen in the environment of the
gitinvocationitself.
Helper
Standard library only, no third-party dependencies; any
python3onPATHwill do. Reads the proxy address and credentials from
$HTTP_PROXYat runtime (necessary — both the port and the token change per session), and falls
back to a direct
ncwhenHTTP_PROXYis unset, so the same config is safeoutside the sandbox.
Wired up as a shell function, so it applies only inside the sandbox and only
when the broken value was actually injected:
Suggested refinement to the fix
Rather than skipping the injection when
proxyAuthTokenis set, the macOSbranch could emit a ProxyCommand that speaks HTTP CONNECT with the
credentials — i.e. give macOS the equivalent of what the Linux branch above
already does with
socat - PROXY:...,proxyauth=<user>:<pass>. Sincenccannot express proxy authentication, that needs a different tool or a small
bundled helper, but the transport itself clearly works.
Confirming this is unchanged on 2.1.259 (macOS Darwin 25.5.0, arm64,
sandbox.enabled: true,enableWeakerNetworkIsolation: true), and adding a one-line config workaround that I did not see in the thread — no helper script, no shell function, nodangerouslyDisableSandbox.The injected value and the refusal are byte-identical to what's already documented above, so just the delta:
Still the same
SSH-2.0-policy_refusalbanner reported on 2026-08-23, andHTTPS_PROXYin the same environment still carriesuser:secret@localhost:<port>— so the credential is present and simply not handed to the SSH path, exactly as diagnosed.Workaround: rewrite SSH remotes to HTTPS, in git config
The 2026-09-01 comment establishes that the proxy's HTTP CONNECT path forwards port 22 fine while SOCKS5 does not. If you only need git (not arbitrary
ssh), you don't need a CONNECT helper at all — you can avoid the SSH transport entirely and letHTTPS_PROXYdo the work, which it already does correctly:Verified in-sandbox on 2.1.259 — the SSH-form URL is rewritten and resolves:
(The identical
https://URL returns the same SHA, so this is the rewrite working, not a fallback.)Caveats, so nobody adopts it and then blames it:
gh auth setup-git. The macOSosxkeychainhelper is not usable here: the keychain is unreachable inside the sandbox (#87008). Mygit ls-remoteabove is an anonymous public read and it still printedfailed to store: 100001twice from the configuredosxkeychainhelper, so anyone relying on it for a private repo will get auth prompts rather than key auth.gh's token reads fine in-sandbox. Public reads need no credentials at all, as above.--localper repo, or a conditional include, if that matters.ssh user@hostis still broken — see the 2026-08-23 comment;insteadOfcannot help there.Not a fix, but it's a two-line change with no script to maintain, so it may be the cheapest unblock for the git-only case while the macOS
ProxyCommandbranch is sorted out.One note on the interaction with
excludedCommandsgitandsshare both in mysandbox.excludedCommands, and the injectedGIT_SSH_COMMANDreaches them anyway — an excludedgitstill runs with the full proxy environment applied. That's a separate defect (#89931, where I've posted a sandbox-off control showing the injection is sandbox-specific), but it's worth knowing here: puttinggitinexcludedCommandsis not a workaround for this issue, because the variable is inherited regardless.The
SessionStart+$CLAUDE_ENV_FILEworkaround does work, on 2.1.263. Not contradicting @jthurne's report, that was 2.1.191 back in June and plenty has moved since.Fresh session after that, sandboxed, no
dangerouslyDisableSandbox:git fetchandgit pushover SSH both work too. I pushed the commit that added this hook with the hook active, which felt appropriately circular.Which channel you use matters
This is the part I hadn't seen spelled out, and it cost me a session to work out.
I first tried the obvious thing, setting
GIT_SSH_COMMAND: "ssh"in theenvblock ofsettings.json. That does not work. Settings env is applied before the proxy vars get injected, so the injected value still wins and you get the same broken pipe, with your correct value sitting right there insettings.jsonlooking innocent.$CLAUDE_ENV_FILEis read afterward, into a cached session-environment script, and it wins. Same variable, same value, different timing. It's only populated forSessionStart,Setup,CwdChangedandFileChangedhooks, so the hook event isn't incidental.Caveat, because this won't help everyone
Setting
GIT_SSH_COMMAND=sshonly hands resolution back to your~/.ssh/config. It doesn't create a path to port 22 on its own.It worked for me because I already run my own SOCKS5 proxy (gost) on
127.0.0.1:1080with a host:port allowlist, plus aProxyCommandin ssh config that routes agent sessions through it. Localhost is exempt from the sandbox's port 22 block, so that relay works fine sandboxed. Baresshis enough to get out of the way and let it do its job.If the harness proxy is your only way out, you still need the CONNECT helper from @jmakinen81's comment. So this fixes the narrow case of "I have my own relay and the injected variable is clobbering it." Probably a small group, but the clobbering itself is squarely this bug: no user configuration at any layer can beat that variable, which is why it took three sessions to characterize what looked like a plain network failure.
One more data point on the refusal, unchanged from what's already documented: the proxy answers the handshake itself as
SSH-2.0-policy_refusaland drops the connection after KEXINIT, so it never dials the real host. Worth knowing that a denied host looks different,SOCKS error 2on :443 through the same proxy. That distinction saved me from chasingsandbox.network.allowedDomains, which has nothing to do with it.Tested on 2.1.263, macOS Darwin 25.5.0 arm64,
sandbox.enabled: true.Same on Claude Code 2.1.224 (Homebrew cask), macOS (Darwin 27.0.0, arm64),
sandbox.enabled: true. The injected value and the refusal match the report above.One data point on
excludedCommandsI have not seen in this thread. With both"git *"and"gh *"listed, onlyghleaves the sandbox: a baregh auth statusreads the login keychain. A bare git call still gets the sandbox filesystem policy, not only the proxy env from #89931:So on macOS
excludedCommandsis no workaround for git.Confirming authenticated SOCKS5 works end to end here, answering the question @folotp raised on 2026-08-23 and @jmakinen81 left untested on 2026-09-01 ("I did not retest authenticated SOCKS5 myself").
Tested against the sandbox's own local proxy (the same one
nctargets, from a live$HTTP_PROXY), withsocat'sSOCKS5-CONNECTaddress and separately withncat's--proxy-type socks5, each doing a fullgit push:The commands above use
127.0.0.1.ncatfailed here againstlocalhost, resolving to::1first with nothing listening there;socathad no such issue with the same string.Tested on Claude Code 2.1.231, macOS 26.6.2 (Darwin 25.6.0, arm64),
sandbox.enabled: true.Still reproduces on Claude Code 2.1.284, macOS, in headless
claude -pturns whose sandbox is set through--settings(sandbox.enabled: true,allowUnsandboxedCommands: false,network.allowedDomains: ["*"]).GIT_SSH_COMMANDis unchanged:ssh -o ControlMaster=no -o ControlPath=none -o ProxyCommand='nc -X 5 -x localhost:<port> %h %p'. The auth token is present inHTTPS_PROXY/ALL_PROXYbut not in the SSH path.git ls-remote git@github.com:<owner>/<private-repo>.git HEADfails withReceived disconnect from UNKNOWN port 65535:1: This proxy requires authentication, and this client did not offer an authentication method, so the connection was refused.(exit 128).GIT_SSH_COMMANDwith plainssh -o BatchMode=yesfails too (Could not resolve hostname github.com), since DNS only works through the proxy.git ls-remote https://github.com/<owner>/<private-repo>.git HEADwithgh auth git-credentialas the credential helper returns the ref.Workaround in use until this is fixed: per-process git config (
GIT_CONFIG_COUNT/GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n) that mapsgit@github.com:andssh://git@github.com/tohttps://github.com/, pluscredential.https://github.com.helper=!gh auth git-credential. When the global git config rewriteshttps://github.com/to SSH viaurl.<ssh>.insteadOf, it also needsGIT_CONFIG_GLOBAL=/dev/nullwith the rest of the global config re-added at command scope, because git applies only oneinsteadOfrewrite.Still present on macOS 27.0.1 (arm64) with Claude Code 2.1.289 (desktop app, Code tab) and the 2.1.288 CLI. I also checked the 2.1.292 darwin-arm64 build, the latest on npm as of 2026-10-07: the macOS branch still injects
ProxyCommand='nc -X 5 -x localhost:<port> %h %p'without credentials, so a fix has not shipped yet.Three fixes are open in sandbox-runtime and still awaiting review: anthropics/sandbox-runtime#452, anthropics/sandbox-runtime#482, anthropics/sandbox-runtime#516.