Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ FrankenPHP sandboxes environment variables per-thread:
1. At startup, the main thread snapshots `os.Environ()` into `main_thread_env` (a PHP `HashTable`).
2. `$_SERVER` is built from a copy of `main_thread_env` plus request-specific variables (in `frankenphp_register_server_vars`). It is rebuilt for every request, including each iteration of a worker script.
3. `$_ENV` is populated from the same snapshot through PHP's `php_import_environment_variables` hook. In regular mode this happens once per script execution; in worker mode it happens once when the worker script starts and is **not** rebuilt between worker requests, which is why writes to `$_ENV` leak across requests (see [Worker Mode](worker.md)).
4. `frankenphp_getenv()` looks a variable up in thread-local layers, the first match wins: `sandboxed_env` (`putenv()` writes and unsets), `prepared_env` (the `env` of the current request, released after each worker request), `worker_env` (the `env` of the worker script, kept for all its requests), then `main_thread_env`. `putenv()` writes land only in `sandboxed_env` and **never** mutate the process-global OS environment, so a value set by one request is not visible to the Go/Caddy side, to child processes (`proc_open`, `exec`, ...), or to other requests. This also avoids races on the global C environment.
4. `frankenphp_getenv()` looks a variable up in thread-local layers, the first match wins: `sandboxed_env` (`putenv()` writes and unsets), `prepared_env` (the `env` of the current request, released after each worker request), `worker_env` (the `env` of the worker script, kept for all its requests), then `main_thread_env`. `putenv()` writes land only in `sandboxed_env` and **never** mutate the process-global OS environment, so a value set by one request is not visible to the Go/Caddy side, to child processes (`proc_open`, `exec`, ...), or to other threads. This also avoids races on the global C environment.
5. `reset_sandboxed_environment()` releases `sandboxed_env` after each PHP script execution. In regular mode that's per request; in worker mode it only runs when the worker script itself exits, so `putenv()` writes are visible to subsequent worker requests on the same thread until the script restarts.

## Request flow (regular mode)
Expand Down
2 changes: 1 addition & 1 deletion docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ These are the surfaces FrankenPHP owns. A vulnerability here is a FrankenPHP vul
- **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.
- **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.
- **Worker-mode state isolation**: FrankenPHP resets `$_GET`, `$_POST`, `$_COOKIE`, `$_FILES`, `$_SERVER`, and `$_REQUEST` between requests, explicitly clears `$_SESSION` (which would otherwise leak between requests) and the errors returned by `error_get_last()` and `libxml_get_errors()`, and the headers returned by `http_get_last_response_headers()`, the `strtok()` state, and the variables added by `output_add_rewrite_var()`, but **`$_ENV` is not reset**, and `putenv()` writes, `static` variables, class static properties, globals, and runtime engine settings (`ini_set()`, the default stream context, the default timezone, the working directory, ...) 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)).
- **Per-thread environment sandboxing**: `frankenphp_putenv()` / `frankenphp_getenv()` operate on a thread-local `sandboxed_env`. `putenv()` writes stay inside that sandbox and never reach the process-global OS environment, so a secret set by one request cannot leak to child processes (`proc_open`, `exec`, ...), to the Go/Caddy side, or to other requests. A child process that must see such a variable has to be given it explicitly, through the environment argument of `proc_open()` or `exec()` (see [Internals](internals.md#per-thread-environment-sandboxing)).
- **Per-thread environment sandboxing**: `frankenphp_putenv()` / `frankenphp_getenv()` operate on a thread-local `sandboxed_env`. `putenv()` writes stay inside that sandbox and never reach the process-global OS environment, so a secret set by one request cannot leak to child processes (`proc_open`, `exec`, ...), to the Go/Caddy side, or to other threads. In worker mode, it stays visible to the following requests on the same thread until the worker script restarts. A child process that must see such a variable has to be given it explicitly, through the environment argument of `proc_open()` or `exec()` (see [Internals](internals.md#per-thread-environment-sandboxing)).
- **CGO memory boundary**: Go string pinning and `C.CString()` / `free()` lifetimes across the Go ↔ C boundary.
- **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.
- **Trusted proxy handling**: incoming `X-Forwarded-*` headers always reach PHP as tainted `$_SERVER['HTTP_X_FORWARDED_*']` values; they are only trusted to derive the real client IP and scheme when [`trusted_proxies`](production.md#running-behind-a-reverse-proxy) is configured.
Expand Down
2 changes: 1 addition & 1 deletion frankenphp.c
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,7 @@ PHP_FUNCTION(frankenphp_finish_request) { /* {{{ */
/* {{{ Sandboxed putenv() that never mutates the process-global OS environment.
* Writes land only in the thread-local sandboxed_env, so a secret passed to
* putenv() by one request cannot leak to the Go/Caddy side, to child processes
* (proc_open, exec, ...), or to other requests. */
* (proc_open, exec, ...), or to other threads. */
PHP_FUNCTION(frankenphp_putenv) {
char *setting;
size_t setting_len;
Expand Down
Loading