Repository navigation
Signing
Experimental: this works and is tested, but may change and gets less hardening than the stable core. This page is about the v1 signer,
tracekitd; the v2 signer's keys: evidence format.
| Mode | Command | Key | Runs as |
|---|---|---|---|
| System (Linux; macOS experimental) |
sudo tracekit init --user <agent-user> (macOS: add --experimental-macos) |
/var/lib/tracekit/keys/signer.key (0600, dir 0700) |
OS user tracekit under systemd (macOS: _tracekit under launchd) |
| Dev (any OS) | tracekit init --dev |
~/.tracekit-signer/keys/signer.key |
your own user. Bundles record signer_isolation: same-user and the verifier warns
|
The public key is keys/signer.pub; it is copied into every bundle. The key id is ed25519: + the first 16 hex characters of sha256(pubkey).
Why a separate OS user and not the OS keychain. A keychain protects the key's bytes, but it signs whatever a process in the user's session asks it to. An agent running in that session could ask it to sign forged or rewritten records, and could still delete or rewrite the ledger file. A separate user owns both the key and the ledger, so the agent's user can only append through the socket and can never rewrite what is there.
Why system mode needs kernel peer credentials. The signer identifies each caller with SO_PEERCRED (Linux) or LOCAL_PEERCRED and LOCAL_PEERPID (macOS): that is how it attests os_user, refuses source=proxy events from anyone but its own user, and checks approvers. System-mode installation is refused on platforms without them (Windows), and on macOS it needs --experimental-macos until it is validated on hardware (platforms). Where there are no peer credentials, dev mode uses an authenticated loopback TCP transport, but caller identity remains unattested and proxy events and approvals are explicitly untrusted. A manually configured Unix socket is refused when peer credentials are unavailable.
tracekit init --dev --signer-cmd "/usr/local/bin/tk-hsm-signer --slot 0" --signer-pub hsm-signer.pub --key-assurance hsmWith an external signer the private key never enters tracekitd: a helper process (TPM, HSM, smart card, enclave, KMS)
signs. The protocol is one line per request on the helper's stdin, sign <hex>, answered with ok <hex signature> or
err <reason>; the helper is started once and kept running. Every signature is verified against the configured public
key before use, so a helper that signs with another key or returns garbage stops the signer instead of corrupting the
ledger, and a failed signature is reported to clients as a retryable write failure with the ledger unchanged.
examples/ext_signer.py is a reference helper; replace its two marked lines with calls to your device.
--key-assurance (tpm, hsm, tee, kms, smartcard) is signed into every checkpoint and shown by tracekit verify as the
signing key check; a file key is reported as a warning. The assurance is the operator's statement: Tracekit cannot
attest where a key lives, so pair it with the device's own attestation where it offers one. Re-running init keeps a
configured external signer (dropping it would silently switch to a new file key and fork the chain).
--key-attestation FILE attaches that attestation: a TPM quote, an enclave (Nitro, TDX, SEV-SNP) attestation document,
or a KMS key's metadata export, up to 1 MiB. Its SHA-256 is signed into every checkpoint, tracekit export puts the
document in the bundle as attestation/<sha256>.bin, and verify reports it under signing key, warning if a
checkpoint names a document the bundle does not carry. Tracekit checks only that it is the document the checkpoints
name. Whether it is genuine and binds this public key is checked with the vendor's tools (certificate chain, PCR or
measurement values, the key in the user data); the run and the evidence for it now travel together.
tracekit init --dev --signer-cmd "/usr/local/bin/tk-nitro-signer" --signer-pub /etc/tracekit/signer.pub \
--key-assurance tee --key-attestation /etc/tracekit/nitro-attestation.cborSigning and key generation use the cryptography package (a required dependency; constant-time, widely reviewed). Verification uses it too when installed, and otherwise a small pure-Python RFC 8032 verifier, so tracekit verify runs with only the standard library. Verification handles public data only, so the fallback's lack of timing hardening does not matter there. Tracekit never signs with the pure-Python code; it is kept only to check the RFC 8032 test vectors.
{"v": 1, "event": {...}, "hash": "<sha256(canon(event))>", "sig": "<b64 Ed25519>", "kid": "ed25519:…"}-
canon= JSON with sorted keys, compact separators, UTF-8 (no ASCII escaping). - The signature covers
canon({"hash": H, "prev_hash": P, "seq": N}), not the event body. That lets a bundle elide records from other runs (keeping onlyseq,hash,prev_hash,sig) while the verifier still checks the full chain and counter.
The socket accepts status, append, checkpoint (adds a checkpoint, nothing else) and the approval operations. There is no delete, rewrite or key export. On append it:
- validates the event against
tracekit/schema/tracekit.event.v1.json; failures are rejected and recorded as anerrorevent; - refuses
source: signerfrom any client; system mode acceptssource: proxyonly from its own OS user, while dev mode's authenticated local transport is explicitly same-user and untrusted; - sets
seq,prev_hashandts_signeditself (the client cannot choose them), and anidif the client sent none; - on
run.start, replacesos_userwith the caller's user fromSO_PEERCREDand setsos_user_attested: true; - checks the per-run, per-stream client counter and writes
capture.gapon a jump, a regression, events beforerun.startor afterrun.end; - stores any attached policy under its hash, after checking it matches the hash the event claims (
run.start.policy.hash, orpolicy.decision.policy_hashwhen the policy changed mid-run); a decision naming a policy that was never recorded gets acapture.gap(policy_unrecorded); - compares the attached transcript mark with the previous one and writes
trace.tamperon deletion, truncation or edits; - appends under an exclusive lock, fsyncs, and checkpoints every N records and at
run.end.
It cannot tell a real event from a well-formed fake. Anything that can write to the socket as the agent's user can submit plausible events and they will be signed. Ordering, counters and schema checks catch sloppy forgeries; independent capture paths (proxy vs hook cross-check) are what catch careful ones.
Generic SDK events are agent-reported. The signer records source: sdk and can attest the connecting OS user when peer credentials are available, but it cannot verify which framework produced the event or whether every tool call was wrapped. An SDK integration that omits a call creates no event; policy enforcement applies only to calls routed through Tracer.tool().
(levels as in threat-model-laptop.md)
| Level | A valid signature + a checkpoint on an independent witness means |
|---|---|
| A1 compromised agent / A2 compromised hook / A3 compromised user account (system mode) | tracekitd accepted this record from a caller with the recorded OS user, at this position, and nobody has changed, removed or reordered it since. It does not mean the event is true: the agent's user could have sent it. A tool call backed by a matching proxy-recorded model exchange is much stronger evidence than one without |
| Same, dev mode (signer is the agent's user) | Only that the file has not changed since signing by someone without the key. The agent could have held the key, so it proves little |
| A4 root | Records up to the latest witnessed checkpoint are unchanged. Later records, or runs with no checkpoint, could have been fabricated with the real key |
| Harness itself lying | Nothing about whether the harness told the truth |
-
Create: at
init, as the signer user. Never printed or sent anywhere. -
Distribute: publish
signer.pub(or its key id) where reviewers can get it independently of any bundle, e.g. in the witness repository's README. Reviewers pin it withtracekit verify --key <signer.pub>; without a pinned key or an independent witness the verifier reports the bundle asUNANCHORED. -
Rotate: stop the service, move
keys/aside, restart. A newkidappears in checkpoints; old bundles still verify against the old public key. Keep and publish old public keys so a verifier can tell a rotation from a forgery. - Revoke: publish the revoked key id and the last checkpoint seq you trust for it (commit it to the witness repository). Treat any record signed by that key after that checkpoint as untrusted. The v1 signer has no online revocation check; the verifier trusts exactly the key you pin.
- Backup: optional; losing the key does not affect existing bundles.
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