You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Serve a normal Laravel app from the embedded runtime on Android, where different requests carry different headers — an XHR that sends X-Requested-With and a top-level navigation that does not, a POST that sends Content-Type and a GET that does not.
What happened?
Every request sees the headers of the request before it.
Headers are passed to PHP as process environment variables (PHPBridge.handleLaravelRequest() sets HTTP_* via nativeSetEnv), and the environment outlives a dispatch in the persistent runtime. Nothing removes them, so $_SERVER in request N contains the union of every header seen since the process started, with the values from whichever request last sent each one.
Two things keep the existing defences from covering this:
PHPBridge.kt:324 clears five names — the Inertia ones — and clears them by setting "" rather than removing them. Every other header keeps its previous, non-empty value.
Clear stale HTTP_* headers from $_SERVER between persistent PHP dispatches #119 added a $_SERVER sweep to the dispatch eval (php_bridge.c:553), but the eval re-imports the environment twenty lines later (php_bridge.c:575, foreach (getenv() as $__k => $__v) { $_SERVER[$__k] = $__v; }), so the stale keys come straight back. $_SERVER cannot be cleaned while the environment it is rebuilt from is not.
Expected: $_SERVER['HTTP_X_FOO'] is absent on a request that did not send X-Foo.
iOS does not have this problem — PersistentPHPRuntime.swift records the keys it sets and unsetenvs them after each dispatch.
How to reproduce the bug
No backend, no Inertia — a stock app, one route, three fetch() calls.
Not visible under Jump — forwardToRemote() proxies to the dev server and never enters a PHP dispatch, so no header ever becomes an environment variable.
Debug Output
Package Version | 4.1.0
PHP Version (Host) | 8.5.0
OS | Darwin
OS Version | 25.6.0
Embedded PHP | 8.5.9
Installed Plugins | None
Xcode | Xcode 26.3
Android Studio | Android Studio AndroidStudio2026.1.3
Gradle | Gradle 8.14.5
Java | openjdk version "17.0.14" 2025-01-21 LTS
CocoaPods | Not found
Which operating systems have you seen this occur on?
macOS
Which platforms were you trying to build for?
Android (Device)
Notes
Device: Pixel 4a (5G), Android 14, arm64-v8a, WebView 150.0.7871.183. Built with native:package android --build-type=release, installed with adb install. NATIVEPHP_RUNTIME_MODE left at its default (persistent).
The user-visible form of this is what #119 set out to fix: an Inertia XHR sets HTTP_X_INERTIA, and the next top-level navigation inherits it and renders the page object as raw JSON. #119 fixed the $_SERVER half; the environment half remains, and it affects every header, not only the Inertia five.
A PR follows that tracks the header env vars each request sets and removes them after the dispatch — the same thing iOS already does.
What were you trying to do?
Serve a normal Laravel app from the embedded runtime on Android, where different requests carry different headers — an XHR that sends
X-Requested-Withand a top-level navigation that does not, a POST that sendsContent-Typeand a GET that does not.What happened?
Every request sees the headers of the request before it.
Headers are passed to PHP as process environment variables (
PHPBridge.handleLaravelRequest()setsHTTP_*vianativeSetEnv), and the environment outlives a dispatch in the persistent runtime. Nothing removes them, so$_SERVERin request N contains the union of every header seen since the process started, with the values from whichever request last sent each one.Two things keep the existing defences from covering this:
PHPBridge.kt:324clears five names — the Inertia ones — and clears them by setting""rather than removing them. Every other header keeps its previous, non-empty value.$_SERVERsweep to the dispatch eval (php_bridge.c:553), but the eval re-imports the environment twenty lines later (php_bridge.c:575,foreach (getenv() as $__k => $__v) { $_SERVER[$__k] = $__v; }), so the stale keys come straight back.$_SERVERcannot be cleaned while the environment it is rebuilt from is not.Expected:
$_SERVER['HTTP_X_FOO']is absent on a request that did not sendX-Foo.iOS does not have this problem —
PersistentPHPRuntime.swiftrecords the keys it sets andunsetenvs them after each dispatch.How to reproduce the bug
No backend, no Inertia — a stock app, one route, three
fetch()calls.Release build, 4.1.0, physical device:
$_SERVER['HTTP_X_FOO']X-Foo: barbarbarbar(absent)bar(absent)Config: default runtime mode (persistent), embedded PHP 8.5.9.
Not visible under Jump —
forwardToRemote()proxies to the dev server and never enters a PHP dispatch, so no header ever becomes an environment variable.Debug Output
Which operating systems have you seen this occur on?
macOS
Which platforms were you trying to build for?
Android (Device)
Notes
Device: Pixel 4a (5G), Android 14, arm64-v8a, WebView 150.0.7871.183. Built with
native:package android --build-type=release, installed withadb install.NATIVEPHP_RUNTIME_MODEleft at its default (persistent).The user-visible form of this is what #119 set out to fix: an Inertia XHR sets
HTTP_X_INERTIA, and the next top-level navigation inherits it and renders the page object as raw JSON. #119 fixed the$_SERVERhalf; the environment half remains, and it affects every header, not only the Inertia five.A PR follows that tracks the header env vars each request sets and removes them after the dispatch — the same thing iOS already does.