Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions docs/doctor.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
sudo /opt/tracekit/bin/python -I -m tracekit doctor # system mode: run it as root, so it can probe as the agent's user
tracekit doctor # the v2 dev signer
tracekit doctor --config signer.yaml # one v2 signer, by its config
tracekit doctor --json # [{"id", "status": "ok" | "warn" | "fail", "detail", "fix"}]
tracekit doctor --json # [{"id", "status": "ok" | "info" | "warn" | "fail", "detail", "fix"}]
tracekit doctor --k8s # inside a pod: the pod and its service account, read from the API server
tracekit doctor --k8s --manifests DIR # rendered manifests (helm template, kustomize build) instead
tracekit doctor --config signer.yaml --issuer-key alias/tracekit-ca # also: may this principal sign with the CA key?
Expand All @@ -20,7 +20,7 @@ Doctor works out which setup it's looking at and runs the checks that apply:
- **`--k8s`:** the Kubernetes checks below, of the pod doctor runs in (it needs `get` on its pod and service account) or
of the manifests under `--manifests DIR`. With `--config` it runs that signer's checks too.

Exit codes: 1 when any check fails, else 0. A warning is advice: it doesn't fail doctor.
Exit codes: 1 when any check fails, else 0. A warning is advice: it doesn't fail doctor. `info` says what a setup doesn't have and needn't (a witness for the dev signer, which uses the public witness by default and has none only until a release names it or after `--no-witness`; Claude Code's hooks where there is no Claude Code; before the dev signer's first start, the keys and report that start makes: `D-CONTENT-VAULT`, `D-KEYS-DISTINCT`, `D-KEY-HYGIENE`). A dev run whose only warning is `D-PROCESS-BOUNDARY` ends with `dev setup OK`.

**Doctor output is advice, not evidence.** It describes the host at the moment you run it. Nothing it prints is signed,
and a verifier never reads it. What a bundle proves comes from its signatures, its checkpoints and the verifier's own
Expand All @@ -42,16 +42,16 @@ skipped with the reason) and checks that doctor flags each one. It also checks t
| `D-AGENT-PROBE` | the agent's user's actual access | as that user (doctor forks and drops to its uid), it can read or write the data dir, the keys dir or a key, or write signer.yaml, the policy or `client.json` (warn: doctor isn't running as root or the agent) | `chmod o-rwx`, `go-w`; owner root or the signer's user |
| `D-CODE-TRUST` | the root-owned venv `/opt/tracekit` | a file in it, or a directory above it, isn't root-owned or is group- or world-writable, or the venv is missing | re-run init to reinstall it |
| `D-KEYS-MODE` | `data_dir` and `data_dir/keys` | either one isn't 0700, or a key isn't a 0600 file owned by the keys dir's owner (warn: doctor can't read them, so run it as root) | `chmod 700` the dirs, `chmod 600` the keys |
| `D-CONTENT-VAULT` | `content` in signer.yaml and `keys/content.key` | the `content` section isn't valid (as the signer checks it), or the vault is on and its key isn't a 0600 file of the signer's user (warn: no key yet, or doctor can't read it; the vault off while runs kept before are still retained: "off, N runs still retained"). The detail says whether the vault is on and its retention | fix `content`; `chmod 600`, chown to the signer's user; erase the retained runs or keep them |
| `D-KEYS-DISTINCT` | `keys/log.key` and `keys/record.key` | they're the same key (warn: none yet); ok when `log_key` names a KMS key | move both away and restart the signer: new keys start a new log |
| `D-CONTENT-VAULT` | `content` in signer.yaml and `keys/content.key` | the `content` section isn't valid (as the signer checks it), or the vault is on and its key isn't a 0600 file of the signer's user (warn: no key yet, or doctor can't read it; dev: info before the dev signer's first start; the vault off while runs kept before are still retained: "off, N runs still retained"). The detail says whether the vault is on and its retention | fix `content`; `chmod 600`, chown to the signer's user; erase the retained runs or keep them |
| `D-KEYS-DISTINCT` | `keys/log.key` and `keys/record.key` | they're the same key (warn: none yet; dev: info before the dev signer's first start); ok when `log_key` names a KMS key | move both away and restart the signer: new keys start a new log |
| `D-KMS-LOG-KEY` | `log_key: {aws_kms}`, with this host's AWS credentials | the key isn't an `ECC_NIST_EDWARDS25519` `SIGN_VERIFY` key (warn: not checked, without boto3, credentials or access) | create the log key with that spec |
| `D-KMS-ISSUER-SIGN` | `--issuer-key KEY_ID`, the record key issuer's CA key | this principal may call `kms:Sign` on it (a `DryRun` Sign succeeds); only the issuer may (warn: the answer was neither allowed nor denied) | remove `kms:Sign` on it from the signer's IAM role |
| `D-PG-SIGNER-ROLE` | `storage: {postgres}`: the role of `dsn_file`'s DSN | it is superuser or has UPDATE, DELETE or TRUNCATE on `tracekit_records`, `_registry`, `_notes` or `_anchors` (warn: not checked, without psycopg or a connection) | grant it only `tracekit.storage.postgres.GRANTS` |
| `D-PG-READER-ROLES` | every other role | one that is neither superuser nor the tables' owner can INSERT, UPDATE, DELETE or TRUNCATE a log table | give readers SELECT only |
| `D-KEY-HYGIENE` | `data_dir/hygiene.json`, written by the signer on start | the signer process may dump core (RLIMIT_CORE not 0), is dumpable (Linux `PR_SET_DUMPABLE`), or could not mlock its key files (warn; also when the signer never started) | run it with `tracekit signer serve`; raise `LimitMEMLOCK` |
| `D-KEY-HYGIENE` | `data_dir/hygiene.json`, written by the signer on start | the signer process may dump core (RLIMIT_CORE not 0), is dumpable (Linux `PR_SET_DUMPABLE`), or could not mlock its key files (warn; also when the signer never started, dev: info before its first start) | run it with `tracekit signer serve`; raise `LimitMEMLOCK` |
| `D-UNIT-HARDENING` | `tracekit-signer.service` (or the launchd plist) | a hardening directive that init writes is missing: the detail gives the score and lists the missing directives (warn) | re-run init to rewrite the service file |
| `D-HARNESS-HELPER` | `tracekit-harness-helper.service` (or its launchd plist), when signer.yaml has `harness_binding` | the file is missing, or its capability bounding set is anything but `CAP_SYS_PTRACE CAP_DAC_READ_SEARCH` | re-run init with `--harness` |
| `D-HOOKS-PRESENT` | the agent's Claude Code settings, and whether they enable the Tracekit plugin (`enabledPlugins`, whose hooks count); Codex's, Gemini CLI's, Cursor's, goose's, Qwen Code's, Factory Droid's, Antigravity CLI's and Crush's v2 hooks and OpenCode's v2 plugin count too | a tool event (PreToolUse, PostToolUse, PostToolUseFailure) has no v2 hook, the plugin isn't enabled and no other coding agent has v2 hooks (not when `tracekit init --v2 --no-hooks`, dev or system mode, recorded that you chose none: the check passes and says how to add them); or the plugin and init's hooks both run, so every approved call is blocked; or (warn) with `init --v2 --managed`, the agent user's own `~/.claude/settings.json` has Tracekit's v2 hooks too, so every call is decided twice (not with `allowManagedHooksOnly` on, which stops them running: the check says so and passes) | re-run init; with both, keep one (`tracekit uninstall`, or `/plugin disable` with the plugin key the check names, e.g. `tracekit@tracekit`); with `--managed`, re-run init with `--managed`, which takes them out of the agent user's file |
| `D-HOOKS-PRESENT` | the agent's Claude Code settings, and whether they enable the Tracekit plugin (`enabledPlugins`, whose hooks count); Codex's, Gemini CLI's, Cursor's, goose's, Qwen Code's, Factory Droid's, Antigravity CLI's and Crush's v2 hooks and OpenCode's v2 plugin count too | a tool event (PreToolUse, PostToolUse, PostToolUseFailure) has no v2 hook, the plugin isn't enabled and no other coding agent has v2 hooks (not when `tracekit init --v2 --no-hooks`, dev or system mode, recorded that you chose none: the check passes and says how to add them; dev: info when there is no Claude Code, no `claude` on PATH and no `~/.claude`); or the plugin and init's hooks both run, so every approved call is blocked; or (warn) with `init --v2 --managed`, the agent user's own `~/.claude/settings.json` has Tracekit's v2 hooks too, so every call is decided twice (not with `allowManagedHooksOnly` on, which stops them running: the check says so and passes) | re-run init; with both, keep one (`tracekit uninstall`, or `/plugin disable` with the plugin key the check names, e.g. `tracekit@tracekit`); with `--managed`, re-run init with `--managed`, which takes them out of the agent user's file |
| `D-HOOKS-MANAGED` | with `init --v2 --managed`: Claude Code's admin-managed settings file, which holds the hooks | the file, or a directory above it, isn't root-owned or is group- or world-writable, so the agent's user could replace it; or (as root) the agent's user can't read it, so its Claude Code starts without it | `sudo chown 0:0` and `sudo chmod 644` the file, `sudo chmod go-w` the directories above it; or re-run init with `--managed` (and `--managed-only` when an init with it turned `allowManagedHooksOnly` on) |
| `D-HOOKS-EVENT` | Claude Code, Codex, Cursor, Gemini CLI, goose, Qwen Code, Factory Droid, Antigravity CLI and Crush hook commands (OpenCode's JavaScript plugin isn't checked); for Claude Code also each Tracekit hook's `matcher` and `disableAllHooks` in the settings files doctor reads (dev mode: the user file, the file init recorded, the working directory's project and local files, and managed settings; system mode: the agent user's file, the file init wrote and managed settings; with `--managed`, managed settings only, as only they can turn managed hooks off) | a hook command doesn't name its configured event (installed before 1.2), so a pre-tool payload the hook can't read lets the call through unrecorded; a hook other than the pre-tool one blocks its event when it can't run (a command init wrote before 1.3, or a Cursor hook with `failClosed` on that event, as init wrote up to 1.4.2), so a broken install blocks prompts and Stop too; or, for the other coding agents, a call can run that no hook decided: no Tracekit v2 hook is on the pre-tool event, Cursor's `preToolUse` hook has no `failClosed: true`, or goose's `PreToolUse` hook has no `on_failure: "block"`; or, for Claude Code, a call can run that no hook decided: `disableAllHooks` is set in one of those files, so no hook runs (fail in production), a Tracekit hook's matcher isn't `*`, empty or absent, so it skips the other tools, or the PreToolUse command isn't wrapped to block the call when the hook can't run (POSIX) (warn) | re-run init for the active mode: `tracekit init --dev --v2 [--agent NAME]` in dev, or `sudo /opt/tracekit/bin/python -I -m tracekit init --v2 --user AGENT [--agent NAME]` in system mode (with Python 3.9 or 3.10, [laptop system mode](deploy.md#laptop-system-mode)'s command); remove `disableAllHooks` from the file the check names |
| `D-HOOKS-VENV` | the hook commands | a hook isn't the command init writes for the root-owned venv: another Python, or written by init before 1.3 | re-run init |
Expand All @@ -69,7 +69,7 @@ skipped with the reason) and checks that doctor flags each one. It also checks t
| `D-POLICY-ENGINE` | the policy, compiled by policy2 | it doesn't compile, or neither `google-re2` nor `regex` is installed | `tracekit policy lint FILE`; `pip install tracekit-ai` (a `--no-deps` install leaves the engine out) |
| `D-DURABILITY` | `durability` | it's `ack-on-write` in a production profile, so a power loss can drop records that were already acknowledged (warn) | `durability: ack-on-fsync` |
| `D-FAIL-MODES` | `fail_modes` and `client.json` `fail_mode` | a tool class, or the system config, fails open (warn) | set them to `closed` unless an outage must not stop the agent |
| `D-WITNESS-CONFIGURED` | `witnesses` | there are none, so checkpoints aren't cosigned and assurance stays `dev` (`local` when `anchors` is set) (dev: warn) | add a witness (`docs/witnesses.md`) |
| `D-WITNESS-CONFIGURED` | `witnesses` | there are none, so checkpoints aren't cosigned and assurance stays `dev` (`local` when `anchors` is set) (dev: info: the dev signer uses the public witness by default, so it has none only until a release names it or after `init --dev --v2 --no-witness`) | add a witness (`docs/witnesses.md`) |
| `D-WITNESS-OWNERSHIP` | the witnesses' ownership classes | every witness is `operator`: the default production profile needs at least one `public`, `customer` or `tracekit` witness (dev: warn) | add a non-operator witness |
| `D-WITNESS-FRESH` | `store/witness-queue.json` against the latest checkpoint | a witness has never cosigned the latest checkpoint, or has been failing for longer than the witness gap window (300 s) (warn) | check the witness is reachable and accepts this log |
| `D-CLOCK-SKEW` | this clock against the latest cosignature timestamp | this clock is more than 300 s behind it (warn) | sync the clock (NTP) |
Expand Down
2 changes: 1 addition & 1 deletion docs/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Another agent: record its calls with the SDK's run handle ([custom agent](quicks

| Agent | Status | Guide |
|---|---|---|
| Claude Code hooks (`tracekit init --dev --v2`) | Stable | [quickstart](quickstart-v2.md) |
| Claude Code hooks (`tracekit init --dev --v2`) | Stable | [quickstart](quickstarts/claude-code.md) |
| The Claude Code plugin | Experimental | [plugin](../plugin/README.md) |
| Codex CLI, Cursor, Gemini CLI, OpenCode | Experimental | [coding agents](coding-agents.md) |
| goose (`tracekit init --dev --v2 --agent goose`) | Experimental | [coding agents](coding-agents.md#on-the-v2-signer) |
Expand Down
2 changes: 1 addition & 1 deletion docs/portability.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Things to check on first use, because they depend on the machine:

- The Python interpreter and the `tracekit` package must be readable by `_tracekit`. A Python under your home
directory is not, and the daemon will fail to start; look at `/var/lib/tracekit/tracekitd.log`.
- `tracekit status` should show `signer_isolation: separate-user` and the ledger owned by `_tracekit`.
- `tracekit status --json` should show `signer_isolation: separate-user` and the ledger owned by `_tracekit`.
- A quick isolation test: `echo x >> /var/lib/tracekit/ledger/ledger.jsonl` as your own user must fail.

If any of that is wrong, please open an issue with the output. Until it has been validated on hardware the
Expand Down
Loading
Loading