Kanariya is a simple canary token service: you place unique URLs/files into specific locations (forms, notes, emails, local files), and if that token is ever accessed, Kanariya records the event and notifies you.
Scope: detection + evidence (logs). Not a high-interaction honeypot.
- Leakage / re-sharing of URLs embedded in:
- Web forms (free text fields)
- Documents or local files (HTML/shortcut tokens)
- Emails (click-detection)
- Recon / automated scanning to “obviously sensitive” paths (optional templates)
- Hosting intentionally vulnerable services
- Active attacker engagement, retaliation, or any offensive behavior
- Capturing payload data beyond minimal request metadata
- Generate a unique token URL:
https://<your-domain>/canary/<token>?src=<source_id>
- Place it only in one specific location.
- When accessed, Kanariya:
- stores an event record (timestamp, token, src, country/ASN/UA, etc.)
- optionally sends an alert (Webhook / email)
flowchart LR
Plant["Plant a unique token<br/>(URL / file / email)"] --> Access["Token is accessed"]
Access --> Worker["Cloudflare Worker<br/>GET /canary/:token"]
Worker --> Dedupe{"Dedupe window<br/>(token, ipHash, ua)"}
Dedupe -->|first hit| Store[("Workers KV<br/>Event record + TTL")]
Dedupe -->|repeat| Store
Store --> Notify["Notify<br/>Webhook / email (optional)"]
Store --> Export["Admin export<br/>JSON (optional)"]
subgraph Minimal metadata
Meta[ts, token, src, ipHash, country, asn, ua, referer]
end
Worker -.writes.-> Meta
- Cloudflare Workers: event collector endpoint (
/canary/*) - Workers KV: lightweight event storage + dedupe keys
- Webhook (Discord/Slack/etc.): instant notifications
- (Optional) Admin export endpoint for JSON export
This section defines the minimum scope for a usable public MVP.
- Plant unique tokens (URL / file / email) per location.
- Detect when a token is accessed.
- Preserve evidence (timestamp + minimal metadata) with low false-positive noise.
- Notify quickly (Webhook-first).
- High-interaction honeypots (SSH, intentionally vulnerable services)
- Payload capture (request bodies), content inspection, or user tracking
- “Attribution” beyond coarse network fingerprints (country/ASN/UA)
-
GET /canary/:token?src=<source_id>&v=1- Response:
204 No Content - Behavior:
- Write event record to storage
- Notify on first hit per
(token, ipHash, ua)within a window - Apply rate limiting / abuse protection
- If signatures are required, only signed requests within the time window are accepted
- Response:
-
GET /admin/export?token=<token>(optional, admin-only)- Response: JSON array of events for the token
- Header:
Authorization: Bearer <ADMIN_KEY> - Notes: for MVP, a shared secret is acceptable; later migrate to Cloudflare Access/SSO.
- If you do not set
ADMIN_KEY, this endpoint is disabled unlessALLOW_PUBLIC_EXPORT=1is set. - If
ALLOW_PUBLIC_EXPORT=1, the admin key is ignored.
Per event (recommended minimum):
ts(ISO8601)tokensrcipHash(HMAC(IP, IP_HMAC_KEY))country(edge header)asn(edge header, if available)uareferer(when present)
Dedupe / notify key:
(token, ipHash, ua)with TTL (e.g., 30 minutes)
Retention (MVP defaults):
- Events TTL: 30 days
- Dedupe TTL: 30 minutes
- Personal / local use: tokens placed in local files/notes; expect click/open to trigger.
- Enterprise / cloud use: primary audit comes from the cloud provider; Kanariya acts as an external-leak evidence supplement.
-
/canary/*deployed and reachable from your domain - KV bound as
KANARI_KV -
IP_HMAC_KEYset (no plain IP persistence) - Webhook notification tested
- Dedupe window prevents spam on repeated hits
- Rate limiting enabled (Worker logic and/or Cloudflare WAF)
- A small “token planting” playbook exists (URL/file/email examples)
This project assumes Cloudflare is the “server side” for the public MVP.
For API token scopes, see docs/cloudflare_token_permissions.md.
- Create a DNS record for the service hostname (example):
kanariya.toppymicros.com(proxied = ON)
- Ensure Universal SSL is enabled for the zone.
- (Recommended) Set SSL/TLS mode to Full (strict).
Bind the Worker to your hostname and paths (example):
- Route:
kanariya.toppymicros.com/canary/* - (Optional admin) Route:
kanariya.toppymicros.com/admin/*- Omit this route if you do not need
/admin/export.
- Omit this route if you do not need
You can also attach the Worker to the whole host and switch by path in code.
Create and bind the following KV namespaces:
KANARI_KV(required)- Stores event records and dedupe keys.
Recommended TTL defaults (tune as needed):
- Events: 30 days
- Dedupe keys: 30 minutes
Configure the following on the Worker:
IP_HMAC_KEY(secret, required)- Used to store
ipHash = HMAC(IP, IP_HMAC_KEY)instead of plain IP.
- Used to store
ADMIN_KEY(secret, optional)- Required only if
/admin/exportis enabled. If unset,/admin/exportreturns 403.
- Required only if
ALLOW_PUBLIC_EXPORT(non-secret, optional)- Set to
1to allow/admin/exportwithoutADMIN_KEY(overrides admin key). Not recommended for public use.
- Set to
MASTER_SECRET(secret, recommended)- Master secret used to derive per-token signing keys.
- Signing key derivation:
derivedKey = HMAC(MASTER_SECRET, "token:" + token) - Recommended when you want different effective keys per token without storing them.
SIGNING_SECRET(secret, legacy)- Backward-compatible fallback if
MASTER_SECRETis not set. - If
MASTER_SECRETis not set, Kanariya will also treatSIGNING_SECRETas the master secret for derived signing (ops-friendly), while still accepting legacy signatures created directly withSIGNING_SECRET.
- Backward-compatible fallback if
Optional:
WEBHOOK_URL(secret, optional)- Webhook destination for notifications.
MAIL_FROM(optional)- Sender email address for MailChannels.
MAIL_TO(optional)- Comma-separated recipient list for MailChannels.
MAIL_FROM_NAME(optional)- Sender display name (default: Kanariya).
MAIL_SUBJECT_PREFIX(optional)- Subject prefix (default: Kanariya alert).
EVENT_TTL_SECONDS(non-secret, optional)- Default: 2592000 (30 days).
DEDUPE_TTL_SECONDS(non-secret, optional)- Default: 1800 (30 minutes).
EXPORT_MAX_ITEMS(non-secret, optional)- Default: 1000 (admin export cap).
RATE_LIMIT_WINDOW_SECONDS(non-secret, optional)- Default: 60 (per-IP window, set 0 to disable).
RATE_LIMIT_MAX(non-secret, optional)- Default: 60 (max hits per window, set 0 to disable).
REQUIRE_SIGNATURE(non-secret, optional)- Default: 0. Set to
1to require signed canary URLs.
- Default: 0. Set to
SIGNATURE_WINDOW_SECONDS(non-secret, optional)- Default: 300 (allowed clock skew for
ts, set 0 to disable window check).
- Default: 300 (allowed clock skew for
ALLOW_PUBLIC_SIGN(non-secret, optional)- Default: 0. If set to
1,/admin/signcan be called withoutADMIN_KEY.
- Default: 0. If set to
You can require a valid signature on every /canary/* request.
This is useful when you want to reduce random scanning noise (only URLs you generated will be accepted).
- Enable signature requirement:
- Set
REQUIRE_SIGNATURE=1 - Set the master secret key (recommended):
wrangler secret put MASTER_SECRET- (Optional legacy)
wrangler secret put SIGNING_SECRET
Important: If
REQUIRE_SIGNATURE=1but neitherMASTER_SECRETnorSIGNING_SECRETis set, the Worker will silently ignore all canary hits (returns204but does not store events).
- Generate signed URLs:
- UI (recommended): call the signing endpoint
/admin/signfrom the Token Studio.- Set
ADMIN_KEYand keepALLOW_PUBLIC_SIGN=0(recommended). - The UI can pass
Authorization: Bearer <ADMIN_KEY>.
- Set
- CLI:
python3 scripts/gen_signed_url.py --base-url https://<your-domain>/canary --master-secret <MASTER_SECRET> --src <source_id>
GET /admin/sign?token=<token>&src=<src>
- Response:
{ "url": "https://<host>/canary/<token>?ts=...&nonce=...&src=...&sig=..." } - Protection:
- Default: requires
Authorization: Bearer <ADMIN_KEY> - Set
ALLOW_PUBLIC_SIGN=1to disable auth (not recommended for public exposure)
- Default: requires
This repo includes a workflow that deploys on every push to main.
It also syncs selected GitHub repo secrets into Worker secrets before deploying.
- Required Worker secrets for signed URLs:
MASTER_SECRET(recommended) orSIGNING_SECRET(fallback)ADMIN_KEY(to protect/admin/sign)IP_HMAC_KEY
- GitHub Actions repo secrets are not automatically available to the Worker runtime.
- This workflow copies selected values into Worker secrets using
wrangler secret put. - It does not update signing secrets; keep
MASTER_SECRET/SIGNING_SECRETmanaged on the Cloudflare side.
- This workflow copies selected values into Worker secrets using
- Rate limiting
- Apply a rate limit for
/canary/*to mitigate abuse. - Start conservative, e.g. per-IP bursts allowed but sustained floods blocked.
- Apply a rate limit for
- WAF rule
- Block obvious bot floods and suspicious user agents.
- Optionally challenge traffic to
/admin/*.
- Bot / Super Bot Fight Mode (if available)
- Useful for public exposure.
- Decide what you retain:
- Keep only minimal request metadata in KV (as documented).
- Avoid logging request bodies.
- For debugging:
- Use Workers logs during development.
- Consider sampling or short retention if you enable additional analytics.
Minimum (MVP):
- Require
Authorization: Bearer <ADMIN_KEY>header.- To disable admin export entirely, remove the
/admin/*route and omitADMIN_KEY.
- To disable admin export entirely, remove the
Recommended (post-MVP):
- Put
/admin/*behind Cloudflare Access (SSO / device posture).
name = "kanariya"
main = "src/worker.js"
compatibility_date = "2026-01-12"
# Route binding
routes = [
{ pattern = "kanariya.toppymicros.com/canary/*", zone_name = "toppymicros.com" },
# Optional admin export
{ pattern = "kanariya.toppymicros.com/admin/*", zone_name = "toppymicros.com" }
]
kv_namespaces = [
{ binding = "KANARI_KV", id = "<KV_NAMESPACE_ID>" }
]
[vars]
# Non-secret vars can go here (optional)
# NOTIFY_DEDUPE_TTL = "1800"Secrets:
wrangler secret put IP_HMAC_KEY
wrangler secret put ADMIN_KEY # optional (only if /admin/export enabled)
wrangler secret put WEBHOOK_URL # optional- Cloudflare account
- Node.js (LTS)
- Wrangler CLI
- Install deps:
npm install - Run unit tests:
npm test
- Create a local vars file:
- Copy
.dev.vars.exampleto.dev.varsand edit values.
- Start the Worker:
npm run dev
If REQUIRE_SIGNATURE=1, generate signed canary URLs via:
- UI: use
public/index.html(Advanced → Signing key) - CLI:
python3 scripts/gen_signed_url.py --base-url http://127.0.0.1:8787/canary --secret "$SIGNING_SECRET"
- Create a KV namespace (for events) and bind it to your Worker as
KANARI_KV. - Configure secrets:
ADMIN_KEY: export endpoint guard (optional)IP_HMAC_KEY: HMAC secret used to hash IPs (PII minimization)WEBHOOK_URL(optional): Discord/Slack webhook
- Deploy the Worker.
Create bindings/secrets via Wrangler (example names):
wrangler kv namespace create "KANARI_KV"
wrangler kv namespace list
wrangler secret put ADMIN_KEY # optional
wrangler secret put IP_HMAC_KEY
wrangler secret put WEBHOOK_URL # optionalNote: This repo may include a ready-to-deploy Worker template (planned). If you already have your Worker code elsewhere, the binding names above match the intended defaults.
The token generator UI is available at docs/index.html so you can host it on GitHub Pages.
It includes a basic admin export viewer (Authorization header required). If admin export is disabled, it will return 403.
- GitHub repo settings → Pages
- Source:
Deploy from a branch - Branch:
main, Folder:/docs
Your UI will be served from the GitHub Pages URL, while the API continues to run on Cloudflare Workers.
https://<your-domain>/canary/<random_token>?src=form_invoice_2026q1&v=1
random_token: long URL-safe random (recommend ≥ 16 bytes)src: location identifier (“where you planted it”)
Create an HTML file like Invoices_2026Q1.html containing:
<!doctype html>
<meta charset="utf-8"/>
<title>Invoices 2026Q1</title>
<img src="https://<your-domain>/canary/<token>?src=file_invoices_2026q1" />- When someone opens it in a browser, the image request triggers the canary.
Put a token URL in the email body:
https://<your-domain>/canary/<token>?src=mail_personal_test
Tip: Avoid image beacons at first. Some clients/gateways prefetch/proxy images and cause false positives.
Stored per hit (recommended minimal set):
ts(ISO8601)tokensrcipHash(HMAC of IP, not plain IP)country(from CDN headers)asn(if available)ua(User-Agent)referer(when present)
Recommended behavior:
- Notify only on first hit per
(token, ipHash, ua)within a time window - Rate-limit aggressively to prevent spam
MailChannels email (optional):
- Requires sender domain verification with MailChannels (see their docs).
- Set
MAIL_FROMandMAIL_TOto enable email alerts.
Provide a simple endpoint like:
GET /admin/export?token=<token>
Authorization: Bearer <ADMIN_KEY>
Return JSON array of events for the token.
If you do not need admin export, remove the /admin/* route and omit ADMIN_KEY.
To reduce spoofed alerts, you can require signatures on canary URLs.
Signed query parameters:
ts: UNIX timestamp (seconds)nonce: optional random nonce for replay protectionsig: HMAC-SHA256 overts|path|query(query excludessig)
Environment:
- Set
REQUIRE_SIGNATURE=1 - Set
SIGNING_SECRET(secret) - Optionally set
SIGNATURE_WINDOW_SECONDS(default 300s)
Generate a signed URL:
SIGNING_SECRET="..." python3 scripts/gen_signed_url.py \
--base-url "https://kanariya.toppymicros.com/canary" \
--token "<token>" \
--src "doc_invoices_2026q1"- PII minimization: store
ipHash(HMAC) instead of plain IP. - Do not store request bodies.
- Prefer
204 No Contentresponses to reveal nothing. - If publishing publicly:
- add per-IP rate limits
- add abuse protections (WAF / bot fight mode)
Kanariya is for defensive, consent-based monitoring only. Do not use it to:
- Monitor systems or data you do not own or have explicit permission to test.
- Send canary links to targets without authorization.
- Collect or store sensitive payloads or personal data beyond the documented minimal fields.
If you deploy publicly, publish a contact channel for abuse reports and respond promptly.
- DNS/HTTP telemetry can’t tell you “what data” was leaked—only that a token was accessed.
- Email environments may trigger false positives (prefetch/proxy).
- If an attacker exfiltrates files but never opens them, file tokens may not fire.
- Public MVP hardening: rate limits, dedupe defaults, and a minimal abuse policy
- Worker template +
wrangler.tomlscaffolding - Token generator UI (static)
- CSV bulk token generation
- Simple dashboard
- Evidence report (ASN/country/time clustering, proxy-like labeling)
PRs are welcome. Keep the project focused on detection + evidence.
Apache License 2.0 (Apache-2.0)
Copyright (c) 2026 ToppyMicroServices OÜ
Add a
LICENSEfile in the repo root with the full Apache-2.0 text.