FrankenPHP 1.13.5 is a security release. It fixes 17 vulnerabilities in FrankenPHP. The most severe let php_server run an uploaded non-PHP file as PHP. Several others affect the default php_server configuration: an open redirect, an HTTP/2 Host spoofing issue, a crash a client can trigger with the official Docker images' PHP settings, and slow request bodies that block every PHP thread. This release also upgrades to Caddy 2.11.9, which brings security fixes in Caddy itself, and to Mercure 1.1.6. Upgrading is strongly recommended.
🔒 Security
- High,
php_server: A file whose name doesn't end in asplitextension could be run as PHP, for instance/uploads/photos.php.php/avatar.jpg. An attacker able to upload a file under the document root could execute it (GHSA-j99q-3wr8-xj43). - Medium,
php_server: The trailing-slash redirect for directories echoed the raw request path, so//attacker.example/..%2fblogredirected to another host. The redirect is now relative, like Caddy'sphp_fastcgi(GHSA-jvmv-5538-pqpg). - Medium, HTTP/2: A
hostheader field sent next to:authorityset$_SERVER['HTTP_HOST']to a host other than the one Caddy routed and access-checked the request on (GHSA-2725-rh2w-9j9w). - Medium,
request_body_timeout: The timeout didn't stop slow request bodies, so an unauthenticated client could block every PHP thread with one slow connection per thread. Fixed by Caddy 2.11.8 and later (GHSA-j45x-h238-h4xx). - Medium, crash: A PHP request startup failing repeatedly on a regular thread, for instance with more parameters than
max_input_varsand PHP's default settings, crashed the process with a segmentation fault (GHSA-5359-vg53-5464). - Medium, crash:
exit()ordie()with a negative status, such asdie(-1), made FrankenPHP panic and stop (GHSA-q6vv-3p75-53wr). - Medium, Caddy rules bypass: Dot segments and encoded slashes after a PHP script, such as
/admin/tool.php/../../missing.php, let requests run a script that a Caddypathrule was meant to deny (GHSA-2x4j-r638-wxff). On Windows, the same check now also covers backslashes by @dunglas in #2810. - Medium,
env: A client'sAuthorization: Basicheader replaced theREMOTE_USERvalue set withenv, and four other variables also overrodeenvvalues. Only 1.12.0 and later are affected (GHSA-388m-835f-7723). - Medium, named routes: A
php_serverorphpdirective in a Caddy named route could run with the root,envvalues and workers of another site. Only 1.13.0 and later are affected (GHSA-g7pw-whv8-97j7). - Medium, source disclosure: A
php_serverwith a workermatchrule could serve the source of scripts using an extrasplitextension, or of any script behind some rewrites (GHSA-v7hf-4gqv-hf8p). - Medium, worker mode: Destructors of objects from a previous request, including session objects, ran while the next client's output was active, sending output and cookies to the wrong client (GHSA-8qcf-v46r-g2mc).
- Medium, embedded apps: The extracted app directory was deleted on configuration reloads while the server kept serving from it, so another local user could recreate it with their own code. Incomplete fix of GHSA-6q3j-62j9-f9hh, only 1.13.2 and later are affected (GHSA-j2f3-wxf2-8qgv).
- Low, Caddyfile disclosure: With the minimal documented layout,
php_serverserved the Caddyfile stored in the document root (GHSA-xh92-4p94-ch7x). - Low, autoscaling: FrankenPHP panicked when the operating system refused to create a new PHP thread (GHSA-hfxc-999w-mp67).
- Low,
PATH_INFO: Functions such asgetlastmod()and session cache limiters let clients probe the existence and modification time of files under the document root (GHSA-g9v4-rp77-rv76). - Low, extension workers:
Workers.SendRequest()ran arbitrary files below the working directory when its worker wasn't running (GHSA-cp2q-fc46-38f5), andWorkers.SendMessage()freed azvalowned by the caller (GHSA-28vm-87fh-3px5). Only custom builds linking a Go extension that uses these experimental APIs are affected.
Most of these vulnerabilities were found by Anthropic's OSS Scanner.
⚠️ Breaking Change
- Worker mode: Between requests, FrankenPHP now only resets state built from the HTTP request: superglobals (except
$_ENV), the request body, response headers and output, the session and the URL rewriter.strtok(),http_get_last_response_headers(),error_get_last()and the libxml errors persist again, like userland state does. Callerror_clear_last(),libxml_clear_errors()orhttp_clear_last_response_headers()if you need a clean state by @dunglas in #2790.
✨ New Features
- Populate
$_SERVER['SERVER_ADDR']with the IP address that accepted the connection, like Caddy's FastCGI transport, nginx and Apache by @dunglas in #2809.
🐛 Bug Fixes
- Return
413when the request body exceeds Caddy'srequest_body max_sizeinstead of passing a truncated body to PHP by @dunglas in #2785. - Return
404 No input file specified.when the script can't be opened, like php-fpm, instead of a200with a fatal error by @dunglas in #2791. - Destroy persistent resources, such as
PDO::ATTR_PERSISTENTconnections, when a PHP thread exits. With PHP 8.4 and later, each worker restart leaked them, until the database refused new connections by @andreasisaak in #2812. - Extensions: Custom Go extensions stay registered after PHP restarts (file watcher,
RestartWorkers(), config reloads) by @aguinaldotupy in #2788. - Extension workers:
SendRequest()reaches workers declared without a name, andSendMessage()returnsErrNotRunningafter shutdown instead of waiting forever by @nicolas-grekas in #2786. - Worker mode: Warnings raised while parsing the request no longer reach the previous request's error handler and can be read with
error_get_last(), as with FPM by @AlliBalliBaba in #2792. - Fix a memory leak reported by OSS-Fuzz in the cached HTTP method strings by @dunglas in #2789.
- Upgrade to Caddy 2.11.9, Mercure 1.1.6 and Vulcain 1.4.6.
php_serverkeeps returning Caddy's 404 for unknown paths in sites without anindex.phpby @dunglas in #2814.
⚡ Performance Improvements
- Avoid copying already rooted request paths when computing
SCRIPT_FILENAMEby @andreybolonin in #2794. - Convert uncommon header names in one pass and one allocation by @andreybolonin in #2795.
- Use a byte search to split response headers by @andreybolonin in #2796.
📖 Documentation
- Select a single platform in the local static build commands by @opctim in #2805.
- Update translations in #2808.
🙏 Sponsor the Maintainers
The "vulnapocalypse" caused by AI adds a lot of work for us: AI-powered scanners now report vulnerabilities faster than ever, and every report has to be reproduced, fixed and disclosed by a maintainer. This release alone fixes 17 advisories. Thank you to our sponsors, who make this work possible!
If you or your company rely on FrankenPHP, please consider sponsoring its maintainers:
💖 New Contributors
- @aguinaldotupy made their first contribution in #2788
- @andreasisaak made their first contribution in #2812
Full Changelog: v1.13.4...v1.13.5