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:
- Registers the value in the canary detection set (keyed by a generated id and your label).
- 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, aghp_-prefixed token, anxoxb-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
- Filter 11: Canary detection — config reference
- Setting up canary tokens — practical walkthrough
- grith canary — CLI reference
- Exfiltration walkthrough — start-to-finish worked attack