Skip to content

Canary tokens

What they are

A canary token is a trip-wire disguised as something valuable. It's a unique, innocent-looking URL that you hide inside bait — a fake credentials file, a "leaked" database backup, a config file, an email. Nobody has a legitimate reason to visit that URL, so the very first request to it is, by construction, proof that someone opened the bait who shouldn't have.

Unlike decoy routes (which catch strangers scanning your site from outside), canary tokens catch whoever is holding your files — a compromised laptop, an insider snooping shared drives, an attacker who exfiltrated a backup and is now reading through it. The token doesn't care how the bait travelled; the moment it's opened and the URL is fetched, the canary sings.

Each token fires exactly once. When its URL is hit, HoneyDjango stamps the token as triggered (recording when, and from which IP), logs a full canary event through the same capture pipeline as every decoy hit, and returns an invisible response so the visitor sees nothing unusual. Two requests racing on a fresh token can't both claim it — exactly one wins.

Generating a URL token from the admin

  1. Open Canary tokens in the admin and use the Create Token button (it leads to /canary/create/ — staff-only, behind the admin login).
  2. Give the token a label you'll recognise when it fires — name it after where you plan to plant it, e.g. fake-prod-backup-on-s3.
  3. Submit. You get back the token's unique ping URL:
https://your-host/canary/3f2a9c1e-8b4d-4e6a-9c0f-1d2e3f4a5b6c/ping/

Copy it — that URL is the trip-wire. It's keyed on a random UUID, so it can't be guessed or enumerated; the only way to hit it is to have found it in your bait.

Where to plant tokens

Anywhere a snooper would look and a legitimate user wouldn't. Ideas:

A fake .env in a repo or file share:

# .env  (bait — none of this is real)
DATABASE_URL=postgres://admin:Sup3rS3cret@db.internal:5432/prod
STRIPE_SECRET_KEY=sk_live_51H8...
LICENSE_CHECK_URL=https://your-host/canary/3f2a9c1e-.../ping/

Anyone who steals the file and probes the URLs in it trips the canary.

A fake backup or "credentials" document — drop prod-db-backup-2026.sql.gz.README.txt on a share, containing a "restore instructions" link that is really the ping URL.

An email or document as a tracking pixel — because the ping returns an invisible 1×1 image, the URL works inside an <img> tag; the canary fires when the mail or page is opened:

<img src="https://your-host/canary/3f2a9c1e-.../ping/" width="1" height="1">

A row in a decoy database — a urls or webhooks table entry that only exists in the copy you expect to be stolen.

Mint a separate token per location. When one fires, its label tells you exactly which bait was opened — that's the whole forensic value.

What a trigger looks like

The visitor receives a 1×1 transparent pixel — nothing to arouse suspicion. On your side, everything lights up at once:

  • Canary tokens admin — the token flips to triggered=True, with triggered_at and the trigger_ip recorded permanently.
  • Events — a full HoneyEvent with decoy_type="canary": the visitor's IP, headers, User-Agent, and JA3 hash, enriched like any other hit (GeoIP, threat feeds, scanner fingerprinting) and folded into an attacker profile.
  • Dashboard — the enriched event slides into the live table tagged canary, and if the IP geolocates, a marker pulses on the map.
  • Alerts — rules are evaluated against the trip immediately and synchronously, before the enrichment queue is even involved. A canary is a rare, high-value signal, so it's exempt from the per-IP rate limit and doesn't wait in line. The rule you want enabled:
{"field": "decoy_type", "op": "eq", "value": "canary"}

with a Slack or webhook notifier — see Alert rules. Set that up before you plant tokens; a trip-wire nobody hears is just a log line.

Once fired, a token stays fired: later hits on the same URL are ignored (the first trigger's forensics are never overwritten). If you want to re-arm a location, mint a fresh token and re-plant.