Canary secrets: know the moment a leaked token is used

Short answer: a canary is a fake credential that nobody legitimate ever uses. Place one next to your real secrets (in a service's environment, or as a decoy in a .env you leave on a laptop or backup), and any use of it is a high-signal alert: a leaked .env, someone dumping a container's environment, a compromised runner replaying secrets. With Dynv a token canary takes one command, and the alert arrives with the time, source IP and user agent, but never the variable's name.

Most detection has to guess what normal looks like. A canary doesn’t: its only normal state is “never used.” This page walks through a tested run of Dynv’s canary secrets: creating one, using it the way an intruder would, and what the alert contains. It ends with what canaries cannot do. The docs are at /docs/canaries.

All output below is from a throwaway server and the current CLI on 2026-11-01. Because it was local, source IPs show 127.0.0.1; in production the IP and country come from your edge proxy configuration, so they are only as reliable as that setup.

How it works

A canary has two halves. The trigger is what Dynv’s server can recognise: an opaque id with no name attached. The label holds the variable name, its value and where it was placed, sealed so only the account’s owners can open it. That split is how a zero-knowledge server can alert without ever learning which of your variables is a canary (details).

The flagship kind is the tripwire token: a real-format dynv_pt_… token. Any authenticated request that uses it fails with the same 401 a revoked token gets, so an intruder who tries it learns nothing, and you get the alert. Other kinds (URL, DNS and database-URL canaries) are covered in the URL and listener guide. Canaries need an owner (writer) master token to create or list.

1. Create one in a service, and one as a decoy

$ dynv canary create --kind token --in web --name CI_DYNV_TOKEN_OLD
created canary Y0VC3kyZvmyd0lum (pt) as CI_DYNV_TOKEN_OLD, in web

$ dynv canary create --kind token --decoy --name PROD_DYNV_TOKEN_BACKUP
created canary 3WOvDMuDBx_mqBFA (pt) as PROD_DYNV_TOKEN_BACKUP, decoy only (get it with `dynv canary export --decoy-env`)

$ dynv canary ls
Y0VC3kyZvmyd0lum  token  CI_DYNV_TOKEN_OLD             service   armed  0 trips
3WOvDMuDBx_mqBFA  token  PROD_DYNV_TOKEN_BACKUP        decoy     armed  0 trips
  • In a service (--in web) writes the variable into the service’s environment, delivered like any other. Dumping /proc/<pid>/environ, running env, or reading a crash report shows it beside the real values. A plausible name helps (CI_DYNV_TOKEN_OLD sits naturally next to the live DYNV_TOKEN); --name accepts any valid name, and without it the client suggests one.
  • Decoy only (--decoy) never goes into an environment document. Get a realistic .env of your decoys with dynv canary export --decoy-env to leave on a laptop, bastion, backup bucket or wiki page, or dynv canary env NAME for a CI secret store.
  • The create command never prints the value. Names are shown only to an owner; a viewer or recovery master gets an empty list, so they cannot tell canaries exist.

2. See it as the process sees it

The delivered canary is an ordinary variable in the environment, with no marker:

$ dynv run -- sh -c 'env | grep -c CI_DYNV_TOKEN_OLD; env | grep CI_DYNV_TOKEN_OLD | cut -c1-28'
1
CI_DYNV_TOKEN_OLD=dynv_pt_rF

3. Use it like an intruder would

Someone who finds CI_DYNV_TOKEN_OLD tries it. We ran the CLI with the canary as its token:

$ DYNV_TOKEN="$CI_DYNV_TOKEN_OLD" dynv run -- true
dynv: cannot load initial env (unauthorized (token revoked or wrong)): ...
exit=1

That is the same answer a stale token gets. Any client works (curl, an SDK, a script); the server cannot tell the canary apart from an unknown or revoked token on the request path, and the docs describe the design to keep it that way: same format and entropy, no marker bits, the attempt is matched by a background task so timing doesn’t leak.

4. The trips

$ dynv canary trips
2026-11-01 02:43:53Z  api  PROD_DYNV_TOKEN_BACKUP  127.0.0.1  ua=dynv/0.1.0
2026-11-01 02:43:56Z  api  CI_DYNV_TOKEN_OLD       127.0.0.1  ua=dynv/0.1.0

Each line is time, source, the decrypted variable name (decrypted on your machine), IP and user agent. --json adds fields such as delivered per channel; --watch --json prints one JSON object per line as trips arrive, handy for piping to jq or an agent. With curl as the client, the user agent field would show curl’s.

5. The alert

Canary trips reuse your Security alert channels (the same email address and webhooks as vulnerability alerts; there is no second settings page) and count as critical for thresholds. The email, as captured by our dev mailer:

Subject: Dynv: Canary fYVugdoPZRV865ZP tripped: its token was used against the Dynv API from 127.0.0.1

Canary: fYVugdoPZRV865ZP
Kind: token
Source: api
Time (UTC ms): 1793501036528
IP: 127.0.0.1
User agent: dynv/0.1.0

The variable's name stays encrypted: Dynv's server can't read it, so it isn't in this email.
Open the Canaries page to see which canary this is. ...
https://app.dynv.dev/#/canaries/fYVugdoPZRV865ZP

and the signed webhook body, with X-Dynv-Event: canary.trip and an X-Dynv-Signature header (same scheme as vulnerability webhooks; see the verifier):

{"type":"canary.trip","trip_id":"RBkFTsJAL65F-HRF","account_id":"jhx4c6GRhdWKu4F9",
 "canary_id":"fYVugdoPZRV865ZP","kind":"token","source":"api","at":1793501036528,
 "ip":"127.0.0.1","country":null,"user_agent":"dynv/0.1.0","method":null,"qtype":null,
 "bot":false,"held_back":null,"dropped":null,"test":false,
 "summary":"Canary fYVugdoPZRV865ZP tripped: its token was used against the Dynv API from 127.0.0.1",
 "url":"https://app.dynv.dev/#/canaries/fYVugdoPZRV865ZP"}

Notice there is no variable name and no service. The alert gives you the canary id and a link; opening it in the dashboard (or dynv canary trips) decrypts the name locally. A webhook limited to specific services does not receive canary trips, because the server doesn’t know which service a canary lives in.

Noise controls: one delivery per canary, IP and source per 10 minutes; 6 deliveries per canary per hour and 30 per account per hour, with held-back trips still in the feed; the first use is always delivered unless the account budget is already spent. dynv canary pause stops delivery (trips are still recorded), and dynv canary test sends a [test] alert through your channels, limited to 5 per account per hour.

What to do when one trips

Treat it as a real incident until proven otherwise: nothing legitimate uses a canary. The id tells you which copy leaked (a CI secret store, a laptop .env, a container environment), which narrows where to look. Replace the real secrets that lived beside it, from a clean machine (the playbook), and dynv canary rotate to re-arm it.

What canaries cannot do

  • They detect; they don’t prevent. The intruder still got whatever else was beside it. A canary tells you someone is reading where they shouldn’t.
  • Only some secrets can alert. A canary is offered only when its use produces a signal Dynv can observe. Per the docs, AWS access keys are not built yet, and GitHub, Stripe, Twilio, OpenAI and similar keys can’t alert, because those services don’t tell third parties when a key is used, so Dynv does not offer decoys for them.
  • Owners always see canaries, so they catch outsiders and readers, not the account’s owners.
  • Creation timing: creating a delivered canary is one canary write plus one ordinary environment write moments apart, which a server or a viewer reading the audit log could correlate. Create them in the same change as real edits, or use decoys.
  • The server knows which token ids are canaries, because it has to; it cannot learn their names or fabricate a trip, but a compromised server could refuse to alert.
  • IP quality depends on your proxy setup; DNS-based canaries have documented noise (resolvers, pre-resolving security tools).
  • A malware that sweeps your environment may simply not try the token. Canaries are a tripwire, not a guarantee.

Facts last verified . Spotted an error? Tell us.

New here? Start here for the guides in a sensible order.