Honeytokens & deception
Decoys nothing legitimate ever touches.
Banks keep dye packs in the cash drawer. A dye pack looks like a stack of twenties, sits with the real money, and no honest transaction ever moves it. The moment it leaves the building it bursts and paints the thief bright red. Deception does the same thing to your infrastructure. You plant something that looks valuable (a credential, a user account, a document, a whole fake server) that no person and no process has any legitimate reason to touch, then you wire it so that *any* touch at all sets off an alarm. Every other lesson in this course fights the signal-to-noise war. Deception steps around the fight, because you engineer the rate of harmless, boring interactions down to zero.
Three words first, because people use them loosely. A honeytoken is a decoy piece of *data*: a fake AWS (Amazon Web Services) access key, a bait row in a database, a document that phones home when someone opens it. A honeypot is a decoy *service or machine*: an SSH (Secure Shell, the standard remote-login protocol) daemon that exists only to be broken into. Canary is the umbrella word, popularised by the security company Thinkst, for any tripwire of this kind. What makes them so strong is arithmetic, not cleverness. An alert's precision, the share of its firings that turn out to be real, depends on how often harmless activity trips it, and for a well-placed canary that number is approximately never. A hit means somebody is inside. You usually catch them early, while they are still hunting for credentials or mapping what they can reach, before real damage.
What every tripwire is made of
A deception asset has three parts, and the weakest one decides what the whole thing is worth. The bait has to be findable and believable, planted exactly where intruders go rummaging. MITRE ATT&CK (a public catalogue of the techniques real attackers use, maintained by the MITRE Corporation) files that rummaging under T1552, Unsecured Credentials: ~/.aws/credentials, .env files, CI (continuous integration, your automated build system) variables, shell history, a wiki page titled "prod runbook". The sensor is the telemetry a touch unavoidably produces: a CloudTrail record (CloudTrail is the AWS audit log of who called what), a Windows logon event, a DNS (Domain Name System, the internet's phone book) lookup. The alarm is the plumbing that turns that record into somebody's phone ringing. A decoy with a broken alerting path is worse than no decoy at all. It cost you effort and it buys you false confidence.
Plant a decoy AWS key
Cloud keys are the classic honeytoken for two reasons. Attackers actively grep for them, and AWS hands you a perfect sensor for free. You mint a *real* IAM key (IAM, Identity and Access Management, is the AWS service that decides who is allowed to do what), so the key survives any validity check an intruder runs, but you attach it to a user with no policies at all. Then you add an explicit deny-everything policy, so the key stays harmless even if a colleague later attaches permissions by mistake. Name the user like a dull service account. A user called canary-token fools nobody.
$ aws iam create-user --user-name s3-backup-svc \--tags Key=purpose,Value=deception{"User": {"Path": "/","UserName": "s3-backup-svc","UserId": "AIDAQ3EGA4EXAMPLE7Y2K","Arn": "arn:aws:iam::111122223333:user/s3-backup-svc","CreateDate": "2026-07-13T08:12:44+00:00","Tags": [{"Key": "purpose","Value": "deception"}]}}$ aws iam put-user-policy --user-name s3-backup-svc \--policy-name DenyAll \--policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Action":"*","Resource":"*"}]}'# (no output on success)$ aws iam create-access-key --user-name s3-backup-svc{"AccessKey": {"UserName": "s3-backup-svc","AccessKeyId": "AKIA3D7EXAMPLECANARY","Status": "Active","SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY","CreateDate": "2026-07-13T08:13:02+00:00"}}
Now plant it. An entry in ~/.aws/credentials on your bastion hosts, a variable in an internal repo's .env.example, a "legacy" secret in CI. Keep a private inventory that maps each key ID to the place you planted it, because when the alert fires that mapping tells you *which host or repo the attacker is standing on*. One deliberate AWS quirk works in your favour here. The call sts:GetCallerIdentity (STS is the Security Token Service, and this call answers the question "who am I?") needs no permissions and cannot be denied by any IAM policy, yet AWS still writes it to CloudTrail. Checking that a stolen key is live is the first thing an attacker does, so your deny-all key rings the bell anyway.
Wiring the alarm so the touch reaches you
CloudTrail records every authenticated management-plane API (application programming interface) call, including the ones that failed with an errorcode of AccessDenied. For paging in real time, an EventBridge rule (EventBridge is the AWS event router) matching userIdentity.accessKeyId is the production wiring. There is one trap in it. A rule left in the default ENABLED state matches only *write* management events, and the first calls an attacker makes (GetCallerIdentity, ListBuckets) are read-only. Create the rule with its state set to ENABLED_WITH_ALL_CLOUDTRAIL_MANAGEMENT_EVENTS, or it will sleep through the entire intrusion. For hunting backwards through history, and for checking your own work, query the CloudTrail archive with Athena, which runs SQL over log files sitting in storage.
SELECT eventtime, eventname, eventsource,sourceipaddress, useragent, errorcodeFROM cloudtrail_logsWHERE useridentity.accesskeyid = 'AKIA3D7EXAMPLECANARY'ORDER BY eventtime DESCLIMIT 10;-- eventtime eventname eventsource sourceipaddress useragent errorcode-- 2026-07-11T02:14:31Z ListBuckets s3.amazonaws.com 185.220.101.45 aws-cli/2.17.5 ... AccessDenied-- 2026-07-11T02:14:09Z GetCallerIdentity sts.amazonaws.com 185.220.101.45 aws-cli/2.17.5 ... (null)
Read those two rows as a short story. Someone checked the key was live with GetCallerIdentity, then went straight for the S3 buckets (S3, Simple Storage Service, is where most AWS data lives), from a Tor exit node, the last hop of an anonymity network. That is not a misconfigured script. That is a person holding your credentials file. The on-premises equivalent is a decoy Active Directory account (Active Directory is Microsoft's central directory of users and machines). Nobody ever logs into it, so Windows events 4624 (successful logon) and 4625 (failed logon) against it are pure signal. A sharper variant registers an SPN on the decoy, a Service Principal Name being the label that says "this account runs this service". That makes the account a target for Kerberoasting, where an attacker asks for a service ticket and cracks it offline for the password. The attempt lands as event 4769, usually with the weak RC4 encryption type 0x17, and it trips the decoy without a single logon. Here is the query in Microsoft Sentinel, written in KQL (Kusto Query Language):
SecurityEvent| where EventID in (4624, 4625)| where TargetUserName =~ "svc-veeam-bak" // decoy — zero legitimate use| project TimeGenerated, EventID, IpAddress, WorkstationName, LogonType| order by TimeGenerated desc// TimeGenerated EventID IpAddress WorkstationName LogonType// 2026-07-12 23:41:07 4625 10.20.8.71 FIN-WKS-114 3// 2026-07-12 23:40:58 4625 10.20.8.71 FIN-WKS-114 3
Decoy services with OpenCanary
Honeytokens catch credential hunting. Honeypots catch network exploration. OpenCanary is Thinkst's open-source honeypot daemon, and it is low-interaction, meaning it copies only the opening handshake and the login banner of a protocol. It *pretends* to be SSH, RDP (Remote Desktop Protocol, the Windows remote-screen service), Telnet, MySQL or an HTTP admin panel, and it writes every connection and login attempt as JSON (JavaScript Object Notation, plain-text structured data) that you ship straight into your SIEM (Security Information and Event Management, the platform where your logs land and your alerts are built). Because the whole thing is mimicry, an attacker never gets a real shell on it, so the decoy itself carries almost no risk of being turned against you.
$ python3 -m venv env && . env/bin/activate$ pip install opencanary$ opencanaryd --copyconfig# [*] A sample config file is ready /etc/opencanaryd/opencanary.conf# enable "ssh.enabled": true (and set a fake "ssh.version" banner), then:$ opencanaryd --start --uid=nobody --gid=nogroup$ tail -1 /var/tmp/opencanary.log{"dst_host": "10.20.8.9", "dst_port": 22, "local_time": "2026-07-12 23:58:41","logdata": {"LOCALVERSION": "SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.6","PASSWORD": "P@ssw0rd!", "REMOTEVERSION": "SSH-2.0-libssh_0.9.6","USERNAME": "root"},"logtype": 4002, "node_id": "opencanary-dc1","src_host": "10.20.44.17", "src_port": 51988}
logtype: 4002 means an SSH login attempt, and the record carries the username and password the attacker typed. That often tells you *which* stolen credential set they are working from. On a flat internal network, any connection to a machine that advertises no legitimate service is somebody looking for a way to move sideways. There is no tuning conversation to have here. Open an incident and go and look at the source host.
Limits, cost, and production practice
Deception is the cheapest signal in detection engineering. An IAM user costs nothing. OpenCanary runs happily on a leftover VM (virtual machine). Thinkst's hosted canarytokens.org mints document, DNS, URL and AWS-key tokens for free. Set that against the analyst hours a single noisy correlation rule burns every month and the return is absurd. Which is exactly why deception sits alongside your Sigma rules and your UEBA baselines (User and Entity Behaviour Analytics, the models that learn what normal looks like for each account) rather than competing with them.
Know what deception cannot do. It proves presence, not technique. A tripped canary says "somebody is in here", not how they got in. Its coverage is a matter of luck as much as design, because an attacker who never rummages in the drawer you baited stays invisible to it, and that is why deception never replaces rule-based or behaviour-based detection. It also demands housekeeping. Document every decoy *out of band*, somewhere your own admins and red team will find it, so nobody burns incident hours chasing your bait or helpfully "tidies it up". Refresh the file timestamps so the bait does not look abandoned. And fire each token yourself once a quarter, to prove the page still reaches a human at the far end.
A tripped canary is the one class of alert you escalate *without* triage. Its precision is effectively 1, so near enough every firing is real, and under-reacting to your highest-signal detection throws away the whole point of building it. Be precise about the words, though. The firing is a detection. The incident is confirmed afterwards, when you follow the key ID back to the host or repo you planted it on and find out what else that identity touched. You page first and you confirm second. That precision figure doubles as a benchmark: it is what a perfect alert looks like. The next lesson, Alert quality & tuning, is about dragging the other ninety-nine percent of your alert corpus, the rules that really do have to fight the noise war, as close to that bar as engineering allows.
Good deception is boring by design. Put honeytokens where attackers look: in a .env.example, in a database table called something like "backup", in a cloud secret named as if it guarded production payments. Then write down the legitimate scanners and humans who might stumble across them, as documented exclusions. If your CI secret scanner trips the canary every night, you do not have a canary. You have a pager. Prefer tokens whose use reveals intent. A cloud key that exists only to answer GetCallerIdentity still proves theft the moment anyone calls it.
Try this
Plant a canary AWS key in a lab repo, never a real account key. Watch CloudTrail for that access key ID, then prove to yourself that any use of it pages you.
$ # Generate a decoy-looking key id/secret for documentation only — do NOT create a real IAM user$ CANARY_AKIA=AKIAAAAAAAAAAAAAAAAA$ # Alert rule sketch (CloudTrail / SIEM): any event where userIdentity.accessKeyId == canary$ cat <<'EOF'title: Canary Access Key Usedlogsource: { product: aws, service: cloudtrail }detection:selection:userIdentity.accessKeyId: AKIAAAAAAAAAAAAAAAAAcondition: selectionlevel: criticalEOF# Trigger test: from a lab runner, attempt an AWS call with the decoy creds.# Expected: auth fails AND your canary alert still fires on the attempt (or on a real canary user).
Takeaway
A canary is a tripwire that nothing legitimate should ever touch. Hold its false-positive rate at zero, keep honeypots walled off from anything real, and treat every hit as hostile until you have proved otherwise.
Next step: put one honeytoken in a non-production secret store, alert on any use of it, and write down the scanner exclusions so your own CI does not burn the signal on night one.