{"slug": "show-hn-sandbox-proxy-a-forward-proxy-that-injects-credentials-to-sandboxes", "title": "Show HN: Sandbox-proxy – A forward proxy that injects credentials to sandboxes", "summary": "A developer released Sandbox-proxy, a zero-dependency Go forward proxy that injects credentials into outbound requests on the wire, allowing sandboxed code to use tokens without seeing them. The tool simplifies secret management for containerized workloads by intercepting HTTPS traffic and injecting credentials only for configured hosts.", "body_md": "A zero-dependency, **stdlib-only** Go forward proxy that injects your real\ncredentials (GitHub `GH_TOKEN`\n\n, npm token, …) into outbound requests **on the\nwire**, so code running in a sandbox can use them without ever *seeing* them.\n\nA simplified version of [Infisical's agent-vault](https://github.com/Infisical/agent-vault),\ndriven by a single script (`sandbox.sh`\n\n) over a dependency-free proxy binary you own.\n\nCode runs in a container with **no network access except through the proxy**.\nThe proxy holds your tokens and injects them into outbound requests as they\nleave — so the workload can *use* them but never *sees* them.\n\n```\nsandbox  (git / gh / npm — no token; HTTPS_PROXY, trusts proxy CA)\n   │\n   │  request without credentials\n   ▼\nsandbox-proxy  (holds the real token; default-deny allow-list)\n   │\n   │  + Authorization injected on the wire\n   ▼\nupstream  (github.com, api.github.com, registry.npmjs.org, …)\n```\n\n- 🌐\n**Open by default**(`allow_all: true`\n\n) for easy setup — egress to any host, with credentials injected only on your configured hosts. Set`allow_all: false`\n\nfor strict default-deny (only listed hosts reachable). - 🔏\n**HTTPS interception** via a CA it generates on first run and the sandbox trusts; the intercepted TLS speaks**HTTP/1.1 only**(ALPN pins`http/1.1`\n\n), and hosts you don't inject into can be blind-tunnelled untouched. - 🛡️ A compromised workload can at most\n*use*a token against the hosts you allow — it can't read or exfiltrate the secret itself.\n\nSource the control script once; it gives you a `sandbox`\n\nfunction that manages\n**one shared proxy** and **any number of sandbox containers**.\n\n```\nsource sandbox.sh\n\n# GH_TOKEN is taken from `gh auth token` automatically if it isn't already set.\nexport NPM_TOKEN=npm_xxx           # optional; export any secret your rules need\n\nsandbox proxy up                   # build (if needed) + start the shared proxy\nsandbox proxy status               # is it running? on which networks?\n\n# in any project directory:\ncd ~/code/my-app\nsandbox run                        # ensures proxy is up, opens a shell in $PWD\n# ...or run a command directly:\nsandbox run npm ci\nsandbox run git clone https://github.com/you/private.git\n```\n\nInside a sandbox there is **no token in the environment**, yet git/npm are\nauthenticated — the proxy injects credentials on the way out. Run as many\nsandboxes as you like at once; they all share the single proxy:\n\n```\n(cd ~/code/app-a && sandbox run npm test) &\n(cd ~/code/app-b && sandbox run npm test) &\nsandbox ps                         # list running sandboxes\n```\n\nCommands:\n\n| Command | Does |\n|---|---|\n`sandbox proxy up` |\nBuild if needed, start the shared proxy. |\n`sandbox proxy status` |\nShow whether it's running and its networks. |\n`sandbox proxy reload` |\nRestart the proxy, picking up current env/tokens and config edits. |\n`sandbox proxy down` / `logs` |\nStop+remove / follow logs. |\n`sandbox run [cmd...]` |\nEnsure the proxy is up, run a sandbox in `$PWD` (shell if no cmd). |\n`sandbox build [proxy|box|all]` |\nForce-rebuild images. |\n`sandbox ps` |\nList running sandboxes. |\n\n**🔑 Where secrets come from:** for each var in `$SANDBOX_SECRET_ENVS`\n\n(default\n`GH_TOKEN NPM_TOKEN`\n\n), `sandbox`\n\nuses the environment value if set, otherwise\nruns `SANDBOX_<VAR>_CMD`\n\non the host. `GH_TOKEN`\n\ndefaults to `gh auth token`\n\n, so\njust being logged into `gh`\n\nis enough — no need to export anything. Resolution\nhappens in a subshell at proxy up/reload, so tokens never persist in your shell,\nand they're passed to the container by name (never on the command line). To pull\nanother secret from a command, e.g.:\n\n```\nexport SANDBOX_SECRET_ENVS=\"GH_TOKEN NPM_TOKEN AWS_TOKEN\"\nexport SANDBOX_AWS_TOKEN_CMD=\"aws-vault exec me -- printenv AWS_SESSION_TOKEN\"\n```\n\n**💾 Persisting tool configs / caches:** set `SANDBOX_VOLUMES`\n\nto a\nwhitespace-separated list of docker `-v`\n\nspecs and every `sandbox run`\n\nmounts\nthem (named volumes are created on first use and survive across sandboxes):\n\n```\nexport SANDBOX_VOLUMES=\"claude-config:/root/.claude codex-config:/root/.codex pi-config:/root/.pi\"\n```\n\nEach entry is a raw `-v`\n\nspec, so host paths (`$HOME/.foo:/root/.foo`\n\n) and\nread-only mounts (`somevol:/root/.x:ro`\n\n) work too. Note these are shared across\nall sandboxes, so treat anything mounted there as readable by any workload.\n\n**♻️ Changing tokens or rules:** edit `proxy/config.json`\n\nand/or update the token\nsource, then `sandbox proxy reload`\n\n— it re-resolves secrets (picking up a\nrotated `gh`\n\ntoken) and restarts. The CA is stored in a persistent Docker\nvolume, so it survives reloads and sandboxes keep trusting it.\n\nThe sandbox network is created `--internal`\n\n, so a sandbox physically cannot\nreach the internet except through the proxy. Confirm:\n\n``` php\nsandbox run sh -c 'env | grep -i token'    # -> nothing (no token inside the sandbox)\nsandbox run curl -sI https://example.com   # reachable via the proxy (allow_all default);\n                                           # with allow_all:false an unlisted host -> 403\n```\n\nOverride defaults (network/image/volume names, which env vars are forwarded as\nsecrets) by exporting `SANDBOX_*`\n\nvars before sourcing — see the top of\n`sandbox.sh`\n\n.\n\n**📦 Installing packages at runtime:** the image sets both upper- and lower-case\nproxy vars, so `apt`\n\n, `curl`\n\n, `wget`\n\n, `go`\n\n, `npm`\n\n, `bun`\n\nall route through the\nproxy — `apt update && apt install <pkg>`\n\nworks (needs `allow_all`\n\n, or the Ubuntu\narchive hosts allow-listed). These installs live in the `--rm`\n\ncontainer and\nvanish on exit; for anything you want every time, add it to `sandbox/Dockerfile`\n\nand `sandbox build box`\n\n. Your host `TERM`\n\n/`COLORTERM`\n\nare forwarded too, so\nfull-color TUIs work.\n\n`proxy/config.json`\n\nmaps **secrets** (how to build an auth header, with the value\nread from the proxy's environment) to **rules** (which host gets which secret):\n\n```\n{\n  \"allow_all\": true,\n  \"secrets\": {\n    \"github\":     { \"type\": \"basic\",  \"env\": \"GH_TOKEN\", \"username\": \"x-access-token\" },\n    \"github-api\": { \"type\": \"bearer\", \"env\": \"GH_TOKEN\" },\n    \"npm\":        { \"type\": \"bearer\", \"env\": \"NPM_TOKEN\" }\n  },\n  \"rules\": [\n    { \"host\": \"github.com\",              \"inject\": \"github\" },\n    { \"host\": \"api.github.com\",          \"inject\": \"github-api\" },\n    { \"host\": \"*.githubusercontent.com\" },\n    { \"host\": \"registry.npmjs.org\",      \"inject\": \"npm\" }\n  ]\n}\n```\n\n**secrets**—`type`\n\nis`bearer`\n\nor`basic`\n\n;`env`\n\nnames the host env var holding the token (never the value itself).`username`\n\nis for basic auth (GitHub uses the token as the*password*with any username).**rules**— matched by`host`\n\n, covering all methods and paths. A`*.`\n\nprefix is a suffix wildcard:`*.githubusercontent.com`\n\nmatches`objects.`\n\n/`raw.`\n\n/ any subdomain (and the bare domain) — handy for CDN hosts behind gh-release/git-lfs/npm-tarball downloads.`inject`\n\nnames a secret to add on every request; omit it to allow a host with no credential added.—`allow_all`\n\nfor simplicity: egress to any host, with injection still scoped to listed hosts (others are blind-tunnelled, untouched). Set it to`true`\n\nby default`false`\n\nfor strict default-deny — only listed hosts are reachable.⚠️ With`allow_all`\n\non, the proxy is a credential*broker*, not a firewall: a compromised workload can send data anywhere. Flip it to`false`\n\nfor untrusted code.\n\nRun `sandbox proxy reload`\n\nto re-read the config.\n\nThe proxy is just a binary — no runtime deps.\n\n```\ncd proxy/src\ngo build -o sandbox-proxy .                 # or: GOOS=linux GOARCH=amd64 go build ...\n\nGH_TOKEN=ghp_xxx NPM_TOKEN=npm_xxx ./sandbox-proxy   # listens on :3128, writes ca/ca.crt\n\n# point any client at it:\nexport HTTPS_PROXY=http://127.0.0.1:3128\ncurl --cacert ca/ca.crt https://api.github.com/user   # authenticated, token never left the proxy\ngit -c http.proxy=$HTTPS_PROXY clone https://github.com/your/private.git\n```\n\nCross-compile for any target with `GOOS`\n\n/`GOARCH`\n\n— the output is a single\nstatic file you can drop anywhere.\n\n```\ncd proxy/src && go test ./...\n```\n\nCovers the policy engine (host matching, `decide`\n\n, credential injection incl.\nplaceholder overwrite), config loading, header/host helpers, WebSocket-upgrade\ndetection, and end-to-end forward-proxy **and** CONNECT/MITM flows (injection,\ndefault-deny, `allow_all`\n\n) via `httptest`\n\n. `_test.go`\n\nfiles are excluded from the\nbuilt binary.\n\n| Var | Default | Meaning |\n|---|---|---|\n`PROXY_LISTEN` |\n`:3128` |\nlisten address |\n`PROXY_CONFIG` |\n`config.json` |\nrules file |\n`PROXY_CA_DIR` |\n`ca` |\nwhere `ca.crt` / `ca.key` are stored/generated |\n`HTTP(S)_PROXY` |\n— | upstream proxy for the proxy's own egress (optional) |\n\nIt lives (mode`ca.key`\n\nstays in the proxy.`0600`\n\n) in the proxy-only CA volume; sandboxes bind-mount**only** the public`ca.crt`\n\nread-only, never the volume — so a workload can't read the key and mint trusted certs. Keep the CA volume private on the host.**Lock down the network**, not just the env. The`--internal`\n\nsandbox network (`sandbox-net`\n\n) is what actually forces traffic through the proxy; without it a workload could ignore`HTTPS_PROXY`\n\nand dial out directly.`sandbox.sh`\n\ncreates it internal by default.**Scope tightly.** Prefer specific injection hosts over broad`allow_all`\n\nso a leaked-but-injected token is useful only for what you intended.", "url": "https://wpnews.pro/news/show-hn-sandbox-proxy-a-forward-proxy-that-injects-credentials-to-sandboxes", "canonical_source": "https://github.com/yagop/sandbox", "published_at": "2026-07-04 11:56:45+00:00", "updated_at": "2026-07-04 12:20:10.795947+00:00", "lang": "en", "topics": ["developer-tools", "ai-safety"], "entities": ["Infisical", "GitHub", "npm"], "alternates": {"html": "https://wpnews.pro/news/show-hn-sandbox-proxy-a-forward-proxy-that-injects-credentials-to-sandboxes", "markdown": "https://wpnews.pro/news/show-hn-sandbox-proxy-a-forward-proxy-that-injects-credentials-to-sandboxes.md", "text": "https://wpnews.pro/news/show-hn-sandbox-proxy-a-forward-proxy-that-injects-credentials-to-sandboxes.txt", "jsonld": "https://wpnews.pro/news/show-hn-sandbox-proxy-a-forward-proxy-that-injects-credentials-to-sandboxes.jsonld"}}