Known Issues & Corrections
Current issues, by-design limitations, honeypot tells and the historical bugs already fixed
Operational
Dashboard drift
Offline and live dashboard state can diverge. Rebuild to NDJSON or export the live objects.
Operational
Same-host Suricata
Loopback capture can attribute alerts to container-internal addresses, so reputation may not fire locally.
By design
DB layer scope
The database honeypot layer swaps only the database, so it cannot isolate file-upload or code-execution attacks.
Deception
Honeypot tells
A blind pentest read the cookie name and TLS cert subject as honeypot evidence. The header oracle is now gated.
Fixed
fast.log parser
Eight capture groups into seven variables shifted fields silently. Replaced by structured eve.json.
Fixed
Session laundering
A single clean request could reset a risky session. Score accumulation now prevents it.
Operational Issues
- Dashboard generation drift: offline vs live dashboard state can diverge. Workaround: re-run
build_dashboards.py --ndjsonorexport_dashboards.py. - Timeline capture requires Kibana auth: embed-mode capture needs a Kibana login; configure an autologin script or use a manual session.
- Suricata same-host testing misattributes
src_ip: Suricata runs withnetwork_mode: host, so in same-host test setups an alert'ssrc_ipmay be container-internal.is_private_ip()filters RFC 1918 / loopback at ingestion, which means reputation may not fire for a purely local adversary — test from an external address to exercise it. - IP reputation is self-reinforcing per address: the router continuously re-imports each IP's reputation from Suricata's alert log, so once an address has been "hammered" during testing, even benign probes from it keep picking that reputation back up and its effective score does not fall — no matter how thoroughly session state is flushed. Validate decay from a clean address, or un-poison the test IP first (as the exploit report's decay scenario does).
- Transient load failures during pool autoscaling: while
pool_managerbuilds a pool, Docker creates its containers and veth interfaces, and Chromium cancels in-flight requests on each interface change (ERR_NETWORK_CHANGED). In the segmentation runs these loads all succeeded on retry — nothing is broken — but a client browsing during a build may see one failed load. It is expected churn, not a routing fault. production_eshopstays busy after it reports healthy: the services reports show the shop holding about one core for roughly 30 s after its healthcheck passes (WordPress warming up). Pages served in that window are slower; wait for the CPU to settle before timing anything.vector_outboundhealthcheck refused when the SIEM starts later: if the edge stack is started before the SIEM, the first healthcheck of the outbound shipper fails with "connection refused" and appears as a warning in the start report. It clears once Elasticsearch is up; start the SIEM first to avoid it.- Badge count differs from the report's Warnings: a Global Visualization tile counts every error-word line since the dashboard's log tail started (including replayed scrollback), the services report only lines logged during the recording. Both are correct for their scope.
By-Design Limitations
- Database honeypot layer isolates data-layer attacks only. With the database layer there is one eshop and only the database is swapped, so a file upload or a code-execution exploit has no separate backend to land in. Use the reverse-proxy layer when those attack classes matter.
- The device signature is a recovery path, not a precise fingerprint. The GDPR signature is derived from the screen size plus the server-observed IP, so it changes when either changes; it is designed to re-divert a returning adversary, not to uniquely track a device.
- Rule-based detection, by choice. Detection is explainable and rule-based rather than ML, so a genuinely novel attack with no matching pattern or behavioural signal can pass as low-risk until a rule is added.
- Absent plugins answer 4xx inside the honeypot. Many seeded exploits target plugins that are not actually installed in the decoy eshop, so the pool replies 404/400 to the exploit request. Routing still works — the attempt is detected and diverted — but a 4xx tells an adversary the plugin isn't there, which weakens the deception and captures little interaction. Install the targeted plugins, or add decoy endpoints, where high deception fidelity matters. The exploit report flags runs where a large share of exploits drew a 4xx.
Honeypot Tells
The blind pentest (Run 1) confirmed a blind tester could identify the deployment as a deception environment from the server's own responses. Status of each tell:
X-Route-Target/X-Risk-Scoreheaders: a live routing/score oracle returned to the client. Fixed — now emitted only for a request carrying a validX-Internal-Test-Authheader, gated behindINTERNAL_TEST_SECRET(fail closed when unset).HONEYPOT_SESSIONcookie name: hardcoded ininit.lua. Not changed — renaming touches session plumbing for every real client — and left as a documented limitation.O=HoneypotOrg, CN=localhostTLS certificate subject: produced by the committed cert-generation script, so it regenerates identically on a clean deploy. Use a realistic subject for any evaluation that must stay covert.- Decoy content artifacts: fabricated XML-RPC methods and an internal hostname leak in REST
guidfields were flagged; tracked for the thesis write-up.
Historical (Fixed)
- Fast.log regex parsing: eight capture groups into seven variables shifted every field. Fixed by switching to structured
eve.json. admin-ajax.phpfalse positives: a blanket rule flagged every request. Fixed: excluded fromis_admin_access()and given dedicated analysis.- Session score laundering: a single clean request reset CVE-triggered sessions. Fixed by score accumulation in
router.lua. is_vulnerable_plugin_request()hyphen escaping: an un-escaped plugin slug broke matching for hyphenated slugs. Fixed vialua_pattern_utils.escape_pattern().ngx.nullsentinel crash:red:get()returnsngx.nulluserdata for a missing key. Fixed with theif not raw or raw == ngx.nullidiom.- TLS GREASE broke fingerprint recovery: Chromium (QtWebEngine) inserts a random GREASE value into every ClientHello, so the passive recovery fingerprint changed per TLS connection and a cookie-cleared adversary was only sometimes re-recognised. Fixed:
session_identity_rules.strip_grease()removes GREASE from the TLS cipher/curve lists before hashing, verified against real QtWebEngine connections. - 502 after a pool scale-down: each nginx worker cached a session's pool route for
LOCAL_CACHE_TTL(300 s), and a removed pool with a stale health status was treated as healthy, so requests were proxied to a container that no longer existed. Fixed: a cached pool that is no longer in the ready list is treated as a cache miss / unhealthy, so the session is reassigned instead of returning 502. - Pool budget charged container limits, not real use: scaling stopped at roughly a quarter of the memory budget because each pool was charged its full container limit (≈1536 MB / 1.5 CPU) while measured use was ≈420–430 MB. Fixed:
POOL_RESERVE_MEM_MB/POOL_RESERVE_CPUSmake the per-pool charge configurable toward measured use, so far more pools fit within the same budget. - Abandoned waiting sessions held pools: a session that was diverted and then stopped browsing still counted as waiting for a pool, so pools were built for nobody. Fixed: only sessions seen within
POOL_WAITING_ACTIVE_SECONDS(60 s) count toward demand. - Spare built before the Compose pools existed: at start-up the manager counted no free pool and built a spare while the Compose-declared pool was still starting, wasting a build. Fixed by waiting for the declared pools before keeping
POOL_SPARESfree. - Slow, serial pool builds: one build at a time took about a minute each, so ten windows waited minutes on a borrowed pool. Fixed with
POOL_PARALLEL_BUILDS(2) and a leaner build; measured builds fell to 27 s (reverse-proxy layer) and 15 s (database layer).POOL_FAST_CLONEremains experimental and off by default. - Containers killed on stop: processes running as PID 1 ignored SIGTERM, so
docker compose downwaited out the grace period and SIGKILLed five containers (24 s). Fixed withinit: trueon the services;downnow finishes in about 8 s with nothing killed. - Kibana dashboards missing after start: the dashboard import ran while Kibana was still in preboot and silently did nothing. Fixed: the healthcheck waits until Kibana is actually available before the import job runs.
vector_outboundshipped nothing: the outbound shipper sat behind a Compose profile and pointed at a placeholder host, so the edge's logs never reached the SIEM. Fixed: it starts with the stack and targets the real Elasticsearch address.- Logs shipped twice and
infotagged as errors: nginx's error log was collected by two sources, and every line from it was labellederror. Fixed: one source, andnginx_error_innow keeps each line's own level. - HTTP 4xx counted as network failures: the test runner retried 4xx answers as if the connection had failed, inflating the failed-load numbers. Fixed: only real network errors are retried.
- Report kept polling after the run: the recorder went on sampling after the last step, stretching the timeline and the clean-up figures. Fixed: recording stops when the run's clean-up ends.
How these were found — round-by-round audits of each report against pool_manager's event log — is
described in Validation Rounds.
Reporting a New Issue
Record a new issue with its severity, the affected component or file, and a workaround if one exists; move it to Historical with a one-line resolution once fixed. Documentation corrections belong here too, with a reference to the page and the correction made. The source repository is at github.com/presshaven/presshaven.