Repository navigation
Scope the AndroidPOST web view bridge to the app's own origin - #655
Merged
Merged
Conversation
addJavascriptInterface exposes the injected object to every frame in the web view, so a cross-origin iframe the app happened to load could call the request-body and CSRF methods on AndroidPOST and feed or poison what reaches PHP. Move AndroidPOST to androidx WebViewCompat.addWebMessageListener scoped to the origins the app actually serves pages from (http://127.0.0.1 and http://localhost), so only first-party page content can reach it. The page now talks to the bridge with AndroidPOST.postMessage(json); the listener unpacks a {method, args} envelope and calls the existing JSBridge methods, which are unchanged. The request-body shim and the CSRF injector both go through one helper that works with either transport. WebViews too old for WebMessageListener keep addJavascriptInterface with a logged warning, because refusing the bridge there would break every form and non-GET request; the @JavascriptInterface methods stay for that path. storePostBody/consumePostBody already wait briefly for the body to arrive, so moving from a synchronous interface call to an async postMessage doesn't drop bodies on the normal fetch, XHR and form paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
simonhamp
added a commit
that referenced
this pull request
Nov 5, 2026
* Authenticate native device events so a web view page can't forge them /_native/api/events has no middleware, so a page can POST to it itself. The 3.3.11 marker gate already stops it from constructing an arbitrary class, but a page could still fire a real device event — a forged PaymentCompleted, a Biometric\Completed(success: true), a scanner or camera result — and run the app's listeners as if the device sent it. The native shell now signs every event it posts there with the app key (held in the Keychain/Keystore, never handed to the page) over a per-event nonce, a timestamp and the exact request body. PHP recomputes the HMAC with the same key and rejects anything stale, replayed or unsigned, then fires the event. The shell builds the body by concatenation and posts it verbatim, so the bytes it signs are exactly what PHP reads back as request()->getContent(). Enforcement is gated on NATIVEPHP_EVENTS_SIGNED, a native setenv on the embedded PHP set only at on-device shell boot (iOS NativePHPApp.setupEnvironment, Android LaravelEnvironment). It never reaches a shared PHP bootstrap or a dev-machine PHP, so those stay unsigned and enforcement stays off there. An app that updates only the composer package without rebuilding keeps working unsigned until it reinstalls the native project. The PHP verifier (Support/NativeEventSignature.php) is byte-identical to the 4.x line, and the two native signers match its purpose string, message layout and header names. Backport of #653. Reported by acd421 in a retest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Harden the Android web view: gate remote debugging and origin-scope AndroidPOST Two residuals acd421 flagged in the retest, both in WebViewManager. setWebContentsDebuggingEnabled(true) was unconditional, so release builds shipped with chrome://inspect debugging on — anyone with device or ADB access could inspect and drive the web view in production. Gate it on the app's debuggable flag so it's on for debug builds and off for release. addJavascriptInterface exposes the AndroidPOST bridge to every frame in the web view, including any cross-origin iframe the app loads, so a foreign frame could poison the stored CSRF token or feed body data to PHP through the request-interception plumbing. Move AndroidPOST to WebViewCompat .addWebMessageListener scoped to the origins the app actually serves pages from (http://127.0.0.1 and http://localhost), so only first-party content reaches it. The page now talks to the bridge via AndroidPOST.postMessage(JSON); a shared helper sends through either transport, and the request-body shim's existing 50ms wait in PHPBridge.consumePostData absorbs postMessage's async delivery on the fetch/XHR/form paths. WebViews too old for WEB_MESSAGE_LISTENER keep addJavascriptInterface with a logged warning, since refusing the bridge there would break every form and non-GET request. Backport of #654 and #655. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Block OTA bridge calls from the web view Ota.Download takes a caller-supplied URL and stages a zip that core applies on the next boot with no signature check, so a web view page (an XSS, or a bundled third-party script) calling it through /_native/api/call could stage an attacker bundle and get it run as the app's code next launch. OTA is app-logic, not a page action — app PHP drives it in-process via nativephp_call, which does not pass through this controller — so refuse the Ota. prefix from the web view with a 403. 3.x has no AsyncTask internal methods, so the refused-prefix list is just ['Ota.']. The gate ships with the composer package, independent of whether the app has rebuilt its native project. Backport of #656; found during the post-retest plugin sweep. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scopes the Android
AndroidPOSTweb view bridge to the app's own origin, from acd421's retest.addJavascriptInterface(..., "AndroidPOST")exposed the request-body-capture and CSRF-token methods to every frame in the web view, including a cross-origin iframe the app might load. This moves toWebViewCompat.addWebMessageListenerscoped to the app's own origin, so only first-party page content can reach the bridge, with a feature-check fallback for older WebViews. The request-interception behaviour (streaming body capture, CSRF token storage) is preserved.See the branch's own commit notes for the fallback choice. Native change: reaches an installed app after
native:install --force+ rebuild.🤖 Generated with Claude Code