I’ve been using canary tokens for about a decade. A document nobody should be opening, credentials nobody should be using, a link left somewhere it has no reason to be fetched. Give each one its own token and you can find out when it gets touched.
I published cfcanary so you can run those tokens on your own domain. It’s open source, runs in your Cloudflare account on Workers and D1, and sends alerts through ntfy, a webhook, or both.
Sometimes an organization’s policy makes sending hit details to another service a problem. Cloudflare might already be approved while a separate canary provider isn’t, or you’d just prefer to own the deployment.
Why I keep using them
A canary gives you a detection you can explain: this file contains credentials that have no legitimate use, and someone tried to use them. Go look.
Placement does most of the work. A fake kubeconfig in a directory someone browses during routine maintenance is different from one sitting in a backup that nobody should be accessing. The mechanism can be identical; the meaning of an alert depends on where you left it.
You can add one without changing the application you’re watching. A decoy file in a share or a registry configuration on a build machine gives you another signal without new application telemetry.
Your own hostname helps too. assets.example.com can look like ordinary company infrastructure; a recognizable public canary domain is easier to identify and avoid.
What runs where
The public endpoint is a Worker on your chosen hostname. D1 holds token definitions and hits. A request containing a known token ID gets the response for that token type; the Worker records the hit and sends the notification in the background.
Administration goes through the Cloudflare API. The CLI deploys the Worker, creates tokens, and reads hits using your Cloudflare credentials. The public Worker has no token-management API.
The cfcanary CLI authenticates to the Cloudflare API to deploy the Worker and manage tokens and hits in D1. A client requests a token URL on your custom hostname. The Worker looks up the token in D1, records the hit, and sends an alert to your configured ntfy server or webhook. Token administration does not pass through the public Worker.
Cloudflare still handles requests and stores hits, and your notification service receives the alert details. This lets you avoid a separate canary provider when your existing Cloudflare account and an internal alert destination meet your requirements.
Leave a kubeconfig behind
A kubeconfig token produces a Kubernetes configuration pointing to your Worker, with a generated decoy bearer token. It grants no access to a real cluster.
You’ll need Node.js 20 or newer, a domain on Cloudflare, and Cloudflare credentials. The README covers permissions and Wrangler login. This example uses self-hosted ntfy; replace the hostnames and notification credentials:
npm install -g github:deoxynet/cfcanary
cfcanary deploy assets.example.com \
--ntfy-server https://ntfy.example.com \
--ntfy-token YOUR_NTFY_TOKEN
cfcanary new kubeconfig \
--memo "backup share kubeconfig" \
-o decoy-kubeconfig
Subscribe to the topic printed during deployment. The memo labels the alert, so use it to identify the placement. Give each placement its own token.
To trigger this example deliberately:
kubectl --kubeconfig ./decoy-kubeconfig get pods
The Worker answers with a Kubernetes-shaped 401 Unauthorized response and attempts to send an alert with your memo, source IP, request path, User-Agent, and available location and network details. Inspect the hit and delivery status with:
cfcanary hits
Reading the file alone doesn’t fire it. The client has to contact the server.
Other things you can plant
There are six token types in the initial release:
| Token | What triggers it |
|---|---|
| Web | Fetching a URL or loading its image |
| Redirect | Opening a URL that then redirects to your chosen destination |
| QR | Fetching the URL encoded in the QR code |
| Cloned site | Running the detection snippet on a hostname outside your allowlist |
| Kubeconfig | Using the configuration to contact its decoy Kubernetes endpoint |
| npmrc | Using the configuration to contact its decoy npm registry |
A web token fits in an internal HTML report or decoy README. An npmrc token fits a build environment. The cloned-site detector depends on its script surviving the copy and being allowed to run.
Make sure the request gets through
Cloudflare’s protections can stop a request before the Worker sees it. Deployment attempts to disable Browser Integrity Check for the canary hostname when your credentials permit it, then probes with script and scanner User-Agents. Repeat those probes with cfcanary check. Other zone protections can still interfere; a check from your IP doesn’t prove every client can reach it.
Use cfcanary test <id> to trigger a token and check delivery. Anonymous and free-account ntfy.sh publishing run into shared-IP rate limits from Workers, so use self-hosted ntfy, a paid ntfy.sh account, or a webhook. Failed deliveries are recorded without retries.
Repeat hits are recorded, with notifications suppressed for the same token and IP within 60 seconds. Recognized preview bots get tagged and sent at low priority. Link previews and security scanners can still trigger URL tokens, so placement matters: a hit tells you something fetched the token, and context helps explain why.
The source is on GitHub under the MIT license, with the full setup, token examples, and caveats in the README.