Repository navigation
Deploy
One signer program, tracekit signer serve, runs in every mode. The agent's code doesn't change between modes:
TRACEKIT_SIGNER points it at the signer (a socket path, an https:// URL, or http:// on a loopback address for
the insecure_loopback listener), and without it the client starts a
dev signer of its own. What changes is who can reach the signer's keys and store, and that decides what a bundle
can prove.
| Mode | Platforms | Agent reaches the signer by | Who can reach the keys and store | Recorded isolation
|
|---|---|---|---|---|
| Laptop dev | Linux, macOS, Windows | auto-spawned Unix socket (Windows: loopback TCP) | the agent's own user | same-user |
| Laptop system mode | Linux; macOS experimental | /run/tracekit-signer/signer.sock |
root and the signer's OS user | separate-user |
| Container sidecar | Linux containers, Kubernetes 1.29+ | a socket in a shared directory | the signer's uid and whoever runs the node | separate-user |
| Central | Linux containers | HTTPS (k8s_sa, mtls or token identity) |
whoever administers the signer's host and storage | remote |
| Compose by hand, over HTTPS | Docker Compose | HTTPS on a private network | whoever administers the Docker host | remote |
What each mode defends, and against whom: threat model: laptop (attackers A1–A4) and threat model: server (adversaries S1–S6). How to read the report a bundle gets: verdicts.
tracekit verify prints two lines (verdicts). Integrity is the same in every mode: a bundle whose
records were signed by the log key you pinned, in one unbroken chain covered by a checkpoint, reads VERIFIED.
Assurance depends on who vouches for the checkpoint, not on the mode:
-
dev: no pinned witness cosigned the checkpoint, or a run holds a self-approval. -
local: onlyoperator-class witnesses (ones you run yourself) cosigned it. -
witnessed: enough independent witnesses cosigned it, or a pinned Rekor anchor verified (witnesses). -
witnessed+monitored:witnessed, plus a fresh, conflict-free report of a monitor your trust config pins (monitor).
The mode shows up as each run's isolation (from the signer, never from the agent). So a system-mode signer with no
witness verifies as Assurance: dev with isolation: separate-user: the agent couldn't reach the keys, but nothing
outside the host shows the log wasn't rolled back. Add a witness the operator doesn't run to reach witnessed: your own or a partner's
(witnesses); the free public witness is not deployed yet.
Every mode is checked the same way: tracekit doctor (doctor) lists each check with its fix and exits 0
(all ok), 1 (a check failed) or 2 (warnings only). Doctor output is advice about the host, not evidence.
For trying Tracekit and catching mistakes, not attacks. Prerequisites: Python 3.9+.
pip install tracekit-ai
tracekit up --wait # optional: the first client starts it anyway
tracekit init --dev --v2 # optional: wire Claude Code's hooks to it
tracekit doctor # dev profile: a same-user signer and no witness are warningsThen follow a framework quickstart and export and verify a run:
tracekit signer trust -o trust.json
tracekit export --v2 --run <run id> -o run.tkb
tracekit verify run.tkb --trust trust.json- Protects against: mistakes, and an agent that follows its tools: the policy gate decides before each call, and every call is signed and chained by a process that holds the key.
- Doesn't protect against: an agent that runs commands as you. It can read the signer's key, stop the signer or rewrite its store (what dev assurance means).
-
Bundles reach:
Integrity: VERIFIED,Assurance: dev,isolation: same-user,approvals: self. A witness in signer.yaml raises the assurance level, never the isolation.
The signer runs as its own OS user from a root-owned install, so the agent's user can't reach its keys, store, policy
or approvals. Prerequisites: Linux with systemd (macOS: launchd, experimental, --experimental-macos), root, a
root-owned Python, and an agent user that is not root and not in a sudo, wheel, admin, docker or similar group.
sudo /usr/bin/python3 -m tracekit init --v2 --user AGENT --approver ADMIN \
--harness claude=/usr/local/bin/claude [--policy /etc/tracekit/policy.yaml]
sudo tracekit doctorWhat init --v2 sets up (system mode has the details):
- the root-owned venv
/opt/tracekit, thetracekit-signeruser and its 0700 data dir/var/lib/tracekit-signer, and the hardenedtracekit-signersystemd unit (no capabilities,ProtectSystem=strict, a system-call filter); -
/etc/tracekit/signer.yaml(root-owned): AGENT's uid in its own tenant, approvals only from ADMIN's uid (self-approval refused), the policy; - AGENT's v2 Claude Code hook, and
/etc/tracekit/client.jsonnaming the system socket, so dev auto-spawn is off and aTRACEKIT_SIGNERpointing elsewhere is refused; -
harness binding (
--harness [NAME=]PATH, repeatable; the binary must be root-owned): the signer accepts a run from AGENT's uid only from below that program's process tree, attested bytracekit-harness-helper, a root service that holds onlyCAP_SYS_PTRACE CAP_DAC_READ_SEARCH. Without--harnessthere is no binding; - the transcript tailer: the
tracekit-taileruser reads AGENT's Claude Code transcripts (the session's and its subagents') through an ACL and reports them to the signer, which lets that uid make onlymodel_event,state_write,tailer_lost,tailer_heartbeatandstatuscalls. An edited, truncated or replaced transcript, or a tailer that stops its heartbeats while the run is open (killed), becomes a signedtailer_lostgap. When the ACL or the sudo rule can't be set up, init says so and each run records atailer_lostgap instead.
Add witnesses on another host to signer.yaml (witnesses:, witnesses) and
restart the unit. Approve from ADMIN's account with tracekit approvals list and tracekit approvals approve <id>.
sudo tracekit uninstall --v2 removes it (--purge also deletes the keys and log).
-
Protects against: the agent's user reading or changing the keys, store, sequence numbers or policy, answering
its own approvals, or pointing the hook at a decoy signer (A1, A2;
eval/e8_insider_v2.pychecks these as real users). -
Doesn't protect against: root on the machine (A4: a witness on another host is the defence), the agent removing
the hook from its own
~/.claude/settings.jsonor acting outside Claude Code (no record, no gap), or a same-user process that starts the registered harness itself (what binding does not stop). -
Bundles reach:
isolation: separate-user, approvals by another user;Assurance: devwithout a witness,localwith your own,witnessedwith an independent one.
The signer image runs next to the agent in the same pod, as uid 10001. They share only the
socket directory; the agent is identified by its peer uid. Prerequisites: the image (built from
deploy/docker/Dockerfile, pinned by digest), Kubernetes 1.29+ for native sidecars, Helm 3, and a PVC whose root
uid 10001 can write.
docker build -f deploy/docker/Dockerfile -t tracekit-signer .
sh deploy/docker/smoke.sh # builds, runs, decides a call over the socket, runs doctor
helm install my-agent deploy/helm/tracekit-signer \
--set agent.image=registry.example/agent@sha256:... \
--set signer.image=ghcr.io/cygnux-labs/tracekit-signer@sha256:...
kubectl exec deploy/my-agent -c tracekit-signer -- tracekit doctor --config /etc/tracekit/signer.yaml
helm template my-agent deploy/helm/tracekit-signer --set agent.image=... --output-dir rendered
tracekit doctor --k8s --manifests rendered # the pod's isolation, from the manifestsThe chart's values, and what it refuses: Kubernetes. Witnesses go in signer.config.
Export from inside the signer container (tracekit export --v2 --config /etc/tracekit/signer.yaml ...) and verify
anywhere else.
-
Protects against: the agent's container reading or changing the signer's keys, log or config (S1, S2): they
are on volumes only the signer mounts, and doctor's
D-K8S-*checks flag a shared volume, a host path, a root container or cloud credentials in the pod. - Doesn't protect against: whoever administers the node or the PVC (S3: a witness detects rollback), and keys in a cloud KMS: workload identity gives the pod's credentials to every container, the agent's too, so a KMS log key belongs to the central signer only.
-
Bundles reach:
isolation: separate-user; assurance from the witnesses insigner.config.
One signer serves many agents over HTTPS; agents set TRACEKIT_SIGNER=https://signer.example:8443. The chart in
deploy/helm renders the sidecar only: run the same image as your own Deployment (one replica per data volume; the
signer holds its storage lock) with the central config below. Prerequisites: a TLS certificate, an identity source
(Kubernetes TokenReview, a SPIFFE CA for mTLS, or named tokens), and optionally Postgres
(storage: {postgres: {dsn_file}}), an AWS KMS log key (log_key: {aws_kms: {key_id, region}}) and a
record-key issuer.
# signer.yaml: central, agents identified by their Kubernetes service account
data_dir: /var/lib/tracekit-signer
durability: ack-on-fsync
http:
listen: 0.0.0.0:8443
cert: /etc/tracekit/tls/tls.crt
key: /etc/tracekit/tls/tls.key
authenticators: [k8s_sa]
k8s_sa:
audience: tracekit-signer
tokenreview: https://kubernetes.default.svc
token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
ca: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
tenants: {"k8s_sa:system:serviceaccount:acme:*": acme}
authorize: {"k8s_sa:system:serviceaccount:acme:*": [register_run, decide, approval_consume, complete, close_run,
model_event, approval_request, approval_wait, status]}
# + approval_list for the TypeScript OpenAI Agents and Vercel AI adapters
metrics: {listen: 0.0.0.0:9464, allow_remote: true}tracekit signer migrate --config signer.yaml # with storage: postgres, create the tables first
tracekit doctor --config signer.yaml # on the signer host; add --issuer-key KEY_ID with an issuer
tracekit signer serve --config signer.yaml
tracekit signer vkey --config signer.yaml > log.vkey # pin it in trust configs; give it to the monitor's host
tracekit monitor --log http://signer.internal:9464 --log-key "$(cat log.vkey)" --state /var/lib/tracekit-monitorRun the monitor on a host the operator doesn't control, with serve_records: true in metrics (monitor).
Remote agents outside Kubernetes use named tokens: remote clients. Metrics and alerts: observability. Model calls through the LLM gateway and tool calls run by a T2 executor are recorded out of the agent's process.
-
Protects against: everything the sidecar does, plus a compromised agent writing another workload's runs (S5:
identities, tenants and
authorizecome from the signer's config) and, with an independent witness and a monitor, the operator rewriting, forking or selectively exporting the log (S3, S4: detected, not prevented). - Doesn't protect against: an operator with no independent witness or monitor (detection needs both), and the cloud provider (S6, out of scope).
-
Bundles reach:
isolation: remote;witnessedwith an independent witness;witnessed+monitoredwith a pinned monitor's report (tracekit verify --monitor-report).
A ready-made stack ships in deploy/compose/ (the compose stack). The layout below builds the
same idea by hand: the signer and the agent as separate services on a private network, the agent with a token and the
CA only.
# signer.yaml: compose, agents identified by named tokens
data_dir: /var/lib/tracekit-signer
durability: ack-on-fsync
http:
listen: 0.0.0.0:8443
cert: /etc/tracekit/tls/tls.crt
key: /etc/tracekit/tls/tls.key
authenticators: [token]
tokens: /var/lib/tracekit-signer/tokens.json
tenants: {"token:*": default}
authorize: {"token:*": [register_run, decide, approval_consume, complete, close_run, model_event, approval_request, approval_wait,
state_write, status, observe, constraint_add]} # observe, constraint_add: the coding-agent hooks
# + approval_list for the TypeScript OpenAI Agents and Vercel AI adapters
metrics: {listen: 127.0.0.1:9464}# compose.yaml
services:
signer:
image: tracekit-signer@sha256:... # deploy/docker/Dockerfile
read_only: true
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
volumes:
- signer-data:/var/lib/tracekit-signer
- ./signer.yaml:/etc/tracekit/signer.yaml:ro
- ./tls:/etc/tracekit/tls:ro
networks: [tracekit]
agent:
image: registry.example/agent@sha256:...
user: "1000:1000"
environment:
TRACEKIT_SIGNER: https://signer:8443
TRACEKIT_SIGNER_TOKEN_FILE: /run/secrets/tracekit-token
TRACEKIT_SIGNER_CA: /run/secrets/signer-ca
secrets: [tracekit-token, signer-ca]
networks: [tracekit]
volumes:
signer-data: {}
networks:
tracekit: {internal: true}
secrets:
tracekit-token: {file: ./agent.token}
signer-ca: {file: ./tls/ca.pem}docker compose run --rm --entrypoint tracekit signer signer token add agent --config /etc/tracekit/signer.yaml > agent.token
docker compose up -d
docker compose exec signer tracekit doctor --config /etc/tracekit/signer.yamlThe certificate must name signer (the service name the agent connects to) and be readable by uid 10001. Never
mount signer-data, signer.yaml or the TLS key into the agent. For Postgres, add a postgres service on the same
network and storage: {postgres: {dsn_file: ...}}; for witnesses, run omniwitness or
litewitness on another host, not in this compose project, or they vouch for
nothing beyond local.
- Protects against: the same as central, for the agents on this host.
- Doesn't protect against: whoever administers the Docker host (root on it is the operator).
-
Bundles reach:
isolation: remote; assurance from the witnesses you add.
Records hold commitments only. To replay runs later (tracekit why), turn on the signer's
content vault in any of the setups above:
# signer.yaml (Helm: signer.content; compose: the content line tracekit deploy compose writes)
data_dir: /var/lib/tracekit-signer
content: {retain: true, retention_days: 30, max_record_bytes: 1048576}
authorize: {"mtls:spiffe://acme/investigator": [content_read, status]} # who may read it: never an agentOn Postgres, run tracekit signer migrate --config signer.yaml first: the vault adds two tables (schema version 2),
and a signer with the vault on refuses to start on an older store (upgrading).
migrate grants nothing: on an existing database, apply tracekit.storage.postgres.GRANTS to the signer's role
again after it, or every item reads back not_retained and the signer logs the failed write.
Key handling: the signer creates keys/content.key on its first start with the vault on, 0600 and owned by the
signer's user, next to its other keys. Back it up with them: without it the vault can't be read, and a restored
backup of the vault needs the key it was written under. Deleting it destroys every retained item at once. The vault
stays readable and erasable while the key exists, even after you turn retain off. tracekit doctor reports the
vault's state, its retention and whether the key is readable only by the signer (D-CONTENT-VAULT). Each read is a
signed content.accessed record and each erasure a content.erased record, in the tenant's tracekit/signer run.
Erasure reaches the live store only: a backup, WAL archive or replica taken before it still holds the run's wrapped key
and blobs, and opens with keys/content.key. Keep those shorter than retention_days
(limits).
Stop the signer, install the new version and start it again; records, keys and bundles carry over. A Postgres store
keeps its schema version until you run tracekit signer migrate, and a new version runs on the older schema as long
as it needs nothing the schema lacks:
-
1.1 adds the content vault's tables (schema version 2). A version 1 store keeps working for a signer with
content.retainoff and for every reader (export, view, reveal, the central viewer). Before turning the vault on, runtracekit signer migrate --config signer.yamlas the migration role, then applytracekit.storage.postgres.GRANTSto the signer's role again. A signer with the vault on refuses to start on a version 1 store, with an error that namestracekit signer migrate. -
1.3 speaks signer protocol 17. A 1.3 signer serves clients and gateways of 1.2 and 1.3, but a 1.3 client or
gateway refuses a 1.2 signer: upgrade the signer first, then the agents' Tracekit and the gateway. A bundle that
holds a
reconcile.result_mismatchrecord (verified tool results) needs a 1.3 verifier; every other bundle verifies with 1.2 too. A same-user dev signer from 1.2 is replaced by the first 1.3 client that finds it. -
1.4 speaks signer protocol 19. A 1.4 signer serves clients and gateways from protocol 15 up (1.2 and 1.3
included), but a 1.4 client or gateway refuses a 1.3 signer: upgrade the signer first. A bundle whose records use
1.4's additions (live constraints, signed findings that cite records, kernel sensor or OpenShell effects,
said_onlymodel messages, theextslot), or that holds a run exported in parts (past 64 MiB of records), needs a 1.4 verifier; an older one reports itUNVERIFIABLE (needs tracekit >= 1.4.0). A same-user dev signer from 1.3 is replaced by the first 1.4 client that finds it. The store's schema is unchanged.
Helm (tracekit-central): migrate each replica's schema before upgrading the release with signer.content.retain
set. Compose: the stack's init script runs only on an empty database, so on an existing stack run
docker compose exec signer tracekit signer migrate --config /etc/tracekit/signer.yaml with a DSN that may create
tables.
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