FrankenPHP 1.13.5 is a security release that fixes 17 vulnerabilities. The highest-severity issue can make an uploaded non-PHP file execute as PHP when a crafted path causes the PHP server's route and path-splitting logic to disagree. If you operate FrankenPHP 1.13.4 or earlier, rebuild every image on 1.13.5, keep uploads outside the document root, replace every running instance, and verify routing and worker isolation before declaring the patch complete.
The official 1.13.5 release was published on November 5, 2026. This guide separates what the maintainers documented from engineering recommendations for platform teams. The official advisories include proof-of-concept material, but the cited project sources do not report active exploitation or a confirmed compromise. An observed probe is not proof that code executed.
Answer first
Treat this as an urgent exposure-driven update, not a reason to test exploit payloads against production. The shortest defensible path is:
- inventory the exact FrankenPHP version and image digest on every node, pod and container;
- move or confirm user-controlled uploads outside the web document root;
- rebuild immutable artifacts with FrankenPHP 1.13.5 or later;
- replace all instances, including long-running workers;
- verify invalid script paths return 404, upload routes remain static, PHP source is never served, and request state cannot cross worker boundaries;
- retain rollout evidence and review suspicious historical requests without equating attempts with compromise.
What the official sources establish
The maintainers describe FrankenPHP 1.13.5 as a security release addressing 17 vulnerabilities. The release also updates bundled components to Caddy 2.11.9 and Mercure 1.1.6 and strongly recommends upgrading.
The highest-severity advisory, GHSA-j99q-3wr8-xj43, affects FrankenPHP through 1.13.4 and is fixed in 1.13.5. It documents a path confusion condition in php_server: when an attacker can place a chosen file or path under the document root, a request containing a crafted PHP-looking path segment can cause a later non-PHP file to be selected and executed as PHP. The patched handler rejects an invalid script path with 404. The advisory assigns CVSS 7.5 and does not assign a CVE.
Two related advisories matter to worker deployments:
- GHSA-v7hf-4gqv-hf8p affects versions 1.8.0 through 1.13.4. A configuration combining a worker
matchrule with the built-in file server can expose PHP source instead of executing it. Version 1.13.5 fixes the issue. - GHSA-8qcf-v46r-g2mc affects versions through 1.13.4. Under specific application patterns, output, headers or cookies generated late by a previous request can leak into the next request handled by the same worker. The advisory says classic mode is not affected.
The release includes additional fixes for open redirects, Host handling, slow request bodies, routing-rule bypasses and crashes. This article focuses on the three issues most likely to change deployment and verification work.
Documented fact: the project published advisories and a fixed release. Maintainer claim: the release resolves the listed issues and should be installed. Engineering analysis: your real priority depends on whether an attacker can write beneath the document root, whether php_server receives upload paths, and whether worker-specific configuration or lifecycle behavior is present.
Exposure decision table
| Runtime condition | Priority | Immediate action |
|---|---|---|
| FrankenPHP 1.13.4 or earlier and user-controlled files can land under the document root | Critical operational priority | Isolate uploads, restrict routing, rebuild on 1.13.5, and review request plus process evidence |
php_server is reachable but uploads are stored outside the document root | High | Upgrade promptly and verify no alternate import, cache or extraction path writes into the root |
Worker mode uses match together with the built-in file server | High | Upgrade; until then disable the file server for that path or remove the risky match pattern |
| Worker mode may emit destructor, error-handler or garbage-collection output after a response | High for confidentiality | Upgrade, rotate affected workers, and test cross-request isolation |
| Classic mode only, with no attacker-controlled file placement | Still patch | The worker leak does not apply, but the main routing issue and other 1.13.5 fixes still require review |
| Version and configuration cannot be proved | Treat as exposed | Stop guessing: capture runtime version, image digest and effective Caddy configuration |
Find every running copy
An image tag in a repository is not runtime evidence. Query the executable inside each live unit and pair the result with the deployed image identifier.
For a standalone host:
frankenphp version
systemctl status frankenphp --no-pager
systemctl cat frankenphpFor Docker, record both the image reference and the version reported in the container:
docker inspect --format '{{.Config.Image}} {{.Image}}' <container>
docker exec <container> frankenphp versionFor Kubernetes, collect image references and immutable image IDs, then verify the binary in representative pods from every workload and revision:
kubectl get pods -A -o custom-columns='NS:.metadata.namespace,POD:.metadata.name,IMAGE:.spec.containers[*].image,IMAGE_ID:.status.containerStatuses[*].imageID'
kubectl exec -n <namespace> <pod> -- frankenphp versionAlso search CI base images, Helm values, Compose files, systemd units, PaaS buildpacks, disaster-recovery templates and dormant replicas. A fixed primary deployment with a vulnerable rollback image remains an incomplete remediation.
Contain before the rebuild
The main advisory's strongest compensating control is architectural: store uploads outside the document root. If the application must serve them, use a static route or object-storage origin that cannot enter the PHP handler.
Until the rollout finishes:
- block writes to the document root except from the build/release process;
- route upload prefixes directly to static storage, never through
php_server; - reject suspicious nested PHP-looking path segments and unusual encoded separators at the edge, while testing legitimate routes;
- disable extraction of untrusted archives into any served directory;
- for the source-disclosure condition, use
file_server offon the affected worker route or remove the risky workermatchconfiguration, as the advisory recommends; - increase monitoring for unexpected PHP child behavior, outbound connections and file changes.
These are temporary controls. They do not replace the fixed release, and file_server off addresses the documented source-disclosure configuration rather than every issue in 1.13.5.
Upgrade as an immutable replacement
- Freeze the baseline. Save the current image digest,
frankenphp version, effective Caddy configuration, environment inventory and representative health results. - Update the build. Select an official FrankenPHP 1.13.5-or-later artifact compatible with your PHP extensions. Pin the resolved digest in production rather than relying only on a mutable tag.
- Rebuild dependencies. Recreate the application image, extensions and operating-system packages; do not copy the old FrankenPHP binary or Caddy module directory into the new layer.
- Scan and attest. Generate an SBOM, run the organization's image scanner, and verify the source and signature or provenance mechanism available in your supply chain.
- Canary safely. Send controlled traffic to a small set of new instances. Exercise uploads, front-controller routes, worker endpoints, cache behavior and authentication without using the public exploit proof.
- Replace, do not merely reload. Terminate old containers and long-running workers so no pre-fix process survives. Confirm every pod, node and rollback revision reports the fixed version and intended digest.
- Promote with evidence. Attach the digest, version output, config hash, test results and rollout timestamps to the change record.
The 1.13.5 release notes say Caddy is updated to 2.11.9. If your build compiles a custom FrankenPHP/Caddy binary, confirm the final executable actually contains the intended component set rather than assuming the base tag did the work.
Verification without weaponizing production
Use negative routing tests and controlled fixtures:
| Test | Expected result on the fixed deployment |
|---|---|
| Request an invalid or nonexistent script path with extra path information | 404; no fallback to a non-PHP file |
| Upload a harmless text fixture through the normal application path | Stored outside the document root and served only as static content |
| Request a PHP source filename through worker and static-file routes | Source is never returned |
| Send two marked requests through the same worker while triggering normal cleanup | No header, cookie or body marker crosses to the next response |
| Send an unknown Host value allowed by your test environment | Request is rejected or routed only according to the intended host policy |
| Slowly stream a request body in a staging test | Timeouts and concurrency controls prevent one client from exhausting all PHP threads |
| Restart and scale the workload | Every replacement reports 1.13.5 or later and the approved image digest |
Run the same checks from the public ingress and from an internal service path because proxies can rewrite the URI, Host or path information. Capture status, response headers and server logs. Do not upload executable PHP payloads or replay public proofs against production.
Historical review: attempts are not compromise
Search access and WAF logs for nested .php path segments, repeated path-info variants, encoded separators, unusual Unicode around script suffixes, upload paths followed by execution-like requests, and successful responses where a non-PHP object should have been static. Correlate those requests with:
- file-creation and integrity logs beneath the document root;
- PHP, Caddy and application error logs;
- process trees and unexpected child processes;
- outbound DNS and network connections;
- secrets access, authentication changes and persistence indicators.
A matching URL is evidence of a probe. A 200 response is stronger but still not conclusive. Confirm execution through process, application, filesystem or network evidence. If compromise is plausible, preserve volatile evidence, rotate exposed secrets from a trusted environment, rebuild from known-good sources and follow the incident-response plan.
Best practices
- Keep uploaded and generated files outside the executable document root.
- Make runtime version and image digest observable in deployment inventory.
- Treat the effective Caddyfile as a controlled, reviewable artifact.
- Use least-privilege filesystem permissions for the FrankenPHP process.
- Bound request-body time and size at both ingress and application layers.
- Give worker-mode applications explicit lifecycle, cleanup and isolation tests.
- Keep a tested rollback artifact that is also patched.
Anti-patterns and limits
Do not declare remediation complete because a Dockerfile changed, a tag says “latest,” one pod restarted, or a WAF rule blocked one example path. Do not use exploit success as the production acceptance test. Do not infer compromise solely from a scanner finding, and do not infer safety from the absence of matching access-log strings.
The advisories describe software behavior, not your entire application. Framework routing, reverse proxies, archive extraction, mounted volumes, custom Caddy modules and worker code can add exposure not covered by a default configuration. Conversely, storing all user content outside the document root reduces the main precondition but does not remove the need to install the other security fixes in 1.13.5.
Executive checklist
- [ ] Every live and rollback instance is inventoried by version and digest.
- [ ] No user-controlled file can be written under the document root.
- [ ] Build artifacts use FrankenPHP 1.13.5 or later.
- [ ] All old containers and workers were replaced.
- [ ] Invalid script paths return 404.
- [ ] Upload routes cannot enter the PHP handler.
- [ ] Worker routes never expose PHP source.
- [ ] Cross-request headers, cookies and body output are isolated.
- [ ] Public-ingress and internal-path tests passed.
- [ ] Historical probes were separated from confirmed execution evidence.
- [ ] Rollout evidence is retained with the change record.
For adjacent response patterns, see the PHP2Shell patch guide, the Next.js 16.4.1 critical RCE guide, and the Babelfish TDS vulnerability guide. The services page connects these controls to platform, cloud and product engineering work.
References
- FrankenPHP 1.13.5 release
- GHSA-j99q-3wr8-xj43: uploaded non-PHP file can be executed as PHP
- GHSA-v7hf-4gqv-hf8p: PHP source disclosure with worker match and file server
- GHSA-8qcf-v46r-g2mc: output from a previous worker request can leak
- FrankenPHP worker mode documentation
- FrankenPHP configuration documentation
- Derrick Coetzee, “Technician with laptop working on server rack at NERSC,” CC0 1.0, Wikimedia Commons
Prepared by: Noor Yasser
Official references
These references document the tools discussed. Examples and design decisions are illustrative and should be adapted to the project and its versions.
- FrankenPHP 1.13.5 release
- GHSA-j99q-3wr8-xj43 — Uploaded non-PHP file can be executed as PHP
- GHSA-v7hf-4gqv-hf8p — PHP source disclosure in worker match configurations
- GHSA-8qcf-v46r-g2mc — Previous worker request output can leak
- FrankenPHP worker mode documentation
- FrankenPHP configuration documentation
- Wikimedia Commons — Technician with laptop working on server rack at NERSC
Prepared by: Noor Yasser
Working through a similar engineering challenge?
I help teams turn architecture decisions into a clear scope and dependable, reviewable implementation.




