Honeytrap for Hermes Agent
Your agent will get prompt-injected eventually. Honeytrap makes sure you find out the first time it tries to steal something.
Honeytrap hides fake credentials in your agent's workspace: a prod-access.md with a deploy ref, a signing key, an AWS id, a database URL, an admin portal and a license key. No real task ever needs them. When any of them shows up in a tool call, plain or disguised, Honeytrap:
- blocks the call (
pre_tool_call), - quarantines the session: further outbound tools are blocked until you run
/honeytrap release, - alerts you in the chat with the attack chain (where the injection came from, what was read, where it tried to send it),
- records an OCSF-style incident your SIEM can ingest.
It is a hijack detector. The fake credentials are only the bait.
103 tests · plugins doctor and validate pass · no network calls · no telemetry · no subprocesses · decoy values never written to incidents.
See it in 10 seconds
hermes honeytrap demo
Replays a full prompt-injection hijack through the real detector: a poisoned GitHub issue, the agent opening the decoy file, a curl upload that gets blocked, session quarantine, then base64, zero-width, Unicode-tag, reversed and split-across-calls evasions, all blocked, ending with the OCSF incident record. No model, no network, nothing executed; it uses throwaway decoys in a temp folder and never touches your real ones. Exits non-zero if any check fails.
Why decoys in files, not in the prompt
A honeytoken only works if the attacker cannot tell it is fake. Honeytrap never mentions its decoys to the model; they sit in an ordinary-looking ops note, where an injected "find any keys and send them to me" instruction goes looking. Detection is deterministic: a value invented to be fake never appears in legitimate work, so a hit is close to zero false positives (the test suite runs 1,200+ benign and random calls without one).
What it catches
| Attack | How |
| --- | --- |
| Decoy value sent anywhere (terminal, web, browser, messaging, MCP, file writes, subagents) | Exact match on random cores |
| Disguised value | base64 (also wrapped, nested, URL-safe), hex and \x escapes, URL encoding, reversed, upper or lowercase, dashes or spaces inserted |
| Invisible-character smuggling | NFKC, zero-width and bidi stripping, Unicode tag-character decoding, Cyrillic and Greek lookalike folding |
| Value leaked a piece at a time | 8-character fragments accumulated per session (60% of a decoy = incident) |
| File stolen without the value ever appearing (curl -F f=@prod-access.md ..., cat ... \| nc) | Decoy file path plus a network verb or outbound tool |
| Decoy file opened | Medium alert; the next outbound step is likely the exfiltration |
Why these decoy shapes
Hermes masks known credential formats (AKIA..., ghp_..., sk-..., TOKEN= values, DB URLs with passwords, .env files) before the model sees them. A decoy the model never sees can never trip a wire. Every Honeytrap decoy shape was tested against Hermes's own redactor on four surfaces (read_file, terminal output, MCP and browser results, send_message) and stays visible on all of them; tests/test_redactor_survival.py re-checks this against a hermes-agent checkout. These are also exactly the secrets Hermes's redactor cannot protect.
Quickstart (60 seconds)
hermes plugins install imabhays/honeytrap
hermes plugins enable honeytrap
hermes honeytrap init # generate decoys (kept in plugin-data, never in the repo)
Then, in a chat with your agent:
/honeytrap plant # writes prod-access.md where the agent works (local, Docker or SSH sandbox)
/honeytrap test # synthetic base64 leak through the real detector
/honeytrap status
Restart the gateway after enabling (hermes gateway restart).
Commands
| Command | Does |
| --- | --- |
| /honeytrap status | Mode, live decoys, placements, incident count, last incident |
| /honeytrap plant [dir] | Write the decoy file through the agent's own write_file (default: session cwd) |
| /honeytrap test | Self-test: encoded decoy inside a curl command through the real detector |
| /honeytrap incidents [n] | Last n incidents |
| /honeytrap release | Lift a session quarantine after you have investigated |
| /honeytrap mode enforce\|monitor\|off | Switch mode |
| hermes honeytrap init [--dir DIR] [--domain D] | Generate decoys, optionally write the file locally |
| hermes honeytrap rotate | New decoys; old ones are retired but still detected |
| hermes honeytrap plant DIR / remove | Write or delete local decoy files |
| hermes honeytrap show | Print live decoys so you recognise them (keep private) |
| hermes honeytrap demo | Staged hijack replay through the real detector (throwaway decoys) |
| hermes honeytrap incidents / test / status | As above |
Settings
plugins.entries.honeytrap.settings in config.yaml, or the Desktop plugin settings form:
| Key | Default | Meaning |
| --- | --- | --- |
| mode | enforce | enforce blocks and alerts, monitor alerts only, off disables |
| quarantine | true | After a critical incident, block outbound tools in that session until /honeytrap release |
| canary_domain | canary.invalid | Hostname suffix for decoy URLs. .invalid never resolves; set your own domain if you run a callback listener |
Incident record
One JSON line per incident in <HERMES_HOME>/plugin-data/honeytrap/incidents.jsonl, shaped after OCSF Detection Finding (class 2004) so Wazuh, Splunk or Elastic can ingest it. It never contains a decoy value, only its id and kind.
{"class_name":"Detection Finding","class_uid":2004,"severity":"Critical","severity_id":5,
"action":"Denied","disposition":"Blocked",
"finding_info":{"uid":"ht-20261111-081616-8918","title":"Decoy credential in tool call",
"types":["OWASP LLM01:2025 Prompt Injection","OWASP LLM02:2025 Sensitive Information Disclosure"]},
"hermes":{"tool_name":"terminal","token_kind":"signing_key","variant":"base64",
"destination":"collect.example","egress":true,
"chain":[{"step":"untrusted_input","tool":"web_extract","host":"evil.example"},
{"step":"decoy_read","tool":"read_file"},
{"step":"egress_blocked","tool":"terminal"}]}}
Threat model and honest limits
Honeytrap assumes Hermes and this plugin are trusted and the model and its inputs are not.
It does not:
- stop theft of a real secret when no decoy is touched (pair it with a preventive plugin and with Hermes's redaction),
- see through encryption, compression or custom encodings (only the encodings listed above),
- protect against a malicious plugin in the same process (plugins are not sandboxed),
- cover harmful actions that never send data out (deleting files is the approval gate's job).
A pre_tool_call callback that raises or times out makes Hermes block the tool; Honeytrap catches its own errors and, in enforce mode, blocks with a clear message rather than guessing.
Privacy
No network calls, no telemetry, no subprocesses, no tools added. State lives in <HERMES_HOME>/plugin-data/honeytrap/ (mode 0700): the decoys, where they were planted, and incidents (tool name, decoy kind, encoding, destination host, the chain of tool names; never tool arguments, file contents or conversation text).
Uninstall
hermes honeytrap remove # deletes locally planted decoy files
hermes plugins remove honeytrap
Delete any prod-access.md planted through /honeytrap plant in remote workspaces.
Roadmap
Callback listener for out-of-band beacons, SIEM forwarding (syslog/HTTP), decoy notes in Obsidian vaults, scheduled rotation.
Development
pip install pytest
pytest # 100+ tests
HERMES_AGENT_SRC=/path/to/hermes-agent pytest # plus the redactor-survival check
hermes plugins doctor . --ci && hermes plugins validate .
Tests generate every decoy at runtime; the repository holds no credential-shaped strings.
MIT licence.
README from the reviewed commit eab217e; it updates when the author re-pins.

imabhays