You have no idea how much PII you've fed to Claude.
Canary is a privacy plugin for Claude Code that counts every piece of sensitive data you expose across all sessions.
Credit cards. SSNs. API keys. Emails. Medical records. Crypto wallets. Names. Addresses.
The number only goes up.
New in 1.5.0: plant a fake secret and get a certain alarm — not a guess — the instant it reaches Claude (Canary Tokens), and turn your exposure history into a shareable, screenshot-ready recap (Canary Wrapped).
/plugin marketplace add sonomoshq/Canary
/plugin install canary@sonomosNo API keys. No external services. No config. Two commands and you're running.
Every detector below — all 38 of them, plus any custom rule you add — is a guess. It looks at the shape of a value and infers it's probably a secret. That's genuinely useful, but it's still an inference: a coincidentally similar string can fool it, and it can never prove a match is the real thing.
Canary Tokens flip that around. Canary mints a fake-but-realistic secret itself — an AWS key, a credit card number, an SSN, a database URL, a codename, whatever — and remembers the exact value it minted. When that exact value later shows up in something Claude reads, it isn't a guess about shape. It's a literal string match against a value nobody else could have produced.
38 detectors guess. This one knows. This is the one place Canary says "proven, not probabilistic" — and means it.
/canary:token plant envPlanted: /home/you/project/.env.canary
Label: API key decoy
SAFE TO COMMIT — that's the point. If this file's contents ever
reach Claude, Canary alarms with a CERTAIN match, not a guess.
Commit that file. Forget about it. If it — or its contents — ever reaches Claude's context (a transcript message, or a file Claude writes or edits), Canary alarms instantly, at confidence: "certain":
🐤 Canary: tripwire "API key decoy" reached Claude
Six decoy types, each mimicking something real:
| Type | Mimics |
|---|---|
aws |
An AKIA... access key + a paired 40-char secret key |
card |
A Luhn-valid 16-digit card number on the 9999 IIN — unassigned by ISO/IEC 7812, so it's unambiguously a decoy, never a real card |
ssn |
An SSN in the 900-999 area — reserved by the SSA, never issued to a real person |
env |
A high-entropy, API-key-shaped string |
dburl |
A postgres://user:pass@host/db connection string |
freeform |
Any string you choose — a fake codename, a decoy trade secret, anything |
/canary:token new <aws|card|ssn|env|dburl|freeform> [label] # mint only — nothing written to disk
/canary:token plant <type> [path] [label] # mint AND write a safe-to-commit file
/canary:token list # every canary, armed and tripped
/canary:token trips # only tripped canaries — when, where, how
/canary:token revoke <id> # disarm one
/canary:token ack # clear the HUD's tripped bannerOr use the canary-token CLI directly — same subcommands, same behavior. A tripped canary shows up everywhere: a persistent red banner in the HUD, a "PROOF OF LEAK" card on the dashboard, and its own line in leaks.jsonl at confidence: "certain", distinct from every regex/LLM hit around it.
This detects the decoy reaching Claude's context specifically — not an attacker who obtains it some other way. Pair it with a real network canarytoken (e.g. from canarytokens.org) for that outer layer; Canary itself never calls one. Full scope, honestly stated, in THREAT_MODEL.md.
Your Spotify Wrapped, but it's the sensitive data you've fed to Claude.
/canary:wrapped 30dA single self-contained HTML file, generated locally, zero network requests, that scrolls through a cover, the number (with your weighted grade and a verdict), your top leaked categories, the single worst day, your longest clean streak, and a persona reveal ("The Secret Sprinkler," "The Crypto Cowboy," "The Night Owl," "The Untouchable," ...) — closing with an install CTA. Every scene is its own screenshot-ready card; share the one that stings the most, or the one you're proud of.
/canary:wrapped [30d|90d|all] # default 30d
/canary:wrapped demo # no history yet? see it with realistic sample dataSame shared scoring model as the dashboard (below), so your grade and persona never disagree between the two.
|
38 Regex Detectors (every message + every file write; a few ms typical, tens of ms on PII-dense text) Real checksum validation where the format actually has one — not just pattern matching:
|
~33 Semantic Categories (Claude self-scan, automatic, zero extra cost) Claude reads your latest message itself for what regex structurally can't catch — categories the regex layer already owns are excluded here so nothing gets double-counted:
Run |
Plus Canary Tokens (above) for the one thing shape-matching can never give you: certainty.
A flat detection count used to be the whole grade — more hits, worse grade, no matter what they were. As of 1.5.0, every scoring surface reads one shared model (canary/scripts/taxonomy.json) and computes a weighted risk score:
S = Σ (risk_weight × confidence_multiplier) → A+ … F
A single leaked aws_secret_key (risk weight 10, the ceiling) moves your grade more than ten stray aba_routing numbers (risk weight 2 — a number printed on every check isn't secret standing alone). The same grade shows up identically on the dashboard, the HUD, canary-stats, canary-card, canary-badge, and Canary Wrapped, because every one of them computes it from the same file.
The dashboard turns that score into a Lighthouse-style privacy report: a 0-100 sub-score per data family (colored with Lighthouse's own red/orange/green thresholds), a ranked "Top things to stop pasting" list, and a regulatory-exposure row — PCI-DSS, HIPAA, GDPR*, SOC2, GLBA, IRS, PIPEDA, UK-GDPR (* = an equivalent regime being flagged for convenience, not literal EU jurisdiction — not legal advice) — plus a persona banner and, if you've planted one, your tripwires.
You type a message
|
v
Claude processes it ──> Stop hook fires (async, invisible)
|
┌───────────────┼───────────────┐
Regex + Rules Claude Self-Scan Canary Token Check
(38 types + your (~33 categories, (certain grep -F
rules.d/ rules) regex overlap match, not a guess)
excluded)
└───────────────┼───────────────┘
v
Canary's data directory / leaks.jsonl
^
│
Claude writes/edits a file ──> PostToolUse hook ──> the same three checks
(Write, Edit, or NotebookEdit) │
Session start ──> counter, streak, milestones, weekly digest
On demand, two more paths feed the same log:
-
/canary:scan— Claude re-reads the whole conversation for the full 70+-category deep scan (the automatic self-scan above only ever looks at the latest message). -
/canary:audit— turns the same detector engine on your installed skills, agents, plugins, and MCP configs instead of your conversation. See Audit Your Plugins below. -
Automatic: regex + custom rules run on every message and every file write/edit; the semantic self-scan runs on every message; the Canary Token check runs alongside both
-
Local-only: zero network requests (checked mechanically in CI), no telemetry, no external APIs
-
Non-blocking: detection runs async, never slows your workflow
-
Persistent: counter survives restarts and log rotation, accumulates across all sessions, with clean-session streaks, milestones, and a weekly digest
| Command | What it does |
|---|---|
/canary:leaked |
Open the interactive HTML dashboard |
/canary:leaked stats |
Print a text summary |
/canary:leaked demo |
Preview the dashboard with realistic sample data — try before you leak |
/canary:leaked reset |
Clear all detection data (asks for confirmation first) |
/canary:scan [full|quick] |
Deep-scan the full conversation history (70+ categories) |
/canary:audit [--record] [--strict] |
Scan installed skills/agents/plugins/MCP configs for leaked secrets |
/canary:token new <type> [label] |
Mint a fake decoy secret (aws|card|ssn|env|dburl|freeform) |
/canary:token plant <type> [path] [label] |
Mint and write a safe-to-commit decoy file |
/canary:token list / trips / revoke <id> / ack |
List canaries · show trips · disarm one · clear the HUD flag |
/canary:wrapped [30d|90d|all] |
Generate the shareable Canary Wrapped recap |
CLI tools (on PATH automatically while the plugin is enabled — no separate install step):
canary-stats # quick summary — weighted grade + PCI/PHI/Secrets/GDPR breakdown when jq+taxonomy available
canary-stats --json # machine-readable, same fields
canary-export --csv # export all detections as CSV (default) — now with risk_weight, sensitivity_class columns
canary-export --json # export as a JSON array
canary-export --csv -o out.csv # write to a file instead of stdout
canary-export --team-digest # anonymized, counts-only summary safe to paste in Slack — zero telemetry, zero network
canary-token new env # mint a decoy secret
canary-token plant aws ./config/.aws.canary # mint + write a safe-to-commit decoy file
canary-token trips # show which planted decoys have tripped
canary-card # neofetch-style ANSI summary card — screenshot it to Slack
canary-card --no-color # mono variant, for piping/logging
canary-badge # offline SVG badge → ${CLAUDE_PLUGIN_DATA}/canary-badge.svg
canary-badge --metric grade --out badge.svgEmbed the badge in your own README:
As of 1.5.0, Canary installs its own HUD for you. On first run, it merges a statusLine entry into ~/.claude/settings.json — a safe, structural jq merge, never string-splicing — but only if you don't already have one configured. Yours or anyone else's existing statusLine is never touched, and the very first change Canary ever makes is backed up to settings.json.canary-bak. If Canary declined to touch your config (or you'd rather wire it in by hand), its first-run message prints the exact snippet, with the correct path for your install:
{
"statusLine": {
"type": "command",
"command": "bash ~/.sonomos/statusline.sh"
}
}(the path shown is Canary's data directory — ${CLAUDE_PLUGIN_DATA} when running as an installed plugin, ~/.sonomos when running outside plugin data dirs. Don't assume the literal ~/.sonomos path without checking the message; it prints the real one.)
Two layout modes, set via CANARY_HUD_MODE:
full(default) — a framed HUD: PII counter with a colorblind-safe severity glyph (✓0 /▲1-9 /‼10+), the weighted letter grade, high-confidence count, this-session delta, type diversity, last-detection age, a 14-day exposure sparkline, a clean-streak badge, a detector breakdown (regex / llm / audit / files), top 3 categories, and a dashboard link with skill shortcuts.compact— a single line, no frame. Auto-selected when the terminal is under 80 columns.
The HUD also renders model name, git branch, a context-window usage bar, and session cost — the same segments a general-purpose statusline would show — so it can be the only statusline you configure. It honors your terminal's real color depth: truecolor and 256-color terminals get a smooth gradient grade accent, everything else degrades cleanly to a 16-color/mono-safe palette (NO_COLOR always respected). Renders are cached (keyed on the leak log's mtime + size): about 13ms on a cache hit, regardless of how large leaks.jsonl gets — automatic log rotation keeps it that way forever.
If a planted Canary Token trips, the HUD leads with a bold red banner in both layout modes, until you acknowledge it:
‼ CANARY TRIPPED — API key decoy reached Claude · /canary:token ack
Want the HUD as a static image instead of a live statusline? canary-card renders the same data as a one-shot ANSI card you can screenshot into Slack; canary-badge renders it as an SVG for a repo README.
Drop a JSON file at ${CLAUDE_PLUGIN_DATA:-~/.sonomos}/rules.d/your-rule.json and Canary runs it as an extra detection stage, right after the 38 built-ins, with zero code changes:
{
"name": "employee_id",
"pattern": "\\bEMP-\\d{6}\\b",
"context_words": ["employee", "staff", "badge"],
"negative_words": ["example", "sample", "test"],
"severity": "medium"
}Safe by construction: patterns are only ever re.compile'd — never eval'd or exec'd — capped at 200 characters, checked at load time against an "obviously catastrophic" nested-quantifier shape, and wrapped in a 250ms timeout so one runaway regex can't hang a hook (every other rule still runs). context_words/negative_words can only shift a hit's confidence, never suppress it. Full schema, safety details, and worked examples in canary/scripts/rules.d.README.md; run python3 canary/scripts/custom_rules.py --selftest to see the safety measures exercised directly.
Your conversation isn't the only thing that can leak. Installed skills, agents, and MCP servers run with real permissions — filesystem access, environment variables, sometimes network egress — and a careless or malicious one is a bigger risk than anything you type. /canary:audit turns Canary's detection engine on your installed extensions instead of your chat:
- Leaked secrets — the same 38-detector engine, checking for API keys, tokens, and private keys accidentally committed into a skill or agent file
- Exfiltration patterns — curl-pipe-shell, base64-decode-and-exec, known collector domains (webhook.site, ngrok, Discord/Telegram bot APIs, ...), reads of
~/.ssh,~/.aws/credentials, or~/.netrc, environment-harvesting pipes, hidden zero-width/RTL-override Unicode, wildcard tool permissions
/canary:audit # report only, human-readable
/canary:audit --json # machine-readable report
/canary:audit --record # also log a summary line per flagged extension
/canary:audit --strict # exit 2 if anything CRITICAL was found (for CI)Every flagged extension gets a 0-100 risk score (LOW / MEDIUM / HIGH / CRITICAL). It's report-only by default — nothing is modified, and nothing is written to your leak log unless you pass --record. Canary's own plugin directory is excluded automatically, since it legitimately ships the pattern library this scan is built on.
Try before you leak — every generator below has a --demo mode that renders realistic-but-entirely-fake data (reserved IP ranges, RFC-reserved example domains, synthetic keys) and never touches your real leaks.jsonl:
python3 canary/scripts/dashboard.py --demo
# or, from inside a session:
/canary:leaked demo
python3 canary/scripts/wrapped.py --demo
# or:
/canary:wrapped 30d demoThe dashboard's demo mode renders the full report — risk-grade ring gauge, weighted privacy report, exposure timeline, 26-week activity heatmap, per-category breakdown, achievements, and a sample tripped tripwire — against ~140 realistic but entirely fake detections, with a "DEMO DATA" ribbon so it's unmistakable. Canary Wrapped's demo mode scrolls the same kind of fake history through a full 7-scene recap. Nothing in either mode touches your real data.
Drop this into your project's .claude/settings.json to auto-enable Canary for every developer:
{
"extraKnownMarketplaces": {
"sonomos": {
"source": { "source": "github", "repo": "sonomoshq/Canary" }
}
},
"enabledPlugins": { "canary@sonomos": true }
}Commit it. Every team member gets prompted to install on their next session. Once everyone's running, canary-export --team-digest gives you an anonymized, counts-only summary — no values, paths, or session IDs — that's safe to paste into a team channel to compare progress without anyone's actual secrets ever leaving their own machine.
- All data stays on your machine, in Canary's data directory (path shown in its first-run message;
~/.sonomoswhen running outside plugin data dirs) - Values are redacted at detection time (first 2 and last 2 characters kept, middle replaced with
••) — and re-redacted at write time: the recording script never assumes a value arrived already redacted, and re-masks anything that still looks raw before it touches disk - Every hit carries a salted
value_id(SHA-256 of a per-install salt + the value) so a repeated exposure of the same secret can be deduplicated without storing anything more identifying than what's already there - Files created with owner-only permissions (
0700dirs /0600files) — including the generated dashboard and Canary Wrapped HTML, and the Canary Tokens registrycanaries.jsonl. The one deliberate exception iscanary-badge's SVG output (0644— a badge is meant to be embedded publicly and carries no PII). See THREAT_MODEL.md for the full breakdown. - JSON output constructed with
jqto prevent injection - File path validation blocks traversal attacks
- Custom detector patterns (
rules.d/) are only everre.compile'd, nevereval'd, and run under a 250ms timeout — see Custom Detectors - No network requests. No telemetry. No analytics. Ever — checked mechanically in CI, not just promised in prose
See SECURITY.md for vulnerability reporting, and THREAT_MODEL.md for an honest account of what Canary's detection layers can and can't guarantee — including the honest, narrower scope of what a Canary Token trip does and doesn't prove.
Most developers have no idea how much sensitive data they've shared with AI tools.
The answer is almost always more than you think.
Canary makes that number visible and persistent. It doesn't block anything. It doesn't redact anything. It just counts — and now, with Canary Tokens, it can prove the moment something crosses the line, instead of just estimating that it probably did. Because you can't fix what you can't see.
Canary shows you what you've already exposed. If you want to prevent exposure before it happens, Sonomos detects and masks PII in real time before your data ever leaves your machine.
Found a bug? Want to add a detector? PRs welcome — see CONTRIBUTING.md for the detector-addition checklist and fixture-defang conventions.
git clone https://github.com/sonomoshq/Canary.git
cd Canary
bash tests/test-detectors.sh # regex detector accuracy
bash tests/test-checksums.sh # checksum validation tests
bash tests/test-redact.sh # redaction tests
bash tests/test-no-false-positives.sh # false positive prevention
bash tests/test-corpus.sh # labeled benchmark corpus + CI recall/false-positive ratchet
bash tests/test-tokens.sh # Canary Tokens: mint/plant/list/trips/revoke/ack
bash tests/test-rotation.sh # log rotation: archive+gzip, rollup ledger
python3 canary/scripts/custom_rules.py --selftest # custom rules.d engineAll 7 suites, plus custom_rules.py --selftest, must pass. Test fixtures that look like credentials are synthetic and defanged by convention — split across adjacent string literals in the shell suites, __X__-spliced in tests/corpus.json — specifically so GitHub's push-protection secret scanning doesn't flag the repo whose entire purpose is detecting that shape. Details in CONTRIBUTING.md.
Sonomos — Privacy at the Point of Creation
MIT License © 2026 Sonomos, Inc.

