FrankenPHP before 1.13.4 contains a double free vulnerability in worker mode that frees SG(requestinfo).contenttypedup twice when a worker script ends its loop after a non-form POST request. Unauthenticated attackers can send repeated JSON POST requests until the worker loop ends, aborting the whole process when USEZENDALLOC=0 is set.
FrankenPHP before 1.13.0 contains a path-split interpretation conflict in splitPos in cgi.go that allows unauthenticated attackers to execute protected PHP scripts by embedding .php inside a filename component. Attackers can request paths like /uploads/a.php.txt/b.php to execute uploads/a.php despite route-level or WAF deny rules and forge PATHINFO, SCRIPTNAME, and PHPSELF values.
FrankenPHP before 1.13.2 contains an information disclosure vulnerability that allows later clients to read earlier clients' libxml errors because worker mode never resets LIBXML(errorlist) between requests. Attackers sending requests to the same worker thread can call libxmlgeterrors() or libxmlgetlasterror() to obtain other clients' parsed-document fragments and server filesystem paths.
FrankenPHP before 1.13.0 contains a denial of service vulnerability in goArray() that allows remote attackers to abort the server process via arrays with null integer-keyed elements. Attackers can send crafted JSON passed to frankenphplog() or returned from worker callbacks, triggering an unrecovered cgo panic that terminates every vhost and in-flight request.
FrankenPHP before 1.13.2 contains an uncontrolled recursion vulnerability in its PHP-to-Go array converter that fails to detect circular references in PHP arrays. Passing a self-referential array to frankenphplog() or returning one from a worker callback triggers a fatal Go stack overflow, terminating the entire server process.
FrankenPHP before 1.13.4 contains a use-after-free vulnerability in worker mode that frees internal streams of SplTempFileObject or SplFileObject on php://temp instances surviving across requests. Remote clients can send requests reaching code that uses such persisted objects, causing reads of freed memory that leak garbage data or crash the process.
FrankenPHP Docker images before 1.1.11 were rebuilt only when new base image tags were created, so security fixes that Alpine, PHP, and Go applied under existing tags were not propagated. Remote attackers could exploit known flaws in the stale system libraries, such as CVE-2025-15467 in libcrypto3, to execute code.
FrankenPHP before 1.13.0 contains an authentication bypass vulnerability that allows unauthenticated attackers to spoof trusted headers by sending dot-form header names that PHP rewrites to underscores. Attackers can send headers like X.Authenticated.User or X.Forwarded.For that map to $SERVER keys, spoofing user identity, client IP, or scheme.
FrankenPHP from 1.5.0 before 1.13.0 contains an incomplete environment sandbox vulnerability in which putenv writes to the process-wide OS environment via os.Setenv, leaking values across concurrent requests. Attackers reaching a request path that spawns child processes with the default environment through procopen or exec can read secrets such as database passwords set by other requests.
FrankenPHP before 1.13.4 contains a denial of service vulnerability in worker mode where frankenphphandlerequest() returns false without finishing the dequeued request after a fatal error during request startup. Attackers who trigger startup fatal errors against worker scripts that keep calling frankenphphandlerequest() can leave client requests hanging indefinitely while the worker continues running.
FrankenPHP before 1.13.2 contains a resource exhaustion vulnerability that allows unauthenticated clients to tie up PHP threads because queued requests are not cancelled when clients disconnect. Attackers can send many requests and close connections immediately, forcing handleRequestWithRegularPHPThreads and worker handlers to execute them and delay all clients sharing the thread pool.
FrankenPHP before 1.13.4 contains an information disclosure vulnerability in worker mode that leaks per-request environment variables into later requests on the same PHP thread. Attackers can read values set via env directives with request placeholders or WithRequestEnv() through getenv() or $SERVER, causing applications to act on another user's routing, tenant, or authorization values.
FrankenPHP standalone static binaries 1.8.0 before 1.13.2 bundle the PHP brotli extension statically linked with Brotli 1.2.0, whose decoder contains an out-of-bounds write (CVE-2026-111497). Unauthenticated remote attackers can send crafted data to applications that pass it to brotliuncompressadd() to crash the entire FrankenPHP process, and possibly cause memory disclosure or code execution.
FrankenPHP before 1.13.4 contains a use-after-free vulnerability that leaves a dangling pointer to a freed environment table when putenv() is called during request shutdown. Attackers triggering putenv() in shutdown functions, destructors, or session handlers can crash the process on the next getenv() call or leak previous request environment values.
FrankenPHP before 1.13.4 contains an information disclosure vulnerability in worker mode that fails to clear last HTTP stream response headers between requests, because the reset only happens in RSHUTDOWN, which worker requests skip. Later requests calling httpgetlastresponseheaders() can read headers fetched by earlier requests on the same worker thread, exposing other users' Set-Cookie or token headers from backend APIs.
FrankenPHP before 1.13.4 contains an information disclosure vulnerability in worker mode that fails to clear last HTTP stream response headers between requests, because the reset only happens in RSHUTDOWN, which worker requests skip. Later requests calling httpgetlastresponseheaders() can read headers fetched by earlier requests on the same worker thread, exposing other users' Set-Cookie or token headers from backend APIs.
Summary In worker mode under concurrency, FrankenPHP can hand a PHP session id from one request to another when PHP's session extension is loaded as a shared module instead of being statically linked into the FrankenPHP binary. A request presenting Cookie: session=A can have sessionid() resolve to B (a different concurrent client's id) or to a stale id from a previous request served by the same worker thread.
Impact - Security: cross-user session exposure / session hijacking in worker mode. Same impact class as CVE-2026-24894. - Functional: users are intermittently logged out; their cookie's session is ignored and they receive a foreign or empty session.
Affected / not affected Exposure is determined by how the session extension is linked in the PHP build FrankenPHP is compiled against, not by the FrankenPHP release:
- Affected: any build where the session extension is not visible to FrankenPHP at compile time, i.e. loaded as a shared / out-of-tree module (extension=session in an ini). This includes the RPM and Debian packages and other packaged php-zts distributions that ship session as a shared extension under conf.d. - Not affected: builds where session is statically compiled into the PHP that FrankenPHP links against, such as the official static binaries and Docker images. There, the reset code below is compiled in and clears the id every request.
The C session-reset code is unchanged across 1.11.x-1.12.x, so this is not a code regression between releases. A "1.12.2 works / 1.12.3 leaks" observation on the same host reflects two differently-built packages, not a source change. The persistent-zval helpers (#2366) are compiled only under FRANKENPHPTEST and are unrelated.
Root cause FrankenPHP resets per-request session state in frankenphpresetsessionstate(), which releases PS(id) so the next sessionstart() re-reads the request cookie. That whole function is guarded by #ifdef HAVEPHPSESSION, a macro defined only when the session extension headers are present at FrankenPHP compile time (session statically enabled in the PHP build). When session is a shared module, HAVEPHPSESSION is undefined, the reset is compiled out, and PS(id) survives the request boundary.
PHP's sessionstart() reads the request cookie only when PS(id) is NULL (ext/session/session.c, tryfindsessionidincookies(): if (!PS(usecookies) || PS(id)) return;). With a stale PS(id), the cookie is ignored and the worker reuses the previous request's id, exposing it to whichever client is served next.
Reproduction Minimal worker script (index.php): php <?php $handler = static function () { sessionstart(); $sent = $COOKIE['session'] ?? '(none)'; $id = sessionid(); header('Content-Type: text/plain'); echo ($sent === '(none)' || $sent === $id) ? "OK sent=$sent id=$id\n" : "LEAK sent=$sent id=$id\n"; sessionwriteclose(); }; while (\frankenphphandlerequest($handler)) {} Run under worker mode with num 2 (≥2 workers), session.usestrictmode=1, on a PHP build where session is a shared module. Pre-create two valid sessions A and B, then fire concurrent requests alternating the two cookies: sh seq 600 | xargs -P32 -I{} sh -c ' c=$([ $(( {} % 2 )) -eq 0 ] && echo A || echo B); curl -s -H "Cookie: session=$c" http://127.0.0.1:8080/' | grep -c LEAK On an affected build a large fraction of responses are LEAK (sessionid() differs from the cookie sent, and A and B sometimes resolve to the same id). On a statically-linked build the count is always 0.
Fix When HAVEPHPSESSION is undefined, FrankenPHP now resets the session module through the module registry on every worker request (its RSHUTDOWN releases PS(id), RINIT re-initialises the globals), so the per-request cleanup always runs regardless of how the extension is linked. Statically-linked builds are unchanged and keep using frankenphpresetsessionstate().
Verified on PHP 8.6-dev ZTS, 2 workers, session.usestrictmode=1, 600 alternating-cookie concurrent requests: before the fix 519/600 leaked; after the fix 0/600. Statically-linked builds stayed at 0/600.
Workarounds - Use a FrankenPHP build with session statically compiled (official static binaries or Docker images). - Or, in the worker loop, after sessionwriteclose() call sessionid('') so the next sessionstart() re-reads the request cookie (with session.usestrictmode=1).
FrankenPHP is a modern application server for PHP. Prior to 1.12.5, the official FrankenPHP Debian and Alpine Docker images include a default /app/public/index.php that executes phpinfo(). A derived image that does not overwrite this placeholder serves the diagnostics page in production and can disclose environment variables used for secrets, PHP configuration directives, loaded extensions, software versions, and system paths. Images built using the documented pattern with their own /app/public/index.php are not affected, and already derived images must be rebuilt or supplied with an application index to remove the exposed placeholder. This issue is fixed in version 1.12.5.