fix: route editor REST requests through a native proxy under iOS Lockdown Mode#544
Draft
jkmassel wants to merge 1 commit into
Draft
fix: route editor REST requests through a native proxy under iOS Lockdown Mode#544jkmassel wants to merge 1 commit into
jkmassel wants to merge 1 commit into
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/544")Built from f933707 |
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.
Summary
Fixes media uploads failing with "Could not get a valid response from the server." when iOS Lockdown Mode is enabled. Ref CMM-2014.
EditorNetworkProxy— a loopback HTTP proxy built on theGutenbergKitHTTPserver from feat: add cross-platform HTTP/1.1 parser and local proxy server for iOS and Android #367 that relays the editor's REST API requests throughURLSession{port, token}to the editor viawindow.GBKitRoot Cause
The editor is a
file://page (loadFileURL), and every REST request it makes is a cross-originfetch()that normally bypasses CORS via theallowUniversalAccessFromFileURLspreference. Lockdown Mode stops honoring that exemption while the preference still changes how WebKit serializes the page's origin: requests go out withOrigin: file://.WordPress core sanitizes the echoed origin in
rest_send_cors_headers()throughesc_url_raw(), whose protocol allowlist does not includefile— so both WP.com and self-hosted sites respond with an emptyAccess-Control-Allow-Origin. WebKit rejects the response, and api-fetch surfaces its genericfetch_error— the exact message in the user reports.Verified on an iPhone 15 Pro (iOS 26.5) with system Lockdown Mode enabled, using a logging echo server to observe the wire:
Origin: file://(current editor)Origin: null'null')Origin: file://, echoed verbatim by the serverTwo corollaries worth knowing: requests do reach the server — only the response is discarded — so a "failed" upload can still create an attachment; and endpoints answering an unconditional
Access-Control-Allow-Origin: *keep working, which is why the editor otherwise appears functional.What We Explored
WKWebpagePreferences.isLockdownModeEnabled = false) crashes withNSInternalInconsistencyException: iOS requires the restrictedcom.apple.developer.web-browserentitlement, which only browsers can hold.Origin: null, which WordPress accepts — verified on device — but the editor fails to boot: Vite's dynamicimport()offile://chunks requiresallowFileAccessFromFileURLs.Origin: gbk-probe://probe-host, which the same sanitization reduces to an empty header. Same failure asfile://.Content-Disposition) changes nothing — the failure is response-side CORS, not the body.Fix
The web view fetches
http://127.0.0.1:<port>and native code performs the real request — CORS is negotiated with a server we control, which echoes the page'sOriginverbatim (WebKit accepts an echoedfile://).HTTPServer(loopback-only, per-session bearer token viaRelay-Authorization). Forwards a request only when itsX-GBK-Upstream-URLfalls under the configuredsiteApiRoot, streams disk-buffered bodies, and answers preflights.defaultWebpagePreferences.isLockdownModeEnabledis set, which reflects the system state at web view creation. Also gains aGUTENBERG_FORCE_LOCKDOWN_MODE=1debug hook, since Lockdown Mode's web view restrictions can be forced per-view — this is how the fix is testable in the Simulator.networkProxypayload.networkProxyFallbackMiddleware, registered innermost so it sees the fully-built request. It retriesfetch_errorrejections through the proxy and prefers the proxy for subsequent requests after the first success.GutenbergKitnow depends onGutenbergKitHTTP.Nothing changes outside Lockdown Mode. The proxy only starts when the lockdown flag is true at web view creation, and the middleware is a no-op when
GBKit.networkProxyis absent.The proxy is not a general relay. It binds to
127.0.0.1, rejects requests without the per-session token, refuses upstream URLs outside the site's API root, and strips any client-suppliedAuthorizationin favor of the natively-held credential.Test plan
wp.apiFetchPOST /wp/v2/mediawith multipartFormData) created attachmentid=5on a local wp-env site: the direct fetch rejected, the middleware retried through the proxy. Notably, the wp-env Playground server sends no usable CORS headers to any origin, so this exercises the worst-case server.swift build(host platform) andeslintpass.GUTENBERG_FORCE_LOCKDOWN_MODE=1against wp-env (make wp-env-start), insert an image from the block inserter. Without this PR the snackbar reads "Could not get a valid response from the server."; with it, the image uploads and the block'ssrcswaps to the site URL.network-proxyOSLog category logs no "listening" line, and uploads behave as on trunk.Before review
Authorizationreplacement, preflight and CORS echo) and the JS middleware (fallback, sticky preference, parse semantics)LockdownModeSheetcopy should soften now that uploads work —WebAssembly/FileReader-dependent features remain degraded (tracked with CMM-2014)Related issues