A zero-dependency, stdlib-only Go forward proxy that injects your real
credentials (GitHub GH_TOKEN, npm token, β¦) into outbound requests on the
wire, so code running in a sandbox can use them without ever seeing them.
A simplified version of Infisical's agent-vault,
driven by a single script (sandbox.sh) over a dependency-free proxy binary you own.
Code runs in a container with no network access except through the proxy. The proxy holds your tokens and injects them into outbound requests as they leave β so the workload can use them but never sees them.
sandbox (git / gh / npm β no token; HTTPS_PROXY, trusts proxy CA)
β
β request without credentials
βΌ
sandbox-proxy (holds the real token; default-deny allow-list)
β
β + Authorization injected on the wire
βΌ
upstream (github.com, api.github.com, registry.npmjs.org, β¦)
- π Open by default (
allow_all: true) for easy setup β egress to any host, with credentials injected only on your configured hosts. Setallow_all: falsefor strict default-deny (only listed hosts reachable). - π 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), and hosts you don't inject into can be blind-tunnelled untouched. - π‘οΈ A compromised workload can at most use a token against the hosts you allow β it can't read or exfiltrate the secret itself.
Source the control script once; it gives you a sandbox function that manages
one shared proxy and any number of sandbox containers.
source sandbox.sh
# GH_TOKEN is taken from `gh auth token` automatically if it isn't already set.
export NPM_TOKEN=npm_xxx # optional; export any secret your rules need
sandbox proxy up # build (if needed) + start the shared proxy
sandbox proxy status # is it running? on which networks?
# in any project directory:
cd ~/code/my-app
sandbox run # ensures proxy is up, opens a shell in $PWD
# ...or run a command directly:
sandbox run npm ci
sandbox run git clone https://github.com/you/private.gitInside a sandbox there is no token in the environment, yet git/npm are authenticated β the proxy injects credentials on the way out. Run as many sandboxes as you like at once; they all share the single proxy:
(cd ~/code/app-a && sandbox run npm test) &
(cd ~/code/app-b && sandbox run npm test) &
sandbox ps # list running sandboxesCommands:
| Command | Does |
|---|---|
sandbox proxy up |
Build if needed, start the shared proxy. |
sandbox proxy status |
Show whether it's running and its networks. |
sandbox proxy reload |
Restart the proxy, picking up current env/tokens and config edits. |
sandbox proxy down / logs |
Stop+remove / follow logs. |
sandbox run [cmd...] |
Ensure the proxy is up, run a sandbox in $PWD (shell if no cmd). |
sandbox build [proxy|box|all] |
Force-rebuild images. |
sandbox ps |
List running sandboxes. |
π Where secrets come from: for each var in $SANDBOX_SECRET_ENVS (default
GH_TOKEN NPM_TOKEN), sandbox uses the environment value if set, otherwise
runs SANDBOX_<VAR>_CMD on the host. GH_TOKEN defaults to gh auth token, so
just being logged into gh is enough β no need to export anything. Resolution
happens in a subshell at proxy up/reload, so tokens never persist in your shell,
and they're passed to the container by name (never on the command line). To pull
another secret from a command, e.g.:
export SANDBOX_SECRET_ENVS="GH_TOKEN NPM_TOKEN AWS_TOKEN"
export SANDBOX_AWS_TOKEN_CMD="aws-vault exec me -- printenv AWS_SESSION_TOKEN"πΎ Persisting tool configs / caches: set SANDBOX_VOLUMES to a
whitespace-separated list of docker -v specs and every sandbox run mounts
them (named volumes are created on first use and survive across sandboxes):
export SANDBOX_VOLUMES="claude-config:/root/.claude codex-config:/root/.codex pi-config:/root/.pi"Each entry is a raw -v spec, so host paths ($HOME/.foo:/root/.foo) and
read-only mounts (somevol:/root/.x:ro) work too. Note these are shared across
all sandboxes, so treat anything mounted there as readable by any workload.
β»οΈ Changing tokens or rules: edit proxy/config.json and/or update the token
source, then sandbox proxy reload β it re-resolves secrets (picking up a
rotated gh token) and restarts. The CA is stored in a persistent Docker
volume, so it survives reloads and sandboxes keep trusting it.
The sandbox network is created --internal, so a sandbox physically cannot
reach the internet except through the proxy. Confirm:
sandbox run sh -c 'env | grep -i token' # -> nothing (no token inside the sandbox)
sandbox run curl -sI https://example.com # reachable via the proxy (allow_all default);
# with allow_all:false an unlisted host -> 403Override defaults (network/image/volume names, which env vars are forwarded as
secrets) by exporting SANDBOX_* vars before sourcing β see the top of
sandbox.sh.
π¦ Installing packages at runtime: the image sets both upper- and lower-case
proxy vars, so apt, curl, wget, go, npm, bun all route through the
proxy β apt update && apt install <pkg> works (needs allow_all, or the Ubuntu
archive hosts allow-listed). These installs live in the --rm container and
vanish on exit; for anything you want every time, add it to sandbox/Dockerfile
and sandbox build box. Your host TERM/COLORTERM are forwarded too, so
full-color TUIs work.
π Reaching raw-TCP / LAN services: the proxy only speaks HTTP/HTTPS, but its
CONNECT/blind-tunnel is a generic TCP splice β so any TCP service is reachable
via the bundled forward (alias tunnel) helper, which exposes it on
127.0.0.1 through the proxy:
forward 192.168.1.39 5432 # 127.0.0.1:5432 -> 192.168.1.39:5432
forward 192.168.1.39 5432 6379 & # several ports, backgroundedPoint your client at 127.0.0.1:<port>. Keep allow_all on and don't add a rule
for the host β a rule would MITM it and break the raw stream. The proxy must be
able to reach the target on the LAN.
proxy/config.json maps secrets (how to build an auth header, with the value
read from the proxy's environment) to rules (which host gets which secret):
{
"allow_all": true,
"secrets": {
"github": { "type": "basic", "env": "GH_TOKEN", "username": "x-access-token" },
"github-api": { "type": "bearer", "env": "GH_TOKEN" },
"npm": { "type": "bearer", "env": "NPM_TOKEN" }
},
"rules": [
{ "host": "github.com", "inject": "github" },
{ "host": "api.github.com", "inject": "github-api" },
{ "host": "*.githubusercontent.com" },
{ "host": "registry.npmjs.org", "inject": "npm" }
]
}- secrets β
typeisbearerorbasic;envnames the host env var holding the token (never the value itself).usernameis for basic auth (GitHub uses the token as the password with any username). - rules β matched by
host, covering all methods and paths. A*.prefix is a suffix wildcard:*.githubusercontent.commatchesobjects./raw./ any subdomain (and the bare domain) β handy for CDN hosts behind gh-release/git-lfs/npm-tarball downloads.injectnames a secret to add on every request; omit it to allow a host with no credential added. allow_allβtrueby default for simplicity: egress to any host, with injection still scoped to listed hosts (others are blind-tunnelled, untouched). Set it tofalsefor strict default-deny β only listed hosts are reachable.β οΈ Withallow_allon, the proxy is a credential broker, not a firewall: a compromised workload can send data anywhere. Flip it tofalsefor untrusted code.
Run sandbox proxy reload to re-read the config.
The proxy is just a binary β no runtime deps.
cd proxy/src
go build -o sandbox-proxy . # or: GOOS=linux GOARCH=amd64 go build ...
GH_TOKEN=ghp_xxx NPM_TOKEN=npm_xxx ./sandbox-proxy # listens on :3128, writes ca/ca.crt
# point any client at it:
export HTTPS_PROXY=http://127.0.0.1:3128
curl --cacert ca/ca.crt https://api.github.com/user # authenticated, token never left the proxy
git -c http.proxy=$HTTPS_PROXY clone https://github.com/your/private.gitCross-compile for any target with GOOS/GOARCH β the output is a single
static file you can drop anywhere.
cd proxy/src && go test ./...Covers the policy engine (host matching, decide, credential injection incl.
placeholder overwrite), config loading, header/host helpers, WebSocket-upgrade
detection, and end-to-end forward-proxy and CONNECT/MITM flows (injection,
default-deny, allow_all) via httptest. _test.go files are excluded from the
built binary.
| Var | Default | Meaning |
|---|---|---|
PROXY_LISTEN |
:3128 |
listen address |
PROXY_CONFIG |
config.json |
rules file |
PROXY_CA_DIR |
ca |
where ca.crt / ca.key are stored/generated |
HTTP(S)_PROXY |
β | upstream proxy for the proxy's own egress (optional) |
ca.keystays in the proxy. It lives (mode0600) in the proxy-only CA volume; sandboxes bind-mount only the publicca.crtread-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
--internalsandbox network (sandbox-net) is what actually forces traffic through the proxy; without it a workload could ignoreHTTPS_PROXYand dial out directly.sandbox.shcreates it internal by default. - Scope tightly. Prefer specific injection hosts over broad
allow_allso a leaked-but-injected token is useful only for what you intended.