ESC
> PressHaven alpha
Home › Docs ›Engine › Honeypot Layers

Honeypot Layers

Where the deception happens: separate decoy backends, or one eshop with a swapped database

Two Layers of Deception

Both layers protect the same target demo application: a WordPress + WooCommerce storefront (a coffee eshop with real products and carts). The honeypot is built for and around that shop — the reverse-proxy level clones the whole WooCommerce backend per session, and the database level swaps only the WooCommerce database — so the deception is tuned to the same WordPress and WooCommerce surface an adversary would actually probe.

The ids stack can run its honeypot at one of two layers. Both are the same Compose project — same project name, same .env, same data — and differ only in which Compose file is used and, therefore, in where a suspicious session is diverted. You pick the layer from the dashboard's Services page ("Honeypot layer"); the choice is persisted in AppState.honeypot_layer and read by core.docker_ctl.Target, so every docker compose call the dashboard makes follows it automatically.

Choosing a Layer

Reverse-proxy levelDatabase level
Compose filedocker-compose.ymldocker-compose.db-proxy.yml
What is switched per suspicious sessionThe whole backend (separate honeypot WordPress pools)Only the database (one eshop, production vs honeypot DB)
Honeypot poolsPer-session pools, autoscaledNone — POOL_COUNT is 0
Resource cost per pool~1536 MB / 1.5 CPU (eshop + database)~512 MB / 0.5 CPU (database alone)
Isolates file / upload / RCE attacksYes — the adversary runs against a real decoyNo — there is no separate backend to land in
Honeypot data volumePer-pool databaseshoneypot_database_proxy_data

Both layers have been segmentation-validated: a ten-window run on each layer gave every flagged session a honeypot pool of its own (verdict EXCLUSIVE). See Validation Results for the recorded figures. The database-level run exercises only data-layer patterns and checks the shared filesystem afterwards, because it has no separate backend to contain a file write (ids/db_proxy/COVERAGE.md).

Budget vs. measured cost

The ~1536 MB / 1.5 CPU above is the hard container ceiling per reverse-proxy pool (1,024 MB eshop + 512 MB database), and the scaling budget charges a deliberately generous 768 MB per pool as headroom. At rest a pool uses far less: in the 2026-10-24 ten-window run the pool containers peaked at a measured ~469 MB each (4,687 MB across 10 pools). Adding a pool took a median 27 s (max 40 s), dominated by the template copy, and a diverted page load was as fast as production (median 372 ms vs 360 ms). See Validation Results for the full figures.

Reverse-Proxy Level

The default, recommended layer. Suspicious sessions are routed to separate honeypot WordPress containers — the per-session pools described on the Engine page. Because each diverted adversary gets a full, real decoy eshop and database of their own, every class of attack has somewhere to "work": upload a web shell, exploit an RCE, dump the decoy database — all against fake data, fully isolated from production and from other adversaries.

Database Level

A lighter alternative. One production_eshop serves every session, and wp-content/db.php acts as a per-request database router: a suspicious session's requests are connected to the honeypot database, a clean session's to the production database. The routing decision still comes from the proxy's Lua logic; proxy_params sets X-Honeypot-Backend from that decision and always overwrites the header, so a client cannot forge it.

What this layer can and cannot catch

Because there is only one eshop and only the database is swapped, this layer isolates database-layer attacks (for example SQL injection reading the decoy database) but not attacks that need a separate backend — a file upload or a code-execution exploit has no honeypot container to land in. Use it when resources are tight or the focus is data-layer deception; prefer the reverse-proxy level otherwise.

Switching Layers

Use the Services page, or by hand from ids/:

docker compose up -d --remove-orphans                                # reverse-proxy level
docker compose -f docker-compose.db-proxy.yml up -d --remove-orphans # database level

Both layers share the project name, so --remove-orphans replaces the services that differ and removes the other layer's extra services. The honeypot database volume is the exception: the database layer uses its own honeypot_database_proxy_data.

What the Database Layer Overlays

docker-compose.db-proxy.yml bind-mounts whole-file replacements from ids/db_proxy/ over the shared copies, at the same relative paths:

  • reverse_proxy_enhanced/nginx.conf — no pool upstreams; every location proxies to production_backend.
  • reverse_proxy_enhanced/proxy_params — sets X-Honeypot-Backend from the Lua routing decision, always overwriting it.
  • lua/health_check.lua and lua/init_worker.lua — set POOL_COUNT to 0 so nothing probes honeypot eshops that don't exist; health is mirrored from the database pools in Redis instead.
  • scripts/replicate_content_to_honeypot.sh — syncs content into the single honeypot_database.
  • honeypot_database_migrations/01_clean-honeypot-data.sql — DELETEs that tolerate missing tables, for the clone-of-production database.

Two files live in the shared production_eshop_files/ because Docker cannot overlay paths that don't exist in the default tree; they do nothing unless HONEYPOT_LAYER=database, which only this layer's Compose file sets: wp-content/db.php (the per-request database router) and wp-content/mu-plugins/honeypot-db-backend-header.php (adds X-DB-* debug headers for the dashboard; requires INTERNAL_TEST_SECRET). See ids/db_proxy/README.md for the complete file list.