From acb1118176e9ed05b2236729ab3451671a9fc5ff Mon Sep 17 00:00:00 2001 From: Christoph Blecker Date: Thu, 25 Jun 2026 14:57:13 -0700 Subject: [PATCH] feat(sandbox-ssh-fix): add plugin to fix git-over-SSH in macOS sandbox The Claude Code sandbox injects GIT_SSH_COMMAND with BSD nc for SOCKS5 proxying, but nc on macOS doesn't support SOCKS5 auth. This plugin detects the broken pattern and replaces it with ncat or falls back to plain ssh. Workaround for https://github.com/anthropics/claude-code/issues/70684 Assisted-by: Claude:claude-opus-4-6 --- .claude-plugin/marketplace.json | 5 +++ README.md | 1 + sandbox-ssh-fix/.claude-plugin/plugin.json | 11 ++++++ sandbox-ssh-fix/CLAUDE.md | 25 +++++++++++++ sandbox-ssh-fix/README.md | 43 ++++++++++++++++++++++ sandbox-ssh-fix/hooks/hooks.json | 14 +++++++ sandbox-ssh-fix/scripts/fix-git-ssh.sh | 29 +++++++++++++++ 7 files changed, 128 insertions(+) create mode 100644 sandbox-ssh-fix/.claude-plugin/plugin.json create mode 100644 sandbox-ssh-fix/CLAUDE.md create mode 100644 sandbox-ssh-fix/README.md create mode 100644 sandbox-ssh-fix/hooks/hooks.json create mode 100755 sandbox-ssh-fix/scripts/fix-git-ssh.sh diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 1e2fd8d..2cfb93c 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -32,6 +32,11 @@ "name": "rh-dataverse", "source": "./rh-dataverse", "strict": true + }, + { + "name": "sandbox-ssh-fix", + "source": "./sandbox-ssh-fix", + "strict": true } ] } diff --git a/README.md b/README.md index 87a683d..9a70e0c 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ claude plugin marketplace add cblecker/claude-plugins | [pr-review-toolkit](./pr-review-toolkit) | Comprehensive PR review board using shared workflow context | | [gws](./gws) | Google Workspace CLI skills for Gmail, Calendar, Drive, Docs, Sheets, Slides, and Meet | | [rh-dataverse](./rh-dataverse) | Red Hat Dataverse MCP server | +| [sandbox-ssh-fix](./sandbox-ssh-fix) | Fixes git-over-SSH in the Claude Code sandbox on macOS by replacing the broken BSD nc SOCKS5 proxy | ## License diff --git a/sandbox-ssh-fix/.claude-plugin/plugin.json b/sandbox-ssh-fix/.claude-plugin/plugin.json new file mode 100644 index 0000000..50f9598 --- /dev/null +++ b/sandbox-ssh-fix/.claude-plugin/plugin.json @@ -0,0 +1,11 @@ +{ + "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", + "author": { + "name": "cblecker", + "email": "admin@toph.ca" + }, + "keywords": ["sandbox", "ssh", "git", "macos", "workaround"], + "homepage": "https://github.com/anthropics/claude-code/issues/70684" +} diff --git a/sandbox-ssh-fix/CLAUDE.md b/sandbox-ssh-fix/CLAUDE.md new file mode 100644 index 0000000..8c7bac0 --- /dev/null +++ b/sandbox-ssh-fix/CLAUDE.md @@ -0,0 +1,25 @@ +# sandbox-ssh-fix + +Workaround for [anthropics/claude-code#70684](https://github.com/anthropics/claude-code/issues/70684). + +## Detection and fix logic + +The SessionStart hook (`scripts/fix-git-ssh.sh`) runs on every session start and +checks three conditions before acting: + +| Condition | Variable | Check | +|-----------|----------|-------| +| Inside sandbox | `SANDBOX_RUNTIME` | `== 1` | +| Broken proxy | `GIT_SSH_COMMAND` | contains `nc -X 5` | +| Auth required | `ALL_PROXY` | contains `@` | + +When all conditions are true, the script parses `ALL_PROXY` +(`socks5h://user:pass@host:port`) and writes a corrected `GIT_SSH_COMMAND` to +`$CLAUDE_ENV_FILE`. + +The `ncat` path uses `127.0.0.1` instead of `localhost` because ncat resolves +`localhost` differently and the connection is refused. + +SSH options `ControlMaster=no` and `ControlPath=none` are preserved from the +original command because SSH mux sockets aren't in the sandbox's allowed Unix +socket paths. diff --git a/sandbox-ssh-fix/README.md b/sandbox-ssh-fix/README.md new file mode 100644 index 0000000..a8ec50e --- /dev/null +++ b/sandbox-ssh-fix/README.md @@ -0,0 +1,43 @@ +# sandbox-ssh-fix + +Workaround for broken git-over-SSH in the Claude Code sandbox on macOS. + +## Problem + +The Claude Code sandbox injects `GIT_SSH_COMMAND` with a `ProxyCommand` using +BSD `nc -X 5` for SOCKS5 proxying. On macOS, `nc` doesn't support SOCKS5 +authentication, and the proxy requires auth — breaking all git-over-SSH +operations (`git fetch`, `git push`, `git pull`). + +Tracked upstream: + +## How it works + +A SessionStart hook detects the broken pattern and overrides `GIT_SSH_COMMAND` +via `$CLAUDE_ENV_FILE`: + +- 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) + +The fix only activates when all conditions are met: + +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 `@`) + +## Prerequisites + +Install `ncat` (from nmap) for full SOCKS5 proxy support: + +```bash +brew install nmap +``` + +Without `ncat`, the plugin falls back to direct SSH which still works but +bypasses the sandbox's SOCKS5 proxy. + +## License + +Apache License 2.0. See [LICENSE](../LICENSE) for details. diff --git a/sandbox-ssh-fix/hooks/hooks.json b/sandbox-ssh-fix/hooks/hooks.json new file mode 100644 index 0000000..3e74881 --- /dev/null +++ b/sandbox-ssh-fix/hooks/hooks.json @@ -0,0 +1,14 @@ +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/fix-git-ssh.sh" + } + ] + } + ] + } +} diff --git a/sandbox-ssh-fix/scripts/fix-git-ssh.sh b/sandbox-ssh-fix/scripts/fix-git-ssh.sh new file mode 100755 index 0000000..c79a12e --- /dev/null +++ b/sandbox-ssh-fix/scripts/fix-git-ssh.sh @@ -0,0 +1,29 @@ +#!/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}" <> "${CLAUDE_ENV_FILE}" + echo "SessionStart:startup hook success: sandbox-ssh-fix: ncat not found, bypassing SOCKS5 proxy (using direct SSH)" +fi