An SSH-exposed honeypot whose shell is an LLM hallucination, driven by ARCP.
An attacker SSHes into :2222, gets a shell that feels like a misconfigured
Debian dev server. Every command they type is forwarded as an ARCP tool_call
event into a job; the agent invents plausible output via Ollama and the runtime
streams it back as a tool_result. Per-attacker persona state (fake $HOME,
fake .bash_history, fake uid) is keyed by the SSH session fingerprint and
survives reconnects.
Showcases: goroutine-per-connection concurrency, persistent per-job state
(session.resume), capability-scoped leases, per-attacker budget enforcement
(cost.budget), job.subscribe for an out-of-band defender dashboard, and the
full spec event vocabulary (thought, tool_call, tool_result, metric,
artifact_ref, progress, log).
cp .env.example .env
make upIn another terminal:
make ssh
# prints the connection string; password is anything
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@localhost -p 2222Run whoami, id, ls /home, cat /etc/shadow. Each appears in the
dashboard event stream as tool_call → tool_result. cat /etc/shadow
returns a plausible-looking hash file the agent invented. Disconnect, reconnect
within 24 hours, and the same fake home directory and history are intact.
ssh client ollama
(attacker) ▲
│ │ http
▼ │
ssh-frontend ──ws/arcp──▶ arcp-runtime
(per-conn (registers
goroutine) honeypot.shell)
│
│ job.subscribe
▼
arcp-client
(TUI dashboard,
writes loot/)
Four containers in docker-compose.yml: ollama, ssh-frontend,
arcp-runtime, arcp-client.
| Variable | Default | Effect |
|---|---|---|
HONEYPOT_SSH_PORT |
2222 |
Host port the ssh-frontend listens on. |
HONEYPOT_HOSTNAME |
debian-prod-04 |
Hostname baked into every persona. |
HONEYPOT_IDLE_TTL |
24h |
How long a paused job is resumable. |
HONEYPOT_PER_SESSION_BUDGET_USD |
0.10 |
Per-attacker cost.budget. 0.00 disables enforcement. |
HONEYPOT_MAX_TOKENS |
256 |
Max output tokens per synthesized command. |
RUNTIME_STORE_DIR |
/data |
Persona-store directory inside the runtime container. |
HONEYPOT_DROP_DIR |
/loot |
Captured artifact_ref payload dir in the client. |
OLLAMA_MODEL |
qwen2.5:1.5b-instruct |
See top-level README for alternatives. |
ARCP_SDK_VERSION |
latest |
Pin a specific module release for reproducible builds. |
The default USD:0.10 budget per attacker caps engagement at ~500 commands
when the demo is wired against a hypothetical hosted endpoint at $0.50/$1.50
per Mtok (~200 prompt + ~80 output tokens per command). Running locally
against Ollama, the cost figure is illustrative only — it still drives
cost.budget.remaining decrement so you can watch the budget mechanic in
action. Set HONEYPOT_PER_SESSION_BUDGET_USD=0.00 to disable enforcement;
don't deploy that way.
make upbrings up four containers; runtime logshoneypot.shellregistered.ssh root@localhost -p 2222with any password lands in a synthetic shell.- Dashboard updates within ~500ms with a new session.
whoami,id,cat /etc/shadoweach appear in the dashboard event stream.- Disconnect, reconnect within
HONEYPOT_IDLE_TTL: the persona is intact. - ~500 commands in one session exhausts the budget; the agent emits
Killed, the connection drops, the dashboard showsBUDGET_EXHAUSTED.
src/runtime/main.go—server.New, registershoneypot.shell.src/sshfrontend/main.go—golang.org/x/crypto/sshlistener, one goroutine per connection.src/client/main.go—bubbleteaTUI; subscribes to everyhoneypot.shelljob.src/honeypot/— Persona, persona store (JSON-on-disk), prompt builder, binary-drop detector, SSH version parser. All pure Go and covered by tests.
A small set of fast local tests (no docker, no Ollama, no network) covers the on-disk persona store, the prompt builder, the binary-drop detector, and the SSH version/fingerprint helpers.
make test
# or
go test ./...make verify