Running the proxy
Running modes
Section titled “Running modes”There are three ways to drive the proxy, trading convenience for interactivity.
One-shot: proxy run
Section titled “One-shot: proxy run”varlock proxy run -- <command>Starts a proxy, runs the command through it, and tears down when the command exits. The single most common way to use the feature. If a proxy start daemon is already running for this directory, proxy run attaches to it (so its terminal owns the live request log); otherwise it runs a self-contained proxy. Pass --new to force a fresh, separate proxy.
Daemon + attach: proxy start
Section titled “Daemon + attach: proxy start”# Terminal 1: owns the proxy and its live request logvarlock proxy start
# Terminal 2: attaches automaticallyvarlock proxy run -- claudeproxy start is a long-lived session that owns its terminal, where the live per-request log appears. Attach to it from another terminal (or shell) so the agent’s stdio doesn’t compete with the daemon’s output.
An attaching proxy run adopts the running session’s env: it fetches the child view (placeholders, non-secret values, omitted keys) directly from the daemon instead of resolving anything itself. That means no second unlock prompt, and the session’s own overrides and env selection apply (attaching means “run me in that session’s env”). Your shell’s ambient values for schema-managed keys are ignored in favor of the session’s; everything else (PATH, etc.) passes through as usual.
Manual env: proxy start + proxy env
Section titled “Manual env: proxy start + proxy env”varlock proxy start # in one terminaleval "$(varlock proxy env)" # in another shellnode agent.js # now routed through the proxyproxy env prints the proxy + CA environment for an existing session so you can source it into any shell or tool.
Sessions
Section titled “Sessions”Every varlock proxy command operates on a session: one running proxy with its own short id (printed by proxy start, listed by proxy status). You target a session in one of two ways:
- By id: pass
--session <id>to any command (proxy env --session abc12,proxy stop --session abc12, etc.). - Auto-discovered: with no
--session, the command resolves one for you:proxy runattaches to the runningproxy startdaemon for the current directory (a session whose directory is this one or a parent of it). If there isn’t one it starts its own proxy; pass--newto always start a fresh, separate one.env/status/audit/reload/stopuse the single active session. If more than one is running they ask you to pass--session <id>; commands run from inside a proxied child target that child’s own session automatically.
Sessions are durable records: they persist after the proxy stops (visible with proxy status --all) so their audit log stays available.
How the child is wired
Section titled “How the child is wired”proxy run (and proxy env) inject a standard set of environment variables into the child so common HTTP clients trust the proxy with no manual setup:
- Proxy address:
HTTP_PROXY/HTTPS_PROXY/ALL_PROXY(and lowercase), withNO_PROXYexcluding localhost. - CA trust:
NODE_EXTRA_CA_CERTS(Node.js),REQUESTS_CA_BUNDLE(Pythonrequests),CURL_CA_BUNDLE(curl),GIT_SSL_CAINFO(git),CARGO_HTTP_CAINFO(cargo),DENO_CERT(Deno), andSSL_CERT_FILE(OpenSSL-based tools). - Runtime opt-ins:
NODE_USE_ENV_PROXY=1, so Node’s built-infetch(Node 24+) routes through the proxy. Without it,fetchignores the proxy env vars entirely and sends requests (with placeholder values) directly to the upstream. On older Node versions the flag is ignored andfetchstill bypasses the proxy; use an env-proxy-aware client (axios, got) or Node 24+.
The proxy URL carries no credentials, and the proxy never asks clients for proxy authentication (no 407 challenges). Session scoping is enforced by the proxy itself, so clients with limited proxy-auth support (like Python’s urllib) work with the plain HTTPS_PROXY url.
The proxy uses an ephemeral, in-memory certificate authority generated per run; only its public certificate is written to a temp file for the child to trust. The CA private key and all per-host certificates never leave memory.
Stdout/stderr redaction is decided per stream: a stream attached to an interactive terminal passes through raw (so TUIs like claude render correctly), while a piped or redirected stream is scrubbed of sensitive values, including the real values the proxy injects at the wire, in case an upstream response echoes one back. Override with --redact-stdout / --no-redact-stdout.
Client compatibility
Section titled “Client compatibility”The proxy works with any client that honors the standard proxy and CA env vars above. Status of common clients:
| Client | Works? | Notes |
|---|---|---|
| curl, git, wget | yes | --http2 is fine: ALPN negotiates down to HTTP/1.1 |
Python: python.org, Homebrew, pyenv, uv builds | yes | urllib, requests, and httpx all verified, including python 3.13+ strict verification |
Python: macOS system (/usr/bin/python3) | partial | proxied, but ignores SSL_CERT_FILE so urllib fails TLS verification; see workarounds below |
Node built-in fetch (Node 24+) | yes | via the injected NODE_USE_ENV_PROXY=1 |
Node built-in fetch (Node < 24) | no | silently bypasses the proxy and sends placeholders directly upstream; use axios/got or Node 24+ |
| Node axios, got | yes | implement env-proxy support themselves |
Bun fetch | yes | |
| Deno | yes | via the injected DENO_CERT |
Go programs (incl. gh, terraform, kubectl) | Linux yes, macOS no | on macOS Go verifies against the system keychain and rejects the proxy CA |
| .NET | Linux yes, macOS no | same system-keychain issue as Go |
| Ruby | build-dependent | OpenSSL-linked builds work; macOS system Ruby has the same SSL_CERT_FILE issue as system Python |
| Rust (reqwest etc.) | build-dependent | native-tls/OpenSSL backends honor SSL_CERT_FILE; rustls platform-verifier or bundled-roots builds reject the proxy CA |
| JVM tools (java, gradle, maven) | no | the JVM ignores both the proxy and CA env vars on every platform; needs -Dhttps.proxyHost/-Dhttps.proxyPort and a custom truststore |
| gRPC clients | no on matched hosts | gRPC requires HTTP/2 and the proxy’s TLS interception is HTTP/1.1 only; hosts without a @proxy rule tunnel through untouched |
WebSockets (wss://) | no on matched hosts | upgrades through the interception path are unsupported; hosts without a @proxy rule tunnel through untouched |
Pinning the port and CA location
Section titled “Pinning the port and CA location”By default the proxy binds a random loopback port and writes its CA cert to a fresh temp dir. Both are discoverable after startup with proxy env / proxy status. When something needs to be wired up before the proxy starts (a config that points at a fixed HTTP_PROXY, or a tool told to trust a CA at a known path), pin them:
varlock proxy start --port 8080 --cert-dir ./.varlock-proxy--port <n> fixes the loopback port, so HTTP_PROXY is http://127.0.0.1:<n>. If that port is already in use the proxy refuses to start instead of falling back to a random one.
--cert-dir <dir> writes ca-cert.pem (and combined-ca.pem, the CA plus system roots) into <dir>, creating it if missing. The CA is still ephemeral and regenerated on each start; on stop, only the cert files varlock wrote are removed, never the directory itself.
Both flags apply only when starting a proxy. They also work on proxy run when it starts its own proxy (with --new, or when no daemon is running for the directory). On a proxy run that attaches to an existing daemon they are rejected, since that daemon already fixed the port and cert location.
Auditing
Section titled “Auditing”Every request through the proxy is appended to a per-session, secrets-free audit log (host, method, path, a request hash, the matched rule, the decision, and which key names were injected, never any values).
varlock proxy audit # current/most-recent sessionvarlock proxy audit --session <id> # a specific sessionvarlock proxy audit --format json # machine-readableSessions are kept as durable records after they end. List them with:
varlock proxy status # active sessionsvarlock proxy status --all # include ended sessionsvarlock proxy status --watch # live updatesEditing the schema while a session is running
Section titled “Editing the schema while a session is running”For safety, a proxied command refuses to run if your env schema has changed since the proxy session started. This prevents an agent from editing the schema mid-session (downgrading @sensitive, or adding a new item that resolves a secret) to recover real values. After an intentional edit, restart the proxy from a trusted (non-proxied) shell so it re-resolves the new schema:
varlock proxy stop --allvarlock proxy start # or: varlock proxy run -- <command>A one-shot varlock proxy run already re-reads the schema on its next invocation, so just re-run it.
A running varlock proxy start also warns in its live log when it detects your env schema changed on disk (any of the loaded .env* files, not just .env.schema). It keeps serving the previous policy (it does not block your agent’s in-flight requests just because a file changed), so reload or restart to apply the change, and if you did not make the edit, a proxied process may have.
Sandboxing
Section titled “Sandboxing”On its own the proxy raises the bar but is not a hard boundary: a determined same-uid agent can still reach a secret’s source. Close that with --sandbox so the child’s only network path is the proxy:
varlock proxy run --sandbox -- claudeBare --sandbox is a built-in macOS jail (loopback-only egress + credential-path and keychain denials). Use --sandbox=docker (or =podman) for a container whose only egress is the host proxy. Details, flags, and third-party recipes: Sandboxing.