Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions sandbox-ssh-fix/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "sandbox-ssh-fix",
"version": "1.0.0",
"description": "Fixes git-over-SSH in the Claude Code sandbox on macOS by replacing the broken BSD nc SOCKS5 proxy with ncat",
"version": "1.0.1",
"description": "Fixes git-over-SSH in the Claude Code sandbox on macOS by wrapping BSD nc with ncat for SOCKS5 proxy auth",
"author": {
"name": "cblecker",
"email": "admin@toph.ca"
Expand Down
25 changes: 0 additions & 25 deletions sandbox-ssh-fix/CLAUDE.md

This file was deleted.

25 changes: 13 additions & 12 deletions sandbox-ssh-fix/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,20 @@ Tracked upstream: <https://github.com/anthropics/claude-code/issues/70684>

## How it works

A SessionStart hook detects the broken pattern and overrides `GIT_SSH_COMMAND`
via `$CLAUDE_ENV_FILE`:
This plugin ships an `nc` wrapper (`bin/nc`) that intercepts SOCKS5 proxy calls
and delegates to `ncat` with the auth credentials parsed from `ALL_PROXY`.

- If `ncat` is available: uses `ncat --proxy-type socks5 --proxy-auth` with
credentials parsed from `ALL_PROXY`
- If `ncat` is not available: falls back to plain `ssh` (bypasses the proxy,
relies on the sandbox network allowlist)
### Flow

The fix only activates when all conditions are met:
1. SessionStart hook prepends `plugin/bin/` to `PATH` via `CLAUDE_ENV_FILE`
2. Sandbox injects `GIT_SSH_COMMAND` containing `nc -X 5 -x localhost:PORT %h %p`
3. SSH runs ProxyCommand, shell finds `bin/nc` wrapper first via PATH
4. Wrapper checks for the SOCKS5 pattern (`-X 5 -x`), reads `ALL_PROXY`
(available at runtime in sandbox), and delegates to `ncat` with `--proxy-auth`
5. Non-SOCKS5 calls or missing `ncat` fall through to `/usr/bin/nc`

1. Running inside the sandbox (`SANDBOX_RUNTIME=1`)
2. `GIT_SSH_COMMAND` contains the broken `nc -X 5` pattern
3. `ALL_PROXY` contains credentials (has `@`)
The wrapper uses `127.0.0.1` instead of `localhost` because `ncat` resolves
`localhost` differently and the connection is refused.

## Prerequisites

Expand All @@ -35,8 +36,8 @@ Install `ncat` (from nmap) for full SOCKS5 proxy support:
brew install nmap
```

Without `ncat`, the plugin falls back to direct SSH which still works but
bypasses the sandbox's SOCKS5 proxy.
Without `ncat`, the plugin falls back to the system `nc` which will fail for
authenticated SOCKS5 proxies.

## License

Expand Down
11 changes: 11 additions & 0 deletions sandbox-ssh-fix/bin/nc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
if [[ "$#" -ge 6 ]] \
&& [[ "$1" == "-X" && "$2" == "5" && "$3" == "-x" ]] \
&& [[ "${ALL_PROXY:-}" == *"@"* ]] \
&& command -v ncat >/dev/null 2>&1; then
_auth="${ALL_PROXY#*://}"
_auth="${_auth%@*}"
_proxy="${4/localhost/127.0.0.1}"
exec ncat --proxy-type socks5 --proxy-auth "$_auth" --proxy "$_proxy" "${@: -2}"
fi
exec /usr/bin/nc "$@"
26 changes: 2 additions & 24 deletions sandbox-ssh-fix/scripts/fix-git-ssh.sh
Original file line number Diff line number Diff line change
@@ -1,29 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail

# Only run inside the Claude Code sandbox
[[ "${SANDBOX_RUNTIME:-}" == "1" ]] || exit 0

# Only fix the broken BSD nc SOCKS5 proxy pattern
[[ "${GIT_SSH_COMMAND:-}" == *"nc -X 5"* ]] || exit 0

# Only needed when ALL_PROXY has credentials (contains @)
[[ "${ALL_PROXY:-}" == *"@"* ]] || exit 0

# CLAUDE_ENV_FILE is required to export the fix
[[ -n "${CLAUDE_ENV_FILE:-}" ]] || exit 0

# Parse ALL_PROXY: socks5h://user:pass@host:port
proxy_auth="${ALL_PROXY#*://}" # user:pass@host:port
proxy_auth="${proxy_auth%@*}" # user:pass
proxy_port="${ALL_PROXY##*:}" # port

if command -v ncat >/dev/null 2>&1; then
cat >> "${CLAUDE_ENV_FILE}" <<ENVEOF
export GIT_SSH_COMMAND='ssh -o ControlMaster=no -o ControlPath=none -o '\''ProxyCommand=ncat --proxy-type socks5 --proxy-auth ${proxy_auth} --proxy 127.0.0.1:${proxy_port} %h %p'\'''
ENVEOF
echo "SessionStart:startup hook success: sandbox-ssh-fix: replaced broken BSD nc with ncat for SOCKS5 proxy auth"
else
echo "export GIT_SSH_COMMAND='ssh -o ControlMaster=no -o ControlPath=none'" >> "${CLAUDE_ENV_FILE}"
echo "SessionStart:startup hook success: sandbox-ssh-fix: ncat not found, bypassing SOCKS5 proxy (using direct SSH)"
fi
echo "export PATH=\"${CLAUDE_PLUGIN_ROOT}/bin:\${PATH}\"" >> "${CLAUDE_ENV_FILE}"
echo "SessionStart:startup hook success: sandbox-ssh-fix: added nc wrapper to PATH"
Loading