grithdocs

Canary tokens

Fake secrets that prove exfiltration when they show up where they shouldn't.

A canary token is a fake credential, deliberately seeded somewhere an attacker might read it, that triggers an alarm when it shows up where it shouldn't. Banks have used them for decades; honeypot tokens, canary URLs, and fake AWS keys are modern descendants.

In grith, canaries are a hard gate. Any registered canary token that appears in an outbound payload — network send, shell argument, file write outside the zone — denies the call and raises an exfiltration alert. It bypasses scoring entirely.

Why a hard gate

The whole filter pipeline is probabilistic about most things. "This looks like an API key" is a heuristic. "This destination is unusual" is a heuristic.

Canary detection is not a heuristic. It's a direct match against a token that the operator put in the filesystem and explicitly told grith was a canary. If that exact string is going out, something went wrong — and the right answer is to deny the call, not to score it.

That's why filter 11 returns DENY rather than a score contribution. There is no amount of trust, reputation, or routine-path-discount that lets a canary token escape.

Adding a canary

grith canary add --label "aws-canary" --value "AKIACANARY0EXAMPLE0NOTREAL"

This:

  1. Registers the value in the canary detection set (keyed by a generated id and your label).
  2. Prints the id, label, and value so you can record them.

grith canary add does not write any file. To turn a registered value into a decoy, copy it into a plausible location yourself — for example a fake ~/.aws/canary_creds. Now, if any supervised process reads that file AND tries to send the contents out, the canary filter triggers DENY and records a high-severity audit event.

Supplying the value:

  • --value "<literal>" — register an exact string you provide. Use this to make a convincingly shaped decoy (an AWS-key-shaped string, a ghp_-prefixed token, an xoxb- Slack token, etc.).
  • --generate — register a random 48-character hex value instead. Good for low-context places where the shape does not matter.

Listing and rotating

grith canary list
grith canary rotate <id> --generate
grith canary remove <id>

Rotation replaces a canary's value while keeping its label and updates the registration; supply the new value with --generate or --value <VALUE>. Use this if you suspect a canary has been leaked (showed up in a paste, in a public log, etc.) — rotate to invalidate the old value while keeping the trap intact. If you placed the old value in a decoy file, update that file with the new value it prints.

Where to place canaries

The point of a canary is to be plausibly attractive to an attacker scanning the filesystem. Good placements:

  • ~/.aws/credentials_backup — looks like a careless backup.
  • ~/secrets.txt — looks like sloppy ops hygiene.
  • Inside repos in test fixtures (test/fixtures/.env).
  • Project root .env.production.bak — looks like a leftover.

Bad placements (will never trip):

  • Anywhere the supervised agent doesn't have a reason to read.
  • Inside ~/.config/grith/ — agents don't look there.
  • Read-only system paths the agent will skip.

The dashboard suggests good placements based on what your agents typically read.

When a canary fires

A canary detection raises a high-severity audit event with:

  • The canary that fired (id, label, format).
  • The session that tried to send it.
  • The destination it was being sent to.
  • The full filter pipeline output (every other filter that fired, for context).

The call is denied. The originating process is not automatically killed by default — but the dashboard / digest offers a one-keystroke t (terminate) for that session.

If notification channels are configured, a canary fire is the kind of event that should page someone. The default Slack / PagerDuty mapping pages on canary fires regardless of other rate-limit rules.

See also

Last updated: 2026-05-14Edit this page on GitHub →