Honeyprompt: LLM-first honeypot that talks back A new open-source deception framework called Honeyprompt uses large language models to power interactive honeypots that engage attackers with realistic responses. Developed by web developers, it supports SSH, HTTP, TLS, TCP, and Telnet, and can be deployed as a small container or single binary on low-end hardware. The tool aims to improve threat detection by making decoys more convincing and reducing the need for manual configuration. "The honeypot that talks back ™" Introducing honeyprompt , an LLM-first deception framework made by/for web developers. Supports all major cloud and local LLM providers. SSH, HTTP, TLS, TCP, Telnet and more. It ships as a small container and a single static binary and keeps every knob in one honeyprompt.yaml . No plugins to compile, no database to run, easily extendable and can be deployed on low-end hardware. A demo instance is available at 172.233.151.216 , with the unauthenticated web panel here: http://172.233.151.216:9090 http://172.233.151.216:9090 . It is a public instance of honeyprompt running on a cheap Linode VPS, with openrouter/free as the sole LLM provider/model. For easiest setup in 2026, we recommend Docker and OpenRouter/ openrouter/free as the LLM provider. All major cloud and local LLM providers are supported. Three files and one command stand up the full default deployment: seven LLM-backed decoys, durable event storage and the operator panel. 1. Grab the default config, compose file, and env template: if you don't have Docker: curl -fsSL get.docker.com -o get-docker.sh && sh get-docker.sh mkdir honeypot && cd honeypot wget https://raw.githubusercontent.com/alectrocute/honeyprompt/main/honeyprompt.yaml wget https://raw.githubusercontent.com/alectrocute/honeyprompt/main/compose.yaml wget -O .env https://raw.githubusercontent.com/alectrocute/honeyprompt/main/.env.example Or clone the repo and cd into it — same three files. 2. Fill in .env. Two values are required: OPENROUTER API KEY=sk-or-... use a dedicated key with a spend limit HONEYPROMPT PANEL PASSWORD=changeme basic-auth password for the panel 3. Start it: docker compose up -d 4. Poke it: curl http://localhost/ default nginx welcome page no LLM ssh -p 2222 root@localhost password: root — then type anything curl http://localhost:2375/v1.54/containers/json "exposed" Docker API 5. Watch it happen in the read-only panel at http://127.0.0.1:9090 http://127.0.0.1:9090 sign in as admin with your panel password . Every connection, credential, and command streams in live. If you are deployed to a remote host, you'll need to expose the :9090 port in compose.yaml /alectrocute/honeyprompt/blob/main/compose.yaml . This is not recommended for production deployments. Pin a numbered release instead of latest for production deployments — set HONEYPROMPT IMAGE in .env . The honeyprompt.yaml /alectrocute/honeyprompt/blob/main/honeyprompt.yaml you just downloaded is a fully annotated showcase. It ships profiles for: A generic corporate web server — port 80, the widest net; / serves the stock nginx welcome page instantly, and deeper paths fall through to the LLM for full HTML/CSS intranet pages, login forms, and admin panels built to keep the attacker clicking. MCP / agent gateways — Streamable HTTP discovery, OAuth metadata, JSON-RPC tool calls, and tempting production tools. Docker Engine API 29.5 — the unauthenticated port 2375 surface used by real cloud worms. Kubernetes API v1.36 — namespace, workload, Secret, ConfigMap, and RBAC discovery. Ubuntu 26.04 AI build infrastructure — SSH, GPU workloads, Docker, kubeconfigs, CI state, and provider credentials. Redis 8.8 — common RESP probes used for credential theft, persistence, and lateral movement. Industrial edge / OT — an intentionally legacy Telnet management plane, because modern defense still has to catch attacks against old infrastructure. The versions are contemporary, but the personas intentionally look useful and slightly exposed. A good decoy should attract interaction, not advertise perfect hardening. Important Even if you're using LLMs, determine the most frequently used paths and add static rules for them. This will save you massive amounts of LLM tokens and speed up responses to requests that are not worth the cost of an LLM call. Random examples: whoami , health checks, favicon, version probes, etc. This minimal honeyprompt.yaml fakes an SSH box with two static rules and no LLM: panel: enabled: true address: "0.0.0.0:8080" events: buffer: 2000 file: /data/events.jsonl durable attacker activity services: - protocol: ssh address: "0.0.0.0:2222" description: "Ubuntu 26.04 LTS build runner" serverName: "gpu-runner-07" passwordRegex: "^ root|admin|123456 $" which passwords "work" commands: - regex: "^whoami$" handler: "root" - regex: "^ .+ $" handler: "bash: command not found" docker run --rm \ -p 2222:2222 -p 8080:8080 \ -v "$ pwd /honeyprompt.yaml:/etc/honeyprompt/honeyprompt.yaml:ro" \ -v honeyprompt-data:/data \ alectrocute/honeyprompt:latest For a persistent deployment, use the included compose.yaml /alectrocute/honeyprompt/blob/main/compose.yaml . The deployment guide /alectrocute/honeyprompt/blob/main/DEPLOYMENT.md covers Docker Hub releases, required GitHub secrets, port and firewall setup, panel access over SSH, upgrades, rollback, event storage, and isolation. A honeypot only has to do one thing well: stay convincing long enough that the attacker keeps typing. Every command they run is intelligence — the tools they reach for, the credentials they reuse, the CVEs they assume you haven't patched. Static honeypots break character the moment someone runs a command the author didn't anticipate. honeyprompt hands that moment to an LLM, so the shell answers dmesg | tail or cat /etc/shadow the way a real one would, and the session keeps going. This is the part worth understanding up front, because the two are deliberately kept apart: - Deception events: Every attacker interaction: connections, auth attempts, each command or request, the response honeyprompt sent back, which provider and model answered, and how long it took. This is your threat intel. It's held in a bounded in-memory buffer for the live panel and you can persist all of it to disk. - Operational logs: Startup, which ports it bound, provider failures, shutdown, internal errors. This is what you read when the runtime misbehaves. It has nothing to do with attacker activity. You configure them separately: The honey: attacker activity. events: buffer: 2000 recent events kept in memory for the panel file: /data/events.jsonl persist every event as JSON Lines The runtime's own diagnostics. logging: level: info debug | info | warn | error format: text how it looks on the console: text human or json file: /data/honeyprompt.log optional; on disk it's always JSON events.jsonl is one self-contained JSON object per line — ready to tail -f , ship to a SIEM, or replay with jq . The Docker commands above mount the named volume honeyprompt-data at /data , so events survive container replacement. Both files are appended to and flushed on a clean shutdown. format only affects how operational logs are rendered to the console; the operational log file , when enabled, is always structured JSON so it's easy to parse. An optional, read-only dashboard streams deception events as they happen, breaks them down by protocol, and exports everything to JSON with one click: panel: enabled: true address: "0.0.0.0:8080" auth: optional basic auth username: admin password: "${HONEYPROMPT PANEL PASSWORD}" The dashboard is plain HTML, CSS, and JavaScript src/panel/assets /alectrocute/honeyprompt/blob/main/src/panel/assets embedded into the binary. Leave auth undefined to disable authentication.Each provider is its own module with its own timeouts, retries, rate limits, and headers. Keys come from the environment. Out of the box: | Provider | type | Notes | |---|---|---| | Ollama | ollama | Local models; defaults to localhost:11434 | | llama.cpp | llamacpp | Local server OpenAI endpoint | | OpenAI | openai | OPENAI API KEY | | Azure OpenAI | azure | needs azure.deployment + azure.apiVersion | | OpenRouter | openrouter | OPENROUTER API KEY | | Anthropic | anthropic | ANTHROPIC API KEY | | Google Gemini | google | GEMINI API KEY | | Anything OpenAI-shaped | openai-compatible | point baseUrl at your gateway | Configure providers, pick a pool.strategy round-robin , weighted , random , or failover , and honeyprompt spreads traffic across them. If the chosen provider times out or returns a retryable error, honeyprompt transparently falls over to the next one — a dead backend never takes the honeypot offline. Non-retryable errors a bad API key, say stop the cascade so you find out instead of silently draining quota. Services use the global pool unless they name their own provider subset: llm: enabled: true providers: local-ollama one name: force this service to this provider List several names to keep load balancing and failover, but only within that subset: llm: enabled: true providers: openai-primary, openrouter-backup When several services should share the same provider group — or a subset needs its own strategy instead of the global one — define a named pool . A pool has a name, a strategy, and an ordered provider list, and a service references it by name anywhere it would name a provider: pools: - name: cheap-first strategy: failover try the local model first, fall back to the paid API order: local-ollama, openrouter - name: spread strategy: round-robin order: openrouter, openai services: - protocol: ssh ... llm: enabled: true providers: cheap-first a pool name, in place of a provider - protocol: http ... llm: enabled: true providers: spread A pool name must be the only entry in providers — mixing a pool with individual providers in one list isn't allowed, since it would be ambiguous which strategy wins. Pool names live in the same namespace as provider names and can't collide with them. When "match a regex" or "ask the model" isn't enough, hooks let you splice your own TypeScript into the request and response path. A hook can rewrite the prompt before it reaches the model, or rewrite the reply before it reaches the attacker. js import { registerHook } from "./src/engine/hooks.ts"; registerHook { name: "fake-latency-notice", transformResponse response, ctx { if ctx.protocol === "ssh" && /rm -rf/.test ctx.input { return "rm: cannot remove '/': Operation not permitted\n"; } return response; }, } ; Reference it by name from any service's hooks: list. A built-in redact-secrets hook ships enabled in the example config so the model can never echo a real credential back out. Prometheus metrics are served at /metrics on the panel unauthenticated, so scrapers just work : honeyprompt events total{protocol="ssh"} 412 honeyprompt llm requests total{provider="openai",protocol="ssh"} 118 honeyprompt auth attempts total{protocol="ssh"} 87 honeyprompt engine errors total{protocol="http"} 0 Contributing or want a native binary? You'll need Deno https://deno.com 2.x — the only dependency. deno task check type-check deno task lint deno task fmt deno task test unit + integration tests deno task start -- --config honeyprompt.yaml run locally deno task dev -- --config honeyprompt.yaml run with file watching deno task compile - ./dist/honeyprompt self-contained binary deno compile bakes the runtime, panel assets, and all into one executable with no dependencies. Prebuilt binaries for Linux, macOS, and Windows are attached to every tagged release /alectrocute/honeyprompt/releases . CI runs formatting, lint, type-check, tests, config validation, a cross-platform compile , and a Docker build on every push. Tagging vX.Y.Z cuts release binaries and publishes the provenance- and SBOM-attested multi-arch image to alectrocute/honeyprompt https://hub.docker.com/r/alectrocute/honeyprompt . honeyprompt run --config