A decoy alert may be unusually specific. That still doesn’t make it a verdict.
We wrote this article while developing CyberHalluciNet open source deception framework that is able to stop a.i threats and agent attacks.
At 2:13 a.m., an analyst receives an alert:
A source requested
/.git/configfrom an internal admin portal, attempted an SSH login assvc-backup, and ranidanduname -a.
It sounds serious. The temptation is to declare a breach, block the IP, and wake everyone up.
But three important questions remain:
- Was this actually malicious — or an authorized scanner?
- Was
svc-backupa planted credential or simply a guessed username? - Did any activity reach a real production system?
A deception alert becomes valuable only when an analyst can turn it into a defensible case.
This article teaches that process:
Touch → Validate → Reconstruct → Grade → Hand off → Authorize
No commercial platform is required. You can run the exercise as a tabletop or use the small loopback-only Python lab included below.
Press enter or click to view image in full size
What you’ll learn
By the end, you should be able to:
- Explain why deception alerts can be more specific than ordinary IDS alerts
- Distinguish simple contact from meaningful engagement
- Rule out common benign causes
- Reconstruct a session in plain language
- Assign a provisional evidence grade
- Write a ticket so another analyst can continue without reopening the original console
- Keep detection separate from authorization and containment
Part 1: Why deception alerts are different
Most security alerts describe behavior that could be legitimate. A new login location might be travel. PowerShell might be administration. A large upload might be a backup.
A properly managed decoy is different. It exists specifically because approved work should not need it.
Examples include:
- An admin portal absent from every runbook
- A credential not assigned to a real person or service
- A fake database table
- A document containing a canary token
- A tool endpoint that no approved agent should invoke
When something reaches one of these resources, the analyst can ask a narrower question:
Why did this source interact with something legitimate work should not use?
That specificity is valuable, but it is not proof of compromise.
Authorized scanners, inventory tools, configuration drift, curious employees, and deployment mistakes can all touch decoys.
The correct mindset is:
A decoy touch is strong evidence that deserves explanation, not automatic proof of an attacker.
Part 2: First validate the bait
Before investigating the source, confirm that the decoy is still genuinely a decoy. This step is easy to skip and embarrassing to discover later.
Ask:
- Is the decoy documented in an internal wiki?
- Was its address added to a monitoring check?
- Did an administrator include it in an automation script?
- Is it inside the scope of an authorized vulnerability scan?
- Did someone place it behind a production load balancer?
- Does a real workflow now depend on it?
If the answer to any of these is yes, the alert may reveal an inventory problem rather than hostile activity.
Common benign explanations
Internet-wide scanning
Typical signs:
- One or two requests
- Common paths such as
/.envor/.git/config - No follow-up
- Activity spread across many unrelated ports or hosts
Authorized vulnerability scanning
Typical signs:
- Source belongs to a known scanner
- Timing matches a scheduled scan
- Requests follow a repeatable template
- The same activity appears across the approved scan scope
Broken automation
Typical signs:
- Events recur at precise intervals
- Requests are nearly identical
- Source is an internal workload
- No interactive progression occurs
Asset discovery
Typical signs:
- Predictable probes
- Fixed cadence
- Known inventory or CMDB source
- Little interest in returned content
Internal testing
Typical signs:
- Activity occurs during a planned exercise
- Source belongs to a security team
- Timing and behavior match the test plan
None of these explanations should be assumed. They should be checked.
Part 3: Use a simple evidence ladder
The following G1–G4 model is a training rubric, not an industry standard. Map it to your organization’s existing severity and incident classifications.
G1 — Contact
The source touched a port, completed a handshake, or sent an isolated request.
Examples:
- TCP connection to a decoy SSH port
- One request for
/.env - A DNS query with no follow-up
- A generic service scan
Analyst action: Enrich, correlate, and usually observe. A suspicious path alone does not automatically make an event G2.
G2 — Engagement
The source interacted with the decoy over multiple meaningful steps.
Examples:
- Requested
/, then/robots.txt,/admin, and configuration paths - Attempted several credentials and reacted to responses
- Entered a synthetic shell and ran discovery commands
- Read or modified decoy content
Analyst action: Open a case and hunt for related activity on real systems.
G3 — Bait consumed
The source used material deliberately planted for detection.
Examples:
- A unique decoy credential was presented to another service
- A canary URL was requested
- A fake API key was exercised
- A honeytool was invoked
- A planted document triggered its callback
Analyst action: Escalate promptly, confirm the originating identity, and search for corroborating evidence.
A guessed username such as admin or svc-backup is not automatically G3. The analyst must establish that the exact artifact was planted and uniquely attributable.
G4 — Independently corroborated
A G3 event is supported by separate telemetry.
Get Whengomarket’s stories in your inbox
Join Medium for free to get updates from this writer.
Examples:
- EDR records the process that read the planted credential
- Identity logs show the same workload using the decoy secret
- Proxy telemetry records the canary callback
- Production ingress shows the same source and behavior sequence
Analyst action: Follow the organization’s incident process and seek authorization for appropriate containment.
Two rules keep the model honest
- Grades come from observed evidence, not vendor scores.
- Urgency does not change the evidence grade.
A risk score may help decide which case to read first. It should not turn G1 evidence into G3 evidence.
Part 4: Keep three operational lanes separate
Deception programs become dangerous when detection and response are treated as the same operation.
Sensing
The decoy records what happened:
- Source
- Time
- Protocol
- Requests
- Commands
- Credentials presented
- Bait artifacts accessed
It does not decide whether the event is an incident.
Investigation
The analyst:
- Validates the bait
- Reconstructs the timeline
- Checks benign explanations
- Assigns a provisional grade
- Correlates independent telemetry
- Documents uncertainties
Investigation does not silently change production state.
Authorization
An authorized person or workflow approves actions such as:
- Blocking a source
- Revoking a token
- Disabling an identity
- Isolating a host
- Escalating into incident response
This separation prevents a spoofed packet, scanner mistake, or telemetry bug from becoming an automatic denial-of-service mechanism.
Part 5: A practical triage sequence
Run these questions in order.
1. Is the bait still valid?
If the resource has become part of a legitimate workflow, correct the inventory problem before treating future touches as strong evidence.
2. Was this contact or engagement?
One generic request is usually G1. A sequence that reacts to the decoy and explores it may be G2.
3. Was planted material actually consumed?
Confirm that the credential, token, URL, or tool was deliberately planted and uniquely tracked. If not, remain at G2.
4. Can you identify the origin?
Look for:
- User identity
- Endpoint
- Workload or service account
- Agent session
- Proxy
- NAT gateway
- Vulnerability scanner
An IP address alone may represent many systems.
5. Does independent telemetry agree?
Check EDR, identity, proxy, DNS, firewall, and application logs.
6. What action is authorized?
Record what happened, what did not happen, and who must approve the next step.
Part 6: A safe local exercise
This server is intentionally minimal. It is a teaching aid — not a production honeypot, do NOT place it on the internet.
It binds only to 127.0.0.1, so it is not exposed to your LAN by default.
Save as decoy_server.py:
#!/usr/bin/env python3import json
from datetime import datetime, timezone
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlsplit
LOG_FILE = "decoy_events.jsonl"
class DecoyHandler(BaseHTTPRequestHandler):
server_version = "TrainingServer"
sys_version = ""
def record(self, status, category):
event = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"source_ip": self.client_address[0],
"source_port": self.client_address[1],
"method": self.command,
"path": urlsplit(self.path).path,
"user_agent": self.headers.get("User-Agent", "Unknown")[:256],
"status": status,
"category": category,
}
with open(LOG_FILE, "a", encoding="utf-8") as log:
log.write(json.dumps(event) + "\n")
print(
f"[{event['timestamp']}] "
f"{event['source_ip']} {event['method']} {event['path']}"
)
def do_GET(self):
path = urlsplit(self.path).path
if path == "/":
body = b"<h1>Internal Administration</h1>"
self.record(200, "surface_contact")
self.send_response(200)
elif path in {"/.env", "/.git/config", "/admin"}:
body = b"Not found"
self.record(404, "bait_path_probe")
self.send_response(404)
else:
body = b"Not found"
self.record(404, "general_request")
self.send_response(404)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *_):
pass
if __name__ == "__main__":
address = ("127.0.0.1", 8080)
server = ThreadingHTTPServer(address, DecoyHandler)
print("Training decoy: http://127.0.0.1:8080")
print("Press Ctrl+C to stop.")
try:
server.serve_forever()
except KeyboardInterrupt:
pass
finally:
server.server_close()
Start it:
python3 decoy_server.pyGenerate a small session from another terminal:
curl -A "training-client/1.0" http://127.0.0.1:8080/
curl -A "training-client/1.0" http://127.0.0.1:8080/robots.txt
curl -A "training-client/1.0" http://127.0.0.1:8080/admin
curl -A "training-client/1.0" http://127.0.0.1:8080/.git/configInspect decoy_events.jsonl.
The important lesson is not that four requests are “an attack.” It is that an analyst now has an ordered sequence to validate and explain.
Part 7: Reconstruct the story before grading it
Consider this fictional timeline:
12:01:03 GET / UA: python-requests/2.31
12:01:04 GET /robots.txt
12:01:09 GET /admin 401
12:01:11 GET /.git/config 404
12:05:40 SSH login as svc-backup
12:05:52 Synthetic shell: id; uname -a; cat /etc/passwd
12:07:18 Outbound connection attempt to 198.51.100.7:443, blockedBefore writing a ticket, summarize it in one sentence:
A source performed web discovery against a decoy, then entered a synthetic SSH session as
svc-backup, ran basic host-discovery commands, and attempted an outbound connection that was blocked.
Notice what this sentence does not say:
- It does not call the source external without evidence.
- It does not claim a production breach.
- It does not claim data exfiltration.
- It does not name an actor or malware family.
- It does not call
svc-backupa canary until that fact is verified.
The provisional grade is G2.
It becomes G3 only if the exact credential was deliberately planted and its use can be attributed to that planted artifact.
Part 8: Turn the narrative into a ticket
Title: [RESEARCH LAB] G2 decoy engagement — HTTP discovery and SSH interactionSummary
A source performed web discovery against two decoy faces, entered a synthetic
SSH session as "svc-backup", ran basic host-discovery commands, and attempted
an outbound connection that was blocked.
Timeframe (UTC)
2026-09-20T12:01:03Z to 2026-09-20T12:07:18Z
Environment
Isolated research lab; no production impact observed.
Source
IP: 203.0.113.45
User-Agent: python-requests/2.31
Identity: not yet established
Case ID: DEC-2026-09201
Observed sequence
1. Requested /, /robots.txt, /admin, and /.git/config
2. Attempted SSH authentication as "svc-backup"
3. Entered a synthetic shell
4. Ran: id, uname -a, cat /etc/passwd
5. Attempted a connection to 198.51.100.7:443; connection was blocked
Provisional evidence grade
G2 — verified multi-step engagement.
Deciding question
Was the exact "svc-backup" credential deliberately planted?
If confirmed, reassess as G3.
Explicit non-claims
- No threat-actor or malware attribution
- No production compromise established
- No data exfiltration observed
- No claim that the source IP identifies one endpoint or person
Actions already taken
Observed and documented only. No production block or identity revocation.
Prioritized asks
1. Verify whether "svc-backup" was planted and uniquely tracked.
2. Identify whether the source belongs to an authorized scanner or test system.
3. Search for the combined path sequence—not merely the common User-Agent—on
relevant production ingress.
4. Check EDR, proxy, and identity telemetry for the same credential, destination,
and timeframe.
5. Confirm the decoy remains absent from monitoring, automation, and runbooks.
This ticket gives the next analyst:
- A concise story
- An ordered timeline
- A provisional grade
- The fact that would change the grade
- Explicit limits on the assessment
- Prioritized next actions
That is much more useful than “HONEYPOT ALERT — BLOCK NOW.”
Part 9: Run it as a 60-minute team exercise
10 minutes: Bait inventory
Each analyst lists three resources that approved workflows should never touch.
For each resource, identify:
- Owner
- Purpose
- Expiration date
- Approved scan exclusions
- How accidental legitimacy would be detected
10 minutes: One-sentence narrative
Give the team the fictional timeline. Everyone writes one neutral sentence describing what happened.
Remove unsupported conclusions.
10 minutes: Grade and defend
Each analyst assigns a provisional grade and identifies the single fact most likely to change it.
10 minutes: Peer-review gate
A partner must be able to answer:
- What happened, in order?
- What remains uncertain?
- What does the ticket explicitly avoid claiming?
- What is the first follow-up action?
- Was any response action authorized?
If the reviewer cannot answer, revise the ticket.
5 minutes: Retrospective
Discuss:
- Which fact was hardest to state objectively?
- Which decoy is most likely to become accidentally legitimate?
- Where could your current process confuse detection with authorization?
Part 10: Common mistakes
“A honeypot hit means we are breached”
It may indicate compromise, but it can also indicate scanning, testing, drift, or poor bait governance.
Better: state what was observed and what remains unproven.
Automatically blocking every source
A spoofed or shared source can turn automatic blocking into denial of service.
Better: use graded evidence and authorized response workflows.
Searching only a generic User-Agent
Values such as python-requests are common and easy to change.
Better: search combinations: path sequence, timing, identity, destination, source context, and protocol behavior.
Treating a guessed username as a planted credential
A common service-account name is not automatically a canary.
Better: verify that the exact credential was intentionally planted and uniquely tracked.
Keeping undocumented bait forever
Orphaned decoys lose evidentiary value and confuse future analysts.
Better: assign an owner, review date, and expiration policy.
Writing tickets as log dumps
The next analyst should not need to reconstruct the case from raw events.
Better: provide one sentence, an ordered sequence, a grade, uncertainties, and prioritized asks.
Final takeaway
Press enter or click to view image in full size
The engineering question for deception is:
Does the decoy appear convincing?
The blue-team question is different:
Can we turn its signal into a clear, defensible decision?
A good deception alert does not end with a dashboard notification. It ends with a ticket that states what happened, what the evidence supports, what remains uncertain, and what an authorized human should do next.
That is how a touch becomes useful.