Howdah 1.2 is released. Layer 1 refuses abusive request shapes; Layer 2 reads the content of request values and logs what looks like an attack. Both watch the request on the way in. Layer 3 stands somewhere new: at the exact moment PHP is about to do something dangerous — run a shell command, unserialize attacker data, include a file, evaluate code — and checks it against a flat, operator-set policy. And, as with every Howdah layer, it watches first and tells you, before it ever gets in the way.
What's in 1.2: Layer 3, sink hardening
At a guarded sink, there is no guessing about whether something dangerous is happening — it is. That makes a decision there more defensible than an input signature. Howdah 1.2 guards four:
- Command / code execution —
system,exec,shell_exec,passthru,popen,proc_open. An anchored allowlist says which commands are fine; anything else is logged, or refused when you arm it. unserialize— object-injection payloads. Off by default, because legitimate object unserialization is everywhere; you opt it in to hunt.include/requireandeval— on PHP 7.0+. Includes outside an allowed directory, and everyeval, are flagged.
This is flat policy at the sink, in the lineage of Suhosin and Snuffleupagus — not taint tracking, which no tool can do honestly or completely. And it is sink hardening, not XSS: Layer 3 watches what the application does, not what it emits.
It logs. It does not block by default.
Layer 3 has its own mode, sinks.mode, which starts at detect (log only) and is independent of the global howdah.mode — so flipping howdah.mode = block does not quietly arm a sink. You read your logs, decide a sink is safe to enforce for your app, and arm that one explicitly (after setting its allowlist). Nobody upgrades to 1.2 and finds their site started refusing calls.
When a sink is armed to block, Howdah does not take the request down mid-execution. It neutralizes the call the way PHP's own disable_functions does: the dangerous function returns false (or the include/eval is skipped) and the script keeps running. The operation simply never happens.
And the matched value is never logged: an event records the sink and the function name (plus the class name for unserialize), never the command line, the include path, or the serialized payload. A flagged secret never ends up in your logs.
Honest about scope
Flat-policy virtual patching is best-effort and bypassable — an application can reach a dangerous effect by a route Howdah does not guard — which is why it logs first. A few honest edges, stated plainly: include and eval guards need PHP 7.0+ (the 5.x opcode VM does not expose what we need; command execution and unserialize still work on 5.4+). proc_open with an array command, and any non-string first argument, are not inspected. And phar:// deserialization and SSRF are not in 1.2 — they are the Layer-3 roadmap, and this site never claims a layer is here before it is.
Every guard ships with a test that proves what it catches and what it leaves alone, across PHP 5.6, 7.4 and 8.3; the block path was audited leak-free before release. Our reproducers recreate only the harmless shape of an attack, never a working exploit.
Get it
If you are already on Howdah, apt-get update && apt-get upgrade (or the yum equivalent) brings in 1.2; Layer 3 arrives in detect mode, logging only. New here? Start at the install picker, read the configuration reference for the [sinks] settings, and check the coverage table and the per-version sink matrix for your PHP version. The source and this site are on GitHub — if a sink flags something interesting on your traffic, we would like to hear about it.