Repository navigation
Integrations Sandbox Runtime
Experimental: this works and is tested, but may change and gets less hardening than the stable core. Tested: the decider against a stand-in that speaks srt-proxy's protocol as its source does; not yet against a released srt (#312).
Anthropic's sandbox-runtime (srt) can run its HTTP proxy on its
own, with no sandboxed command: srt-proxy, also srt proxy. A host that runs the workload elsewhere (a VM, a
container, another sandbox) points the workload's traffic at it. Before srt-proxy forwards a request, it asks a
separate process, the decider, and it forwards nothing without one (sandbox-runtime's
docs/srt-proxy.md).
tracekit srt-decider is that decider. The Tracekit signer's policy decides each request, holds the ones it asks
about for a person, and signs a record of each one into a run.
| srt-proxy | Tracekit | |
|---|---|---|
| What it does | Accepts the workload's connections and decrypts its HTTPS with your CA. It refuses hosts outside the decider's list and checks the address it resolves before dialling | Decides each request srt-proxy asks about, holds risky ones for a person, signs a record of each decision and approval |
| Where | A process the host starts | A signer outside the workload (dev or system mode) |
| What a third party can check later | Nothing: it keeps no record | A signed, hash-chained bundle, verified offline |
This page is for srt used without Claude Code. With Claude Code, use Claude Code's sandbox with Tracekit's hooks; how the two differ is below.
Linux and macOS. You need srt-proxy (decider protocol version 1, as in sandbox-runtime 0.0.80; it runs on Node 22.12
or later), a signer (tracekit up starts
the dev signer; TRACEKIT_SIGNER names another) and a CA for srt-proxy to decrypt HTTPS with. Without a CA,
srt-proxy refuses every CONNECT. srt-proxy needs an RSA key; this is the command sandbox-runtime's own test CA was
made with:
openssl req -x509 -newkey rsa:2048 -nodes -sha256 -keyout ca.key -out ca.crt -days 30 \
-subj '/CN=srt-proxy CA' -addext 'basicConstraints=critical,CA:TRUE' \
-addext 'keyUsage=critical,keyCertSign,cRLSign' -addext 'subjectKeyIdentifier=hash'
cat ca.key ca.crt > ca.pemStart srt-proxy through Tracekit, naming the hosts the workload may reach:
tracekit srt-decider --allow-domain api.github.com --allow-domain '*.pythonhosted.org' --allow-domain pypi.org \
-- srt-proxy --listen 127.0.0.1:3128 --ca-fd 3 3<ca.pemtracekit srt-decider registers a run (agent srt-proxy; --agent NAME to change it). It then starts the command
after --, adding --decider-fd with one end of a socket pair. srt-proxy inherits the descriptors Tracekit got from
your shell, so its own --ca-fd, --listen-fd and --lifeline-fd work as they do without Tracekit. When srt-proxy
exits, Tracekit closes the run and exits with srt-proxy's exit code. When Tracekit exits, srt-proxy refuses whatever is
waiting and exits too. The signer closes a run with no request for idle_s (an hour by default); Tracekit refuses
the next request and stops srt-proxy, so run it under a supervisor that restarts it. --listen binds a loopback
address and is for development; in production, hand srt-proxy a listening socket with --listen-fd.
The --allow-domain hosts are the list srt-proxy holds requests to (*.example.com for subdomains). srt-proxy
refuses every other host itself, with 403 and X-Deny-Reason: host_not_allowed, and never asks Tracekit, so
Tracekit records nothing of those requests. Its protocol has no entry that matches every host.
Point the workload at the proxy and make it trust the CA, for example:
export HTTPS_PROXY=http://127.0.0.1:3128 HTTP_PROXY=http://127.0.0.1:3128
export SSL_CERT_FILE=$PWD/ca.crt NODE_EXTRA_CA_CERTS=$PWD/ca.crt REQUESTS_CA_BUNDLE=$PWD/ca.crtTo run the workload in srt's sandbox as well, set network.httpProxyPort in ~/.srt-settings.json:
{
"network": {"allowedDomains": [], "deniedDomains": [], "httpProxyPort": 3128},
"filesystem": {"denyRead": [], "allowWrite": ["."], "denyWrite": []}
}srt then starts no HTTP proxy of its own and points the command's HTTP_PROXY and HTTPS_PROXY at that port. Its
allowedDomains don't apply to that traffic: srt's settings schema says "the external proxy must handle domain
filtering". Here srt-proxy's list and Tracekit's policy do that. The command still has to trust the CA
(srt -- env SSL_CERT_FILE=$PWD/ca.crt curl https://api.github.com).
Each request is decided as the tool srt:request, class http, with these arguments:
{"method": "POST", "url": "https://api.github.com/repos/o/r/issues?x=1", "body": "sha256:3f5a…"}-
urlishttps://when the request came through a decrypted tunnel with a TLS server name, elsehttp://. srt-proxy's request has no scheme field. The path and query are as the client spelled them; a request target that isn't a path (absolute-formhttps://host/path, or*) is refused (400). -
bodyis the SHA-256 of the request body. It is there for a request other than GET, HEAD or OPTIONS whose headers announce a body. Tracekit asks srt-proxy for the body (up to 32 MiB) and refuses a longer one (413). - The policy's
httprules apply. The dev pack allows every request and flags it (TK-F002,TK-F012). The server pack denies internal addresses and names (TK-N001,TK-N003) and data sent outside its allowlist (TK-N002) (policy). Writeaskrules for the requests a person should see. A policy that extends no shipped pack maps the tool itself (tools: {'srt:request': http}).
| Decision | srt-proxy answers the workload |
|---|---|
| allow | forwards the request |
| deny |
403, with the rule ids in the reason |
| ask |
403 with the approval id: held for a person's approval (apr-…); the decider prints the same id |
| signer unreachable |
503, whatever the run's fail mode says |
| a request Tracekit can't read | 400 |
srt-proxy waits 5 seconds for each answer, too short for a person. So an ask is refused at once, and its approval
waits in the signer:
[tracekit] POST api.github.com: waiting for approval apr-… (TK-…)
tracekit approvals approve apr-… # from another terminal
Once a person approves it, the same request sent again (same method, URL and body) is allowed, once. The next one asks again. A request that differs in any of them is a new request with its own approval. A rejected approval is reported on the next try, and the try after that asks again (approvals).
Claude Code runs srt inside its own process. Its sandbox settings set the allowlist, and its own prompts ask about new
hosts. Setting sandbox.network.httpProxyPort hands that traffic to your proxy, and Claude Code's allowlist,
prompts and local-address check stop applying to it (Claude Code's sandbox). srt-proxy with
tracekit srt-decider is a proxy that enforces an allowlist, but we haven't tested Claude Code pointed at it. With
Claude Code, keep its own proxy and use Tracekit's hooks, which already decide every call Claude makes.
- Sees: each request srt-proxy asks about: its method, host, port, path, query and the body's digest. It also sees the decision and any approval. The run's records commit to these arguments rather than store them (privacy).
-
Doesn't see: requests to hosts outside
--allow-domain, which srt-proxy refuses itself. It doesn't see the response to an allowed request, or whether srt-proxy forwarded it: srt-proxy can still refuse an allowed request, for instance when the host resolves to a private address or the answer came after its 5 seconds. The record says the request was allowed and that Tracekit doesn't see the response. - Doesn't bind: request headers, and the body of a GET, HEAD or OPTIONS. An approval covers the method, URL and body digest.
- Doesn't attribute: which process sent a request. srt-proxy's request names no client, so one decider is one run. Run one srt-proxy per agent run.
Limits lists these with what to do.
Generated from docs/ at v1.4.1. Edit the docs in the repository, not here.
Get started
- Quickstart: the v2 signer
- Quickstarts
- Quickstart: Claude Agent SDK
- Quickstart: a custom agent
- Quickstart: LangChain and LangGraph
- Quickstart: MCP client
- Quickstart: OpenAI Agents SDK
- Coding agents other than Claude Code
- tracekit doctor
- Walkthrough: Tracekit end to end
How it works
- How Tracekit works
- Overview
- The life of a tool call
- The evidence format
- Verification and assurance
- Witnesses and monitoring
- Policy
- Approvals
- Detection and containment
- Capture and completeness
- Custody
- Provenance and tracekit why
- Deployment and isolation
- Storage, durability and performance
- Integrations
- Security model
- Architecture
- tracekit why: architecture
Integrations
- Integrations
- Adapters
- OpenTelemetry
- Remote clients
- Kubernetes agent-sandbox
- agentgateway
- agentsh and Tracekit
- Browser Use
- Claude Agent SDK
- Claude Code's sandbox
- Codex's sandbox
- CrewAI
- Docker MCP Gateway
- E2B
- Falco
- Gemini CLI's sandbox
- Google ADK
- Haystack
- LangChain v1
- LiteLLM
- LlamaIndex
- MCP client
- microsandbox
- Microsoft Agent Framework
- OpenAI Agents SDK
- NVIDIA OpenShell
- Pydantic AI
- sandbox-runtime's proxy on its own
- Strands Agents
- Tetragon
- Vercel AI SDK
- Wassette
Policy and approvals
- Policy reference
- Approvals
- Findings
- Reading the model's reasoning
- Incidents and containment
- Webhooks
Deploy and operate
- Deploying the signer
- The signer as a container
- The signer on Kubernetes
- The compose stack
- Identity
- Tool executor
- MCP gateway
- LLM gateway
- Egress observation
- Witnesses
- Log monitor
- Record-key issuer
- Signer observability
- Viewer
- tracekit why: causal investigation and replay
- Migrating
- Platforms
Verify the evidence
- Reading a v2 verify report
- Evidence format v2
- Auditor guide
- Sample evidence bundle
- Signing
- What Tracekit guarantees
Security and privacy
- Threat model: the v2 signer
- Threat model: the v1 laptop signer
- HTTP security checklist
- Threat-model review packet
- Privacy
- Known limits and trade-offs
- What's stable
Compliance
- Compliance mapping
- EU AI Act
- ISO/IEC 42001
- NIST AI RMF and the Generative AI Profile
- OWASP Top 10 lists for LLM and agentic applications
Evaluation
Demos
- Demos
- Agent approvers within bounds
- Completeness and tamper evidence
- Detection and containment
- Network coverage: an egress observer
- Guardrails: allow, deny, or ask a person
- MCP tools, gated and recorded
- Prompt injection held by provenance
- Why: which input caused the action
Help