Conversation
XCFramework BuildThis PR's XCFramework is available for testing. Add the following to your .package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/730")Built from a219102 |
`onPageStarted` received the loaded URL and discarded it, so any page reaching the main frame was handed `window.GBKit` — the site credential and the upload server's port and token. `shouldOverrideUrlLoading` admits several site URLs into that frame, and since #181 the editor shares an origin with the site, so those pages are served by the site's own theme and plugins. Check the destination before advertising the globals or starting the upload server, matching the dev server by authority so a local site on another port of the same host is not mistaken for the editor. Readiness still resets for any page, since navigating away from the editor leaves it unusable either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zAv5Tqtqr1bcYWVDJHGAh
…ring The navigation policy admitted any site URL whose path contained `/wp-json/` or whose query contained `rest_route=`. Both are satisfied by ordinary pages — `/blog/wp-json/a-post`, or any URL carrying `?utm_campaign=rest_route=x` — which WordPress serves with the site's theme and plugins, inside the editor's own frame. Compare against the configured `siteApiRoot` instead: a path under its path root, or `rest_route` as an actual query parameter. Reading the root also settles the cases the characters cannot, so the same path is the API on a subdirectory install and a page on a root install. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zAv5Tqtqr1bcYWVDJHGAh
Without pretty permalinks the API root is `/index.php?rest_route=/`, and `/index.php` also serves ordinary pages, so matching its path admitted them into the editor frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSbnGRWvgz7WNn6cGFRKxs
A root without a trailing slash, such as `/wp-json`, otherwise prefixes page slugs like `/wp-json-tutorial/`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSbnGRWvgz7WNn6cGFRKxs
WordPress skips an empty route, including `0`, and renders the requested page with the site's theme instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSbnGRWvgz7WNn6cGFRKxs
Checking only the last evaluated script would pass if another script ran after an injection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSbnGRWvgz7WNn6cGFRKxs
The asset loader serves one scheme, so the same path over the other reaches the site over the network, yet it was admitted and handed the editor globals. One helper now backs both checks so they can't drift apart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSbnGRWvgz7WNn6cGFRKxs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSbnGRWvgz7WNn6cGFRKxs
The asset loader also serves the host app's other bundled pages, some of which load third-party scripts, and those received the editor globals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSbnGRWvgz7WNn6cGFRKxs
WordPress lets the parameter override the route a `/wp-json/` path sets, so `/wp-json/?rest_route=` serves the themed front page, yet it passed the path check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSbnGRWvgz7WNn6cGFRKxs
The editor reaches the REST API by fetch, which never passes through `shouldOverrideUrlLoading`, so the allowlist only admitted navigations. Those let site pages whose URLs WordPress reads differently from Android, and http API URLs on https sites, replace the editor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSbnGRWvgz7WNn6cGFRKxs
dcalhoun
force-pushed
the
fix/android-scope-config-injection
branch
from
September 25, 2026 20:37
1a1a592 to
39894ad
Compare
Since the REST allowlist was removed, the navigation policy admits no site pages, but loads it never sees still can. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSbnGRWvgz7WNn6cGFRKxs
Nothing pinned the editor-only check above the server start, so reordering them would have passed every test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSbnGRWvgz7WNn6cGFRKxs
… client The client reads it, so assigning it alongside the asset authority avoids relying on no navigation running in between. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSbnGRWvgz7WNn6cGFRKxs
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.
What?
Mitigate exposing editor globals to non-editor pages.
Why?
The editor globals contain configuration that should only be accessible to the editor HTML page and its scripts.
How?
fetchof these URLs works without thisTesting Instructions
Manual testing on a device against wp-env, in both configurations, using
chrome://inspectto reach the editor WebView's console:GUTENBERG_EDITOR_URLunset inandroid/local.properties, open a post.window.GBKitis defined.blob:frames, neither of which the policy gates.window.top.location.href = location.origin + '/wp-json/wp/v2/posts'window.top.location.href = location.origin + '/?rest_route=/wp/v2/posts&rest_route='(ontrunk, the themed front page)httpssite (not wp-env),window.top.location.href = 'http://' + location.host + '/wp-json/wp/v2/posts'android/app/src/main/assets/probe.html, rebuild, and navigate tolocation.origin + '/assets/probe.html'. It loads, and in its consolewindow.GBKitisundefined(ontrunk, the object).GUTENBERG_EDITOR_URL=http://10.0.2.2:5173/, runmake dev-server, open a post.window.GBKitis defined.http://10.0.2.2:8888/— the wp-env site on another port of the same host. It opens in the OS browser rather than being taken for the dev server.Accessibility Testing Instructions
N/A, no user-facing changes.
Screenshots or screencast
N/A, no user-facing changes.
AI-generated details
Problem
onPageStartedreceived the URL of the page that had begun loading and discarded it, sowindow.GBKit— the site credential and the local upload server's port and token — was injected into whatever loaded in the main frame. Separately, the navigation policy admitted the REST API into the main frame, recognizing it by substring: any path containing/wp-json/or any query containingrest_route=.Impact
Since #181 the Android editor loads from the site's own origin, so the pages those substrings admit are ordinary pages that WordPress serves with the site's theme, plugins and third-party scripts — rendered inside the editor's frame, and handed the credential in a readable global.
/blog/wp-json/a-postand/a-page/?utm_campaign=rest_route=xboth qualify. iOS is unaffected: it blocks all main-frame navigation outside the editor.Both behaviors reproduce on
trunk; the Robolectric cases added here fail against it.Mechanism
onEditorPageStartedtakes the loaded URL and advertises the globals, and starts the upload server, only for the editor document. Readiness still resets for any page, since navigating away leaves the editor unusable either way. The dev server is matched with fix(android): match the dev server by host and port #729'sisDevServerUrl, by authority, so a local site on another port of the same host is not mistaken for the editor.shouldOverrideUrlLoading, so the allowlist only ever admitted navigations — and matching WordPress's URL parsing proved open-ended: a duplicate or emptyrest_route, orhttpon anhttpssite, still let a page replace the editor.GutenbergViewNavigationTestrather thanGutenbergViewTest, which Detekt flags asLargeClassonce they are added.Testing
Unit and lint, both green, and each commit passes on its own:
Manual testing on a device against wp-env, in both configurations, using
chrome://inspectto reach the editor WebView's console:GUTENBERG_EDITOR_URLunset inandroid/local.properties, open a post.window.GBKitis defined.blob:frames, neither of which the policy gates.window.top.location.href = location.origin + '/wp-json/wp/v2/posts'window.top.location.href = location.origin + '/?rest_route=/wp/v2/posts&rest_route='(ontrunk, the themed front page)httpssite,window.top.location.href = 'http://' + location.host + '/wp-json/wp/v2/posts'android/app/src/main/assets/probe.html, rebuild, and navigate tolocation.origin + '/assets/probe.html'. It loads, and in its consolewindow.GBKitisundefined(ontrunk, the object).GUTENBERG_EDITOR_URL=http://10.0.2.2:5173/, runmake dev-server, open a post.window.GBKitis defined.http://10.0.2.2:8888/— the wp-env site on another port of the same host. It opens in the OS browser rather than being taken for the dev server.make test-android-library-e2eon an emulator.Not verified on a device: asset paths over the other scheme, covered by a unit case.
The
localStoragecopy of the globals is removed separately in #613.