Skip to content

Commit 514b4ff

Browse files
docs: add security model documentation
1 parent 61077e1 commit 514b4ff

3 files changed

Lines changed: 69 additions & 0 deletions

File tree

SECURITY.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ Please ensure that you're always using the latest release.
77

88
Binaries and Docker images are rebuilt nightly using the latest versions of dependencies.
99

10+
## Security Model
11+
12+
FrankenPHP embeds the PHP interpreter into a Go and Caddy server, so its trust boundaries span Go, C, and PHP.
13+
Before auditing the project or reporting an issue, read the [security model documentation](docs/security.md),
14+
which describes what is trusted, what is not, and which attack surfaces belong to FrankenPHP itself.
15+
1016
## Reporting a Vulnerability
1117

1218
If you believe you have discovered a security issue directly affecting FrankenPHP,

docs/security.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
title: "FrankenPHP security model: trust boundaries between Go and PHP"
3+
description: "Where FrankenPHP's trust boundary lives: which inputs are untrusted, what the Go/C runtime is responsible for, and what falls to the PHP application or to upstream projects."
4+
---
5+
6+
# Security model
7+
8+
This document describes FrankenPHP's trust model: which inputs are trusted, which are not, and where the boundary between them lives.
9+
It is meant to help security audits and automated scanners reason about **FrankenPHP itself**, not the PHP applications it serves.
10+
11+
For the internal mechanics referenced here (threads, the CGO boundary, environment sandboxing), see [Internals](internals.md).
12+
For state persistence in long-running processes, see [Worker Mode](worker.md).
13+
14+
## Trust boundaries
15+
16+
FrankenPHP runs in a stack with four distinct actors:
17+
18+
| Actor | Trust | Notes |
19+
| ----------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
20+
| **Remote client** | Untrusted | The HTTP request (method, URI, headers, cookies, body, uploads) is the primary source of tainted input. |
21+
| **Operator** | Trusted | Supplies the deployment configuration: the `Caddyfile`, environment variables, `php.ini`, installed PHP extensions and Caddy modules, and the application code itself. |
22+
| **PHP application code** | Trusted *by provenance* | Deployed by the operator, so FrankenPHP never executes attacker-supplied code, but this code *consumes* untrusted request data. |
23+
| **FrankenPHP (Go + C)** | Trusted computing base | Embeds PHP, transports data in and out, and isolates requests and threads. Its own defects are what this document scopes. |
24+
25+
## Code provenance vs. data taint
26+
27+
This is the single most important distinction, and the one that resolves most "do we trust PHP or not?" confusion:
28+
29+
- **Code provenance is trusted.** FrankenPHP only executes PHP files deployed by the operator: the script resolved under the document root, or the configured worker script. It never evaluates code carried in the request itself (body, query string, headers): the SAPI boundary carries *data*, never untrusted *code*.
30+
- **Request data is tainted.** Everything the PHP code reads from the request (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES`, `$_SERVER`, `php://input`) is untrusted, exactly as in any PHP SAPI. Sanitizing it is the application's job.
31+
32+
So "we trust what comes from PHP" is true for the *code*, while "the SAPI carries untrusted input" is true for the *data*: the two are not in conflict.
33+
FrankenPHP's job is to carry that tainted data faithfully and to keep one request's data from leaking into another.
34+
35+
## What FrankenPHP is responsible for
36+
37+
The trusted computing base has three jobs. Security defects in FrankenPHP live in one of them:
38+
39+
1. **Faithful transport**: map the request into PHP superglobals and `php://input`, and carry PHP's output and headers back to the client, without introducing injection (header/CRLF injection, request smuggling, execution of the wrong file).
40+
2. **Isolation**: keep state from crossing between requests, between PHP threads, and between worker iterations.
41+
3. **Memory safety**: manage the CGO boundary (Go ↔ C/PHP) without corrupting memory.
42+
43+
## In scope: FrankenPHP's own attack surface
44+
45+
These are the surfaces FrankenPHP owns. A vulnerability here is a FrankenPHP vulnerability:
46+
47+
- **Request to superglobal mapping** (`cgi.go`, `frankenphp_register_server_vars`): building `$_SERVER`, `REMOTE_ADDR`, `SCRIPT_NAME`, `PATH_INFO`, and the other CGI variables from the request.
48+
- **PHP script-path resolution**: the request path is split on `split_path` (`.php` by default) into `SCRIPT_NAME` / `PATH_INFO`, then joined to the document root with `sanitizedPathJoin` (`filepath.Join(root, filepath.Clean("/"+reqPath))`), which keeps `SCRIPT_FILENAME` from escaping the document root (path traversal). The `php_server` directive additionally sets a default `try_files` rewrite that routes requests to existing files or the front controller, mitigating the classic PHP-FPM pitfall of executing the wrong file.
49+
- **Worker-mode state isolation**: FrankenPHP resets `$_GET`, `$_POST`, `$_COOKIE`, `$_FILES`, `$_SERVER`, and `$_REQUEST` between requests, and explicitly clears `$_SESSION` (which would otherwise leak between requests), but **`$_ENV` is not reset**, and `putenv()` writes, `static` variables, class static properties, and globals persist across requests on the same thread. Request- or user-specific data left in that state can leak into a later request (see [Worker Mode](worker.md#state-persistence)).
50+
- **Per-thread environment sandboxing**: `frankenphp_putenv()` / `frankenphp_getenv()` operate on a thread-local `sandboxed_env` so concurrent threads don't race on the global C environment (see [Internals](internals.md#per-thread-environment-sandboxing)).
51+
- **CGO memory boundary**: Go string pinning and `C.CString()` / `free()` lifetimes across the Go ↔ C boundary.
52+
- **Caddy admin API**: the `/frankenphp/workers/restart` and `/frankenphp/threads` endpoints, exposed through Caddy's admin API (which listens on `localhost:2019` by default). Exposing that endpoint beyond localhost is an operator decision.
53+
- **Trusted proxy handling**: `X-Forwarded-*` headers are only honored when [`trusted_proxies`](production.md#running-behind-a-reverse-proxy) is configured.
54+
55+
## Out of scope
56+
57+
- **Vulnerabilities in the application's PHP code** (SQL injection, XSS, insecure deserialization, etc.). FrankenPHP delivers untrusted request data to the application unchanged; defending against it is the application's responsibility, just as with any SAPI.
58+
- **Flaws in upstream components** used by FrankenPHP (PHP, Caddy, Go) or in projects built on top of it (Laravel Octane, Symfony Runtime). Report those to the relevant project.
59+
60+
## Reporting a vulnerability
61+
62+
See [`SECURITY.md`](../SECURITY.md) for how to report a security issue affecting FrankenPHP.

llms.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This index points AI agents and crawlers at the canonical English documentation.
1010
- [FrankenPHP Worker Mode](https://frankenphp.dev/docs/worker/): keep your PHP application bootstrapped in memory between requests for lower latency.
1111
- [Configuring FrankenPHP](https://frankenphp.dev/docs/config/): Caddyfile, JSON, environment variables, and `php.ini` configuration.
1212
- [FrankenPHP Internals](https://frankenphp.dev/docs/internals/): thread types, the state machine, the Go/C/PHP CGO boundary, auto-scaling, and per-thread environment sandboxing.
13+
- [FrankenPHP Security Model](https://frankenphp.dev/docs/security/): trust boundaries between Go, C, and PHP (which inputs are untrusted, what the runtime is responsible for, and what is out of scope).
1314

1415
## Setup and build
1516

0 commit comments

Comments
 (0)