Released

Howdah 1.3.0 is released — Layer 1 (request-shape limits), Layer 2 (input inspection, log-only) and Layer 3 (sink hardening, log-only). Howdah starts in detect mode: the critical limits block, everything else only logs. Read the coverage table for exactly what has been tested on your PHP version before you switch rules to block.

Open source · defensive · built for PHP 5.4 to 8.5 (see coverage for what is tested)

Howdah stops abusive request floods inside PHP, before they exhaust memory.

Upload-part floods are the shape of the PHP2Shell attack. Howdah is a firewall that loads into PHP itself, so it protects even end-of-life PHP (5.4 to 8.1) that will never get the official fix — on the versions tested in the coverage table.

Built for sysadmins who run PHP on their own servers, including servers stuck on old PHP. Howdah is defensive only: it recognizes and refuses abusive requests, nothing else.

What is Howdah, and why the name?

Howdah is a native extension for PHP. When a web request arrives, PHP starts parsing it before any of your code runs. Howdah watches that parsing step and refuses requests that are built to exhaust memory, while PHP is still working through them.

Version 1.0 completes Howdah's first layer: limits on multipart/form-data uploads, on the number and shape of input variables, and on request headers. The critical resource-exhaustion limits block by default, even in detect mode; everything else starts in log-only mode so you can watch it before you let it block. Version 1.1 adds Layer 2, which inspects request content for injection patterns and logs them; it does not block by default. Version 1.2 adds Layer 3, which guards four dangerous PHP sinks, and also logs first. Version 1.3 extends it with phar:// and SSRF guards (off by default) and brings include and eval to PHP 5.x.

It is not a replacement for patching. Upgrade PHP when you can. Use Howdah when you cannot, or until you can.

How it works: four layers

Howdah's protection is organised in four layers. Version 1.0 delivered Layer 1; version 1.1 added Layer 2; version 1.2 added Layer 3, extended in 1.3. Layer 2 detects and logs SQL injection, path traversal and command/code injection in request values; Layer 3 guards dangerous sinks (command execution, unserialize, include, eval, and in 1.3 phar:// and SSRF); neither blocks by default. Layer 4 is the roadmap, and this page never claims it is here yet.

  1. Layer 1: Request parsing Active in 1.0

    Upload/multipart floods (PHP2Shell), input-variable floods, deeply nested arrays, oversized headers — refused while PHP parses the request.

  2. Layer 2: Request inspection Active in 1.1 · log-only

    Content inspection of decoded GET/POST/cookie/path values for SQL injection, path traversal and command/code injection. It detects and logs; it does not block by default.

  3. Layer 3: Runtime checks Active in 1.2 · log-only

    Sink hardening: guards command execution, unserialize, include and eval with a flat allowlist policy, plus phar:// and an SSRF egress guard (both off by default, new in 1.3). It detects and logs; it does not block by default.

  4. Layer 4: After a breach Planned

    Blocking execution of PHP from upload dirs, webshell behaviour, unexpected outbound connections.

A request enters at Layer 1. Because Howdah runs inside PHP's own request handling, Layer 1 can count upload parts and input variables as PHP reads them and stop the request early — something a PHP-level script or a network box cannot do (see the PHP2Shell story below).

Layer 1 — what Howdah enforces

These are the Layer-1 default limits. A request that crosses a limit has the offending data dropped and is logged; in block mode (or for a Critical rule in any mode) it is also refused with HTTP 403/413 and a request ID that matches the log line. Every limit is configurable in a TOML file; the config editor builds and validates one in the browser. Numbers below are the shipped defaults.

Multipart upload limits

Multipart upload default limits
LimitDefaultStops
max_parts2000Floods of form-field parts
max_files200File-part floods (the PHP2Shell shape)
max_filename_bytes512Oversized filenames
max_field_name_bytes1024Oversized field names
max_parse_memory_bytes256 MiBTotal body Howdah will process

Enforced while PHP parses the body, before per-part memory is allocated — the 403/413 lands before your script runs. max_part_headers and max_part_header_bytes also exist but are best-effort (only the Content-Disposition header reaches the extension).

Input-shape & request-header limits

Input-shape and request-header default limits
LimitDefaultStops
max_input_vars2000Variable / hash-collision floods
max_input_nesting_depth64Deeply nested arrays
max_input_key_bytes1024Oversized variable names / keys
max_request_headers200Request-header floods
max_request_header_bytes16 KiBOversized header lines

Honest caveat: for these, the guaranteed mitigation is that Howdah drops the offending variable (bounding the symbol table) and logs it. The 403 is best-effort and may arrive at request shutdown on SAPIs that build superglobals lazily. See the rule reference.

Modes & severity. Critical rules (the unambiguous resource-exhaustion shapes) block even in detect mode; Standard rules log in detect and block only in block mode. This is why the recommended “start in detect” rollout never leaves you open to PHP2Shell during the first hours. Full directive and rule reference: Configuration.

Layer 2 — what Howdah inspects 1.1 · log-only

Layer 1 limits the shape of a request. Layer 2 looks at the content of decoded request values: GET query parameters, POST fields, cookies and the URL path, through the same request hook Layer 1 uses. It detects and logs; it does not block by default.

Three classes of pattern

Layer 2 detection classes with illustrative shapes
ClassRule nameExample shape
SQL injectionsql_injection' OR 1=1 --
Path traversalpath_traversal../../etc/passwd
Command / code injectioncommand_injection; cat /etc/passwd

The examples illustrate each shape; they are not a complete list of what is matched. Cross-site scripting (XSS) is not covered.

How detection works

A small, high-confidence signature scan that runs in linear time, so there is no catastrophic backtracking. Work is bounded per value and per request, so the scan itself cannot be turned into a denial of service; values past a budget are not scanned.

Log-first, on purpose

Layer 2 has its own inspect.mode, which defaults to detect (log only). It is independent of the global howdah.mode: setting howdah.mode = block does not arm it. To block, an operator sets inspect.mode, or one class, to block explicitly.

What a detection looks like

One JSON log line carrying the rule class, the source (get, post, cookie or path) and the variable name. The matched value is never logged.

{"ts":"2026-10-23T09:41:02Z","id":"91ab3f","rule":"sql_injection","action":"log","ip":"203.0.113.7","method":"GET","uri":"/search.php","reason":"sql_injection pattern in get variable 'q'"}

Illustrative. The query string is stripped from the logged uri; for a path detection the path itself is the logged URI.

Honest caveat. Signature inspection is best-effort and can be bypassed, which is exactly why Layer 2 logs rather than blocks. Use the log to see what is happening, and fix the application (parameterized queries, path normalization, no shell calls on user input). To configure or enable it, see the [inspect] section of the configuration reference.

Layer 3 — the sinks Howdah guards 1.3 · log-only

Layer 2 looks at request values. Layer 3 looks at what the application does with them: it guards dangerous PHP functions, called sinks, with a flat allowlist policy in the style of Snuffleupagus. Version 1.3 actively guards six sinks. It detects and logs; it does not block by default. This is sink hardening, not XSS or output inspection.

Six active sinks

Layer 3 sinks guarded in 1.3
SinkFunctionsDefault
Command executionsystem, exec, shell_exec, passthru, popen, proc_openLogs when not allowlisted
UnserializeunserializeOff; opt in to log
Includeinclude, requireInert until allow_paths is set
EvalevalLogs when reached
Pharphar:// via stream functions and includeOff; opt in per sink
SSRFfile_get_contents, fopen, get_headers, fsockopen, stream_socket_client, curlOff; opt in per sink

Known limitation: proc_open with an array command (PHP 7.4+) is not inspected.

Coverage by PHP version

Layer 3 sink coverage by PHP version
SinkPHP 5.4–5.6PHP 7.0–7.4PHP 8.0–8.5
Command executiondetect + blockdetect + blockdetect + block
unserialize (off by default)detect + blockdetect + blockdetect + block
include / requiredetect + block (opcache caveat)detect + blockdetect + block
evaldetect + blockdetect + blockdetect + block
phar:// (off by default)detect + block, deserializationdetect + block, deserializationdetect + block only (no deserialization claim)
SSRF (off by default)detect + blockdetect + blockdetect + block

Verified on PHP 5.4, 5.6, 7.4, 8.3 and 8.5; other minors build and are expected to work but are not independently verified. A debug-build leak review is pending. Blocking an include or eval stops it running and the script continues. PHP 5.x include is checked at compile time, so a file served from a warm opcache is not re-inspected (eval is unaffected; the 7.0+ opcode handler is not affected by opcache). Phar: on PHP 5.x/7.x this covers metadata deserialization via the stream wrapper; on 8.x it is detection and denial of phar:// use only. The direct Phar API (new Phar, PharData, Phar::loadPhar, Phar::mapPhar, Phar::getMetadata) is not covered. SSRF is best-effort, resolve-at-check-time detection, not a connection-time boundary: DNS rebinding, redirects, curl_multi_*, CURLOPT_RESOLVE/CONNECT_TO and a non-string or absent CURLOPT_URL bypass it, and copy, file, readfile, SoapClient, DOMDocument::load* and simplexml_load_file are not hooked. It adds a DNS lookup per distinct outbound host with no timeout. More detail on the coverage page.

Log-first, on purpose

Layer 3 has its own sinks.mode, which defaults to detect (log only). It is independent of the global howdah.mode: setting howdah.mode = block does not arm sinks. Any active sink can block, and only when an operator sets that sink to block explicitly, after setting its allowlist. The 1.3 phar and ssrf sinks are off by default and only an explicit per-sink mode arms them.

The argument is never logged

An event records the sink and the function name (plus the class name for unserialize). The command line and the serialized payload are never written to the log.

Honest caveat. A flat allowlist policy is best-effort virtual patching and can be bypassed, which is why Layer 3 logs rather than blocks. Use the log to learn what your application really calls, then fix the application (no shell calls on user input, no unserialize of untrusted data). To configure it, see the [sinks] section of the configuration reference.

The PHP2Shell story, plainly told

On 21 October 2026 the PHP project patched two linked flaws, CVE-2026-109618 and CVE-2026-109619, which security researchers named PHP2Shell. As publicly reported, a crafted multipart/form-data request makes PHP use a lot of memory while it reads the headers of upload parts. A body well under the default 8 MB limit can hold hundreds of tiny, empty file parts. Corrupted state from that can, according to the reports, lead to remote code execution without logging in.

According to public reports, the official fix ships for PHP 8.2 and newer; older branches get no official patch. PHP 8.1 and older still run a very large share of the web.

What we did and did not verify

Our tests reproduce only the harmless structure of such a flood: how many parts and fields, and how large. We did not reproduce the CVE's session.upload_progress header-parsing path, so we do not claim Howdah blocks CVE-2026-109618 on a stock configuration.

What we measured: PHP's own max_input_vars and max_file_uploads already keep the flood shape we built small on a default box. On a box where an admin has raised those caps, the same shape used 127 to 173 MB, against a 128 MB limit, and Howdah's default limits refuse it long before that. Howdah's value is strongest on raised-cap servers and on old PHP with no official fix. See the coverage table and the measurements.

Why only a PHP extension can stop it

  • PHP reads the whole upload into $_FILES and $_POST before any PHP script runs, so a firewall written in PHP (for example via auto_prepend_file) is too late.
  • opcache.preload runs once when the server starts, never per request.
  • A network firewall sees bytes on the wire, not how PHP will interpret them.
  • A compiled extension runs inside PHP's own upload parser, so it can count parts as PHP reads them and stop the request early.

The incident details above come from public reports. We are checking them against the official advisories (GHSA-w8hw-w9fx-j79w, GHSA-6wmf-4rv5-p3rp, php.net) and will correct this page if they differ.

Version support: PHP 5.4 to 8.5

Howdah ships one build per PHP minor version. A version marked Tested has a passing lab result (run 2026-10-23); a version marked Expected shares a generation's code path with a tested version but has not been independently run, so it is not claimed as a test result. The coverage page has the full per-server-type detail.

  • 5.4.16 Tested
  • 5.5 Expected
  • 5.6.40 Tested
  • 7.0.33 Tested
  • 7.1.33 Tested
  • 7.2.34 Tested
  • 7.3.33 Tested
  • 7.4.33 Tested
  • 8.0.30 Tested
  • 8.1.34 Tested
  • 8.2.35 Tested
  • 8.3.36 Tested
  • 8.4.27 Tested
  • 8.5.12 Tested

Linux x86_64 and aarch64, non-thread-safe builds. The exact per-architecture binaries available are whatever the release build produced — see the install picker and the release assets.

How Howdah compares

Howdah does one narrow thing well: it refuses resource-exhaustion request shapes from inside PHP, on versions nothing else still covers. It is not a general-purpose WAF. Here is where it sits among tools people reach for, as of October 2026.

How Howdah compares with other PHP protection tools, as of October 2026
Tool Where it runs License Main job Runs on EOL PHP (5.4–8.1)? External service
Howdah Inside PHP (Rust extension) MIT OR Apache-2.0 Refuses Layer-1 request-shape & resource-exhaustion floods during parsing Yes — 5.4 to 8.5, including EOL branches None
Snuffleupagus Inside PHP (C extension) LGPL Hardening: kills bug classes + virtual-patching PHP 7.0+ (not 5.x) None
OpenRASP (Baidu) Inside the app (RASP, hooks sinks) Apache-2.0 Runtime detection of SQL/file/SSRF via sink hooks PHP extension; project quiet since 2022 Self-hosted management
Aikido Zen In-app agent (separate process) Open-source agent + commercial cloud Hooks runtime sinks (SQLi, SSRF, path/command injection) PHP 7.2+ (not 5.x) Aikido cloud (SaaS)
ModSecurity + OWASP CRS Web-server module (Apache / Nginx) Apache-2.0 Generic signature WAF at the server layer PHP-agnostic (sits in front of PHP) None
Datadog App & API Protection In-app (tracing library) + SaaS Proprietary (commercial) In-app WAF / threat protection with managed rules Per the Datadog PHP library Datadog cloud (paid)

Compiled October 2026 from each project's own documentation: Snuffleupagus (snuffleupagus.readthedocs.io), OpenRASP (github.com/baidu/openrasp), Aikido Zen (github.com/AikidoSec/firewall-php), ModSecurity & OWASP CRS (owasp.org / owasp-modsecurity), Datadog (docs.datadoghq.com). These tools do more than Howdah in their own areas; Howdah's point is the narrow, in-PHP, EOL-friendly niche. Found something out of date? Tell us.

Coverage: what has actually been tested

Every result is generated from the test matrix, never hand-typed. Totals: 973 checks passed, 0 failed, 33 skipped, with 0 false positives across the legitimate-upload corpus. A version is only called “Blocked” where the test for that exact version and server type passed; untested versions are marked plainly and never claimed as protected.

Read the full coverage table

Install in a minute

On Debian/Ubuntu with the apt repository (one build per PHP minor; replace 8.1 with yours). The key is pinned to this repo only:

sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://howdah.org/key.asc | sudo tee /etc/apt/keyrings/howdah.asc >/dev/null
echo "deb [signed-by=/etc/apt/keyrings/howdah.asc] https://pkg.howdah.org/apt stable main" \
  | sudo tee /etc/apt/sources.list.d/howdah.list
sudo apt-get update && sudo apt-get install php8.1-howdah

On RHEL/CentOS/Alma (Remi layout) the package is php81-howdah via yum, with both package and metadata signatures enforced. Both package names, the exact .repo file, the signed-tarball path for other setups, and the verify/rollback steps are in the picker. Once it is installed, the config editor writes your howdah.ini and howdah.toml from a form and checks the TOML with the extension's own parser.

Open the install picker Build your config file Check your PHP version first