# Canary token formats (M11 ch3) `tirith canary` plants **honeytokens** — deliberately-synthetic, secret-shaped tokens you place where nothing legitimate should ever read them (a decoy `~/.aws/credentials`, a fake `.env`, a bait line in a private repo). tirith records each token locally; when that exact token later appears in a command you run, a paste, or a tool output tirith inspects, the engine fires `CanaryTokenTouched` (High). This document specifies the generated token shapes. The defining property is that every shape is **clearly synthetic**: it carries a literal, obviously-fake marker. That serves three goals: 1. **No accidental real-credential collision.** A canary must never resemble a genuine third-party credential closely enough to trip an external provider's abuse / take-down workflow when it surfaces in a log or a paste. 2. **Still matches tirith's own detection.** The shapes keep the recognizable provider prefix so the token reads as a secret to a human and to tirith. 3. **Human-recognizable.** A developer who notices the token can immediately tell it is a tirith canary, not a real leak. ## Shapes | kind | format | example (illustrative) | | -------------------- | --------------------------------------------------------- | ------------------------------------------------------- | | `aws-like` | `AKIA00CANARY` + 8 base32 chars | `AKIA00CANARYBMJY3PVA` | | `github-like` | `ghp_canary_` + 30 alphanumerics | `ghp_canary_ZDuktRyqJjF0YNEP1KbpI2wHBzCnXv` | | `gcp-like` | `AIzaCANARY` + 30 URL-safe chars | `AIzaCANARY8fK2…` (40 chars total) | | `env-line` | `TIRITH_CANARY_TOKEN=canary_` + 24 hex | `TIRITH_CANARY_TOKEN=canary_3f9a…` | | `private-key-shaped` | PEM block whose body is `TIRITHCANARY` + 52 base64 chars | `-----BEGIN TIRITH CANARY PRIVATE KEY----- …` | ### The `00CANARY` marker keeps the token clearly synthetic The `aws-like` token keeps the recognizable `AKIA` prefix so it reads as an AWS credential at a glance, then embeds a literal, obviously-fake `00CANARY` infix. That marker is the point: it makes the token visibly a tirith canary rather than a live secret, so anyone who inspects a flagged value — or a provider's abuse team, if one ever sees it — can tell it is bait and not a real leaked key. This sharply reduces the chance of a canary being mistaken for a genuine credential and triggering an unwanted takedown or rotation. This is a "clearly-labelled synthetic" property, not a mathematical guarantee: tirith does not claim the string is impossible to confuse with every real key, only that the explicit marker makes accidental confusion unlikely. The random suffix keeps the overall length and shape close to a real key so the token still looks credible as a planted decoy. The `ghp_canary_`, `AIzaCANARY`, `canary_`, and `TIRITHCANARY` markers play the same clearly-synthetic role for the other kinds. ## Randomness The random portion of every token is drawn from the OS CSPRNG (`getrandom`), with rejection sampling to avoid modulo bias, so tokens are unguessable. tirith does not depend on the `rand` crate at runtime for this (it is a dev-dependency only); the entropy source is the same one the anomaly-baseline salt uses. ## Storage Tokens live in a local-first JSONL store at `state_dir()/canaries.jsonl` (mode `0600` on Unix), one `{id, token, kind, created_at, callback_url?}` object per line. `tirith canary list` shows them; `prune` / `rotate` manage them. ## Detection scope Detection is a **store lookup**, not a shape match: ONLY tokens you registered fire `CanaryTokenTouched`. An unrelated, genuine AWS key in a paste fires the existing `CredentialInText` / `HighEntropySecret` rules instead — never the canary rule, because that key is not in your store. ## Local-first, opt-in callback (D3) By default a canary is **local-only**: a hit raises the finding and writes to the local audit log; nothing leaves the machine. A canary MAY be created with `--callback-url `, where `` is an endpoint **you self-host**. On detection (and only on detection) tirith sends one best-effort POST of `{kind, detected_at, context}` — **never the token value** — to that URL. There is no tirith-operated endpoint. A callback failure is logged to the audit log and never blocks the verdict. This is the single exception to tirith's no-network rule, and it is gated entirely behind your explicit `--callback-url`.