Repository navigation
Issuer
Experimental: this works and is tested, but may change and gets less hardening than the stable core.
A v2 signer signs records with a record key. By default that key is a file, keys/record.key, pinned for the life of
the log. With an issuer, the signer instead signs with short-lived record keys. A separate service certifies each
key and holds the CA key; the signer never does. Every certificate is published to a witness before the signer gets
it back, so a certificate the issuer handed out in secret can't verify.
signer ──(new key + proof of possession)──▶ issuer ──(certificate leaf)──▶ issuance log ──▶ witness cosigns
signer ◀──(certificate, inclusion proof, cosigned checkpoint)── issuer
signer: signer.epoch{keys: [{kid, alg, spki, cert}]} then key.retire{previous kid, last_seq}
A certificate is a small signed JSON document, not X.509 (tracekit/format/cert.py):
{"cert": {"kid": "sha256:…", "spki": "<base64 DER>", "log_id": "…", "tenants": ["acme"],
"not_before": 1791547200, "not_after": 1791633600, "serial": "<hex>"},
"sig": "<base64 Ed25519 by the CA key over {…cert, \"t\": \"tracekit.cert.v2\"}>"}Times are unix seconds. not_before is set BACKDATE_S (300 s) in the past, so a signer whose clock runs a little
behind the issuer's still signs inside the validity window. A revocation is {"revocation": {"serial", "last_seq"?}, "sig"}, signed by the CA key under the tracekit.retire.v2 tag. Without last_seq it covers every record of the key.
With last_seq, it covers only the records after that seq.
Each certificate and each revocation is a leaf of the issuer's issuance log. The leaf data is SHA-256 of the
document's canonical JSON. The log is a C2SP checkpointed RFC 6962 tree like the signer's, signed by its own log key
(not the CA key). The issuer answers a request with the issuance entry
{certificate, index, inclusion, checkpoint}, and only once at least one configured witness has cosigned a
checkpoint that includes the leaf. If no witness cosigns, the request fails with unavailable and nothing is
returned. The leaf stays in the log, and the next checkpoint covers it.
tracekit issuer serve --config issuer.yaml
tracekit issuer vkey --config issuer.yaml # {"vkey", "issuance_log_vkey"}: the trust config's issuers entry
tracekit issuer revoke SERIAL [--last-seq N] --config issuer.yaml
data_dir: /var/lib/tracekit-issuer # keys/ca.key, keys/log.key, issuance.jsonl
http: {listen: 0.0.0.0:8444, cert: tls/issuer.pem, key: tls/issuer.key,
client_ca: {acme.org: tls/acme-ca.pem}, authenticators: [mtls, k8s_sa]}
name: issuer.example.org # the CA key's vkey name
origin: issuer.example.org/issuance # the issuance log's origin
ca_key: {aws_kms: {key_id: alias/tracekit-ca, region: eu-west-1}} # default keys/ca.key
log_key: {aws_kms: {key_id: alias/tracekit-issuance, region: eu-west-1}} # default keys/log.key
witnesses:
- {url: https://witness.example.org, vkey: "witness.example.org/w1+1234abcd+BA..."}
identities: # identity or prefix:* -> scope
"mtls:spiffe://acme.org/signer": {log_ids: ["3f2a…"], tenants: [acme], max_ttl_s: 86400}The http section is the signer's (mTLS, k8s_sa or bearer-token identities; see tracekit/transport/http.py).
Every request is checked against the caller's scope: the log_id must be listed (or the list must be ["*"]), every
requested tenant must be listed, and ttl_s must be in 1..max_ttl_s. A caller with no entry gets nothing. A request
must also carry a proof of possession: the requested key's signature over cert.pop_message(spki, log_id, tenants, ttl_s). Any failed check is answered forbidden (invalid_request for a malformed request).
A new signer's log_id is random and stays unknown until the log's first record is written. Start such a signer under
log_ids: ["*"], read its log_id from its first record or its default origin tracekit.local/<log_id>, then
narrow the scope.
Keep the CA key where the signer's credentials can't reach it. In AWS, grant kms:Sign on the CA key to the issuer's
role only, never to the signer's.
record_key:
issuer:
url: https://issuer.internal:8444
vkey: "issuer.example.org+…" # from `tracekit issuer vkey`
issuance_log_vkey: "issuer.example.org/issuance+…"
tenants: [acme] # every tenant this signer writes records for
ttl_s: 86400
cert: tls/signer.pem # mTLS client certificate (or token_file: a bearer token)
key: tls/signer.key
ca: tls/issuer-ca.pemAt every start, and once two thirds of the current certificate's validity have passed, the signer:
- generates a fresh record key in memory (it is never written to disk);
- gets the key certified, and checks the answer against the pinned
vkeyandissuance_log_vkey; - writes a
signer.epochthat carries the issuance entry, signed by the new key, followed bykey.retireof the previous key withlast_seqset to the record before the epoch.
If the issuer can't be reached at start, the signer does not start. If renewal fails, the signer retries every 60 s
and keeps signing with the current key. Records written after that key's not_after fail verification.
tracekit signer fsck checks each record against the certified keys the log declared. Without record_key, the
pinned file key path is unchanged.
Pin the issuer in the trust config:
{"logs": ["…"], "witnesses": [{"vkey": "witness.example.org/w1+…", "class": "customer"}], "algs": ["ed25519"],
"issuers": [{"vkey": "issuer.example.org+…", "issuance_log_vkey": "issuer.example.org/issuance+…"}]}A key whose signer.epoch entry has a cert is declared only when all of these hold: the certificate is signed by a
pinned issuer; it names that key and the log's log_id; it is included in that issuer's issuance log at a checkpoint
signed by the issuance log key and cosigned by max(1, witnesses_required) pinned witnesses. If any of these fails,
keys fails, and the key's records fail signatures with unknown kid. Each record under a certified key must also
have its ts inside the certificate's validity window, and its tenant among the certificate's tenants. The signer's
own records are exempt from the tenant check. The key assurance line then reads certified: n of m record key(s).
Revocations come from the verifier's side, never from the bundle:
tracekit verify run.tkb --trust trust.json --revocations issuance.jsonl
A --revocations file is JSON lines. The issuer's issuance.jsonl works as is; lines that are not revocations are
ignored, and a revocation no pinned issuer signed is a warning. Records of a revoked key after the revocation's
last_seq make the verdict UNVERIFIABLE (key revoked) (exit 2), not FAILED, unless another check fails.
All limits: limits.md.
-
tracekit monitorstill flags eachsigner.epochafter seq 0 as a key announcement it was not told about. A rotating signer's kids can't be listed in advance with--allow KID, so expect those conflicts until the monitor can pin issuers. - The issuer handles one issuance at a time and re-reads
issuance.jsonlfor each one. That is fine for record-key volumes; past ~100k certificates it needs an index and tiles. - The verifier does not fetch revocations itself; pass the issuer's log, or a copy of it, with
--revocations.
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