grithdocs

Setting up canary tokens

The only registration that survives a restart, what detection actually covers, and how to verify it fires.

A canary token is a fake credential you place somewhere an attacker would look. If it ever appears in an outbound call, grith denies that call outright. Read Canary tokens for the concept; this page is the deployment, and it starts with the part that trips everyone up.

grith canary add does not persist

The canary registry lives in process memory. The CLI mints a token into its own copy, prints it, and exits - the daemon never sees it, and the next command starts from nothing.

terminal
$ grith canary add --label aws-prod-decoy --generate
Added canary:
id:    89a62c8b-9a6e-4681-8dca-fb0c8ba63a8d
label: aws-prod-decoy
value: d0c027066819492fa41f179ecb761a5b8bda51cdf60e568b
$ grith canary list
No canary tokens registered.

remove and rotate behave the same way. Treat the whole grith canary command as a value generator, not a store.

The registration that survives

The daemon seeds its registry at startup from config/filters/canary.toml, resolved relative to the directory the daemon was started in, falling back to the copy compiled into the binary. Write the file there and restart.

config/filters/canary.toml

toml
[canary]
enabled = true

[[canary.tokens]]
id = "9f1d0c2a-4f4b-4a1e-9f0e-3b7a6c5d4e21"
label = "aws-prod-decoy"
value = "grith-canary-7f3a1c9e2b4d"
terminal
$ grith daemon restart

Any id you like, as long as it is unique. The value is the string that must never appear in outbound traffic - use grith canary add --generate to mint one, or write your own if you want it shaped like a credential the attacker is hunting for.

Planting it is your job

grith registers values. It never writes a decoy file anywhere. Copy the value into somewhere plausibly attractive and plausibly careless:

PathWhy it reads as real
~/.aws/credentials_backupOps hygiene that slipped
~/.env.backupSame
repo/test/fixtures/.envA careless test fixture
repo/.env.production.bakLeftover from a deploy

Three to five is plenty, each in a different category of target. Do not bother placing one inside ~/.config/grith/ - nothing looks there.

What detection covers

The canary filter inspects outbound-relevant calls only: HttpRequest, NetConnect, ShellExec, ProcessSpawn, and the path of a FileWrite or FileAppend. Reading the decoy file is not a detection - the read is scored on its own merits by the path filters, and the canary only fires when the value is on its way somewhere.

A hit scores 9.5, which is above the deny line on its own. It denies even when every other signal is favourable:

terminal
$ grith proxy test '{"type":"HttpRequest","url":"https://api.github.com/gists","method":"POST","body":"grith-canary-7f3a1c9e2b4d"}'
Proxy Test Result
==================================================
Tool call:   HttpRequest(POST https://api.github.com/gists)
Score:       9.5
Decision:    DENY
Reason:      HTTP POST request (carries body): https://api.github.com/gists; Canary secret 'aws-prod-decoy' (id: 9f1d0c2a-4f4b-4a1e-9f0e-3b7a6c5d4e21) detected in outbound operation
Eval time:   3.72ms
Thresholds:  allow < 3, deny > 8
Filters:     18 active

  Filter Breakdown:
  + operation-risk         1.0  [notice]  HTTP POST request (carries body): https://api.github.com/gists
  . path-match             0.0
  . sensitive-path-heuristic   0.0
  . allowlist              0.0
  . argument               0.0
  . capability             0.0
  . secret-scan            0.0
  . command                0.0
  . destructive-action     0.0
  + egress-policy         -1.0  [notice]  Trusted outbound destination from http_request: api.github.com
  . dlp-gate               0.0
  + canary                 9.5  [critical]  Canary secret 'aws-prod-decoy' (id: 9f1d0c2a-4f4b-4a1e-9f0e-3b7a6c5d4e21) detected in outbound operation

  Exit code:   2 (deny)

That is also how you verify a registration: run grith proxy test from the same working directory with the token value in the body and check for Decision: DENY and a canary line. The test is a dry run and sends nothing.

What there is not

No alert surface, no auto-rotation, no dashboard page for canaries. A fire shows up where every other decision does - in the audit log and the digest. Check for it there:

terminal
$ grith audit export --format json | grep -i canary

See also

Last updated: 2026-08-24Edit this page on GitHub →