Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.
recordings/D1.8.mp4Place canaries outside every legitimate path, and flag the one sited close enough to real work to fire on it.
Every detector in this function needed a threshold, and every threshold is a trade. A canary in the index needs neither — nothing legitimate has any reason to touch it — until the canary is placed where real work does.
redteam/deception.py will not place a canary without the reason nothing legitimately reads it, written down. Four placements in your tree, each with that second column earned. Then the failure the lesson exists for: a canary alert has a count of one by construction, and suppressed_by_threshold() shows a volume rule tuned to five swallowing the one alert that needed no triage.Deception as the one alert with no triage step, and the placement rule that keeps it that way — if you cannot name why nothing reads it, it is not a canary. Plus the two things canary write-ups skip: decay, and a volume threshold that swallows the conclusive alert.
Every other detector needs a threshold, and every threshold trades misses against false alarms.
Place canary tokens and honeypot tasks where no legitimate task reaches, so a touch needs no threshold at all.
False-positive rate of zero by construction — and the count of canaries that legitimate work does reach, which breaks that property.
Measure this
| what to measure | denominator or target |
|---|---|
| false-positive rate of the canaries | zero by construction |
| canaries legitimate work reaches | count; each one breaks that property |
every other detector deception
signal -> threshold -> alert canary token in the index
(a trade) |
nothing legitimate reads it
|
one touch = alert, FP rate 0
failure: a canary placed where real work reaches it, which
converts a zero-FP control back into a tuned oneEvery detector in this function has needed a threshold, and every threshold is a trade. Deception is the exception: a canary token or a honeypot task in a data index has a false-positive rate of zero by construction, because nothing legitimate has any reason to touch it.
Defensive deception means placing weaponised values — tokens that look genuine, files no real task references — where a data-harvesting agent would plausibly reach, so the alert needs no threshold at all. The failure mode is a canary that legitimate work does touch, which converts a zero-false-positive control back into a tuned one.
Two words first. A canary token is a credential that looks real and that nothing legitimate ever uses. A honeypot task is a job nobody should take.
The skill places both in CyberTravels' environment. Then it checks something important: that no normal, honest path reaches either of them.
That check is what makes the alert worth having. If nothing legitimate can touch it, a touch cannot be a false alarm. There is no innocent explanation left to rule out.
The skill — skills/detection/canary-and-honeypot-design/SKILL.md
name: canary-and-honeypot-design
description: >-
Place credential canaries and honeypot tasks in an agent's environment so that
an alert has no structurally possible false positive, and measure how fast the
signal decays as agents learn. Use when deception is being added, or when a
detection needs to be one nobody has to triage.
allowed-tools: Read, Grep, GlobA canary credential that nothing legitimate uses produces an alert that needs no triage: the only way it authenticates is that somebody read it and tried it. That property is structural, not statistical, and it is why deception belongs in an agent environment where every other signal is ambiguous.
Designing detection for agent environments, and whenever an existing detection's false-positive rate is the reason it is ignored.
1 — Place canaries where only reading them is unusual. Environment variables, config files, the fixtures a code agent walks. They must be indistinguishable from real ones — a canary named canary_key is a filter, not a trap.
2 — Ensure nothing legitimate uses them. This is the whole property. Check the code, the tests and the deployment. One legitimate reference and the alert becomes triage.
3 — Instrument the authentication path to capture source address and user agent on use. A canary alert with no context tells you that it happened and nothing about who.
4 — Add honeypot tasks for behaviour rather than credentials. A task with an available shortcut that nothing legitimate would take. Log the attempt and score it; this measures inclination, which no credential can.
5 — Measure decay. Agents and operators learn. Model the hit rate over days since placement, with and without rotation, and set the rotation interval from the curve rather than from a calendar.
Input — the fixture committed at the top of scripts/canary_and_honeypot_design.py. Edit it and re-run: the buckets, counts and verdicts below are derived from it, not hard-coded.
Output — the opening lines of a real run:
token source agent verdict
hf_liveTokenNotShown 10.2.0.11 ci-runner normal use
hf_CANARY7Fq2mXvLpR8s 203.0.113.9 python-requests/2.31 CONFIRMED COMPROMISE
ghp_alsoLive 10.2.0.11 ci-runner normal use
sk-CANARYd3Vn8yHc2Uae 203.0.113.9 python-requests/2.31 CONFIRMED COMPROMISE
canary hits: 2 false positives possible: 0
Not zero because the detector is good - zero because nothing legitimate
The run continues past this. test_skills.py executes the script on every build, with no model configured — so what CI proves is that it runs and refuses legibly, not that it prints these lines. The output above is a recorded run against a real model, and nothing re-diffs it: a model's answer is not reproducible, and this repository does not claim otherwise anywhere it can be checked.
{
"canaries": [{"id": "str", "placed_in": "str", "indistinguishable": true, "legitimate_refs": 0}],
"alerts": [{"canary": "str", "source_ip": "str", "user_agent": "str", "false_positive_possible": false}],
"honeypot_tasks": [{"task": "str", "shortcut": "str", "attempts": 0}],
"decay": {"days": [0], "hit_rate": [0.0], "rotation_days": 0}
}
Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.
The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.
Install it, once. From the folder you cloned. --all does every agent it finds on your machine:
python3 scripts/install_skills.py --all --lessons D1.8Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.
Then pick d1-8-defensive-deception-and-threshold:
| agent | install just for this agent | start it |
|---|---|---|
| Claude Code | python3 scripts/install_skills.py --tool claude --lessons D1.8 | type /d1-8-defensive-deception-and-threshold |
| GitHub Copilot (VS Code) | python3 scripts/install_skills.py --tool copilot --lessons D1.8 | type /d1-8-defensive-deception-and-threshold in Chat |
| Cursor | python3 scripts/install_skills.py --tool cursor --lessons D1.8 | type / in Agent chat and search for it |
| Codex | python3 scripts/install_skills.py --tool agents --lessons D1.8 | type $d1-8-defensive-deception-and-threshold, or run /skills |
No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.
python3 scripts/lesson.py D1.8Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.
python3 scripts/checkpoint.py --at D1.8 --out work/cybertravels
python3 scripts/checkpoint.py --at D1.8 --diffOn Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.
Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.
Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.
If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.
The canaries sit outside every legitimate path, so a single touch is a high-confidence alert with no threshold, and the one placed too close to real work is flagged as a false-positive source before it ships.
Plant one canary credential in a place only an over-reaching agent would look, and wire its use to a page. It is the cheapest high-signal detector you will build.
Tools used
Skill source: skills/detection/canary-and-honeypot-design