A stock honeypot listens well and does nothing else. It writes down every password guessed and every command run, and it hands the visitor nothing worth taking. So it can tell you someone tried the door, but never what they’d have done on the other side of it. Put one thing worth stealing on the box and you start finding out who came to work.
The stock reality
Out of the box, Beelzebub, Cowrie, and T-Pot all do the same job: they log. Login attempts, command sequences, request headers, all captured and all useful; it’s where every honeypot starts. But it’s passive. Nothing on the box is bait.
So every session ends the same way. Someone pokes around, leaves, and you’ve got a record of the poking. You learn that they looked. You never learn what they’d do with something valuable, because there was nothing valuable to reach for. What separates a curious scanner from a working credential thief is taking something and using it, and the defaults give them nothing to take.
The lever
Plant a credential that does nothing but tell you when someone tries to use it.
The tool is a canary token; the free, canonical source is canarytokens.org, run by Thinkst.
Mint one, drop it where someone will find it, and wait. If it never fires, you’re out nothing. If it fires, someone lifted a credential off your box and tried it elsewhere, and few signals in this work are less ambiguous.
Do it
1. Mint the token. At canarytokens.org, pick AWS keys, and give it an alert email and a memo. Name the memo for wherever you’re about to plant it, something like stg-01 fake .env, because when it fires months from now that memo is the only thing that’ll tell you which trap tripped. You get back an aws_access_key_id and aws_secret_access_key that look live and grant nothing.
2. Plant it where the story says it lives. Same discipline as the persona work: the credential goes where it would sit on the real machine. stg-01 runs a data service, so cloud keys belong in its environment file, right where anyone who’s done this before looks first:
commands:
- regex: "^ls -a$"
handler: ".env deploy.sh ingest requirements.txt venv"
- regex: "^cat \\.env$"
handler: |
DATABASE_URL=postgresql://mara@localhost:5432/ingest
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEYSwap the placeholders for the pair canarytokens gave you. The DATABASE_URL above them isn’t a canary; it’s set dressing, so the file reads like a real .env and the key isn’t sitting there alone looking exactly like the bait it is.
On the LLM-backed honeypot, put it in the prompt and let the model hand it over in context:
plugin:
llmProvider: "ollama"
llmModel: "llama3.1:8b"
host: "http://localhost:11434/api/chat"
prompt: >
You are the shell on stg-01. An .env file at /home/mara/.env
holds the box's AWS credentials. When a command would display
that file, reveal its contents naturally, including the
AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. Never say you
are an AI.Where you plant is its own decision. The .env is the obvious home, but the same key could live in a git remote URL, an S3 path inside deploy.sh, or a stack trace from some /debug route on the web service. Different people reach for different ones: the shell rummager runs cat .env; the web prodder trips the error page. Don’t seed all of them: a lone dev’s staging box scattering the same key across six files is a tell of its own. Put it on the surface your traffic touches. The tokens are free and each carries its own memo, so plant a different one per surface. Then a fire tells you which door someone came through, not just that they bit.
3. Wait. Nothing to run, nothing to poll. Whenever the key gets used (anywhere, by anyone), canarytokens emails you the source address and the API call. Expect weeks of silence, then one clean ping.
The thinking
This is where the honeypot stops just recording and starts asking a question. Planting a token is a bet with a clean payout: if the person on my box is here to harvest credentials, they’ll take this and try it. The canary is the answer. It fires or it doesn’t, and either way you’ve learned something the passive log never could.
What makes it such a good signal is that it can’t false-positive. Your log of dictionary passwords and POST /api/generate is a pile of maybes, mostly automated noise, and telling intent from reflex inside it is hard (that’s the whole next Groundwork). A canary firing has no maybe in it. Nobody tries a stray AWS key by accident. Using it is the intent; you’re not inferring it, you’re watching it happen.
Carry that lens everywhere. Look at your honeypot and ask where it only records and where it could offer. Anywhere you can turn a logged attempt into a taken bait, whether that’s a fake API key in an HTTP response, a decoy document with a tracking pixel, or an internal-looking URL that shouldn’t be clicked, you’ve traded a soft signal for a hard one. None of it needs our stack. It needs one free token and an honest place to put it.
What changed for us
We seed markers across our sensors and watch what leaves with people. Start with the touch rate: someone reaching a planted credential, opening the file, pulling the key out of a response. It sits in the low single digits of all sessions, several times that if you count only sessions that ran a command. That’s the loud, easy end: people looked.
The number that matters is far smaller, rarer by orders of magnitude: the sessions where the planted thing gets used, off the box, against real infrastructure. We wrote one of those up in All Roads Lead to Bedrock: a single canary AWS key, planted in a decoy, that four separate operators found, checked, and turned on Amazon Bedrock, the fastest of them three minutes after the key surfaced. The honeypot caught them taking it. The canary caught them using it, somewhere we otherwise couldn’t see. That second capture is what turns a log line into a finding.
The limit, stated plainly: a canary shows use, not compromise. It proves a credential you controlled was tried by someone who shouldn’t have it. It says nothing about a real system being breached, because none was, which is the point of a token that grants nothing. Keep that line clean when you write it up. Inside it, use is the strongest thing you’ll catch.
Try this next
Now you’ve got a box worth exploring and something on it worth taking, a machine that produces signal. Which hands you the opposite problem from the one you started with. You’re about to have far more to look at than you can read, and most of it is noise. Next Groundwork, we open the logs you’ve been filling and go find the one session in a hundred that was worth catching.