Skip to content

fix!: send the Authorization header only to the site and the hosts the app names - #757

Draft
jkmassel wants to merge 1 commit into
trunkfrom
jkmassel/pr-workspace-name
Draft

jkmassel wants to merge 1 commit into
trunkfrom
jkmassel/pr-workspace-name

Conversation

@jkmassel

@jkmassel jkmassel commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Fixes a bug where the site's Authorization header was attached to every request the editor made, whatever the host. A plugin script served from a vendor's CDN, or a block calling apiFetch( { url } ) against another party's service, received the site's credentials.

The header now goes to the site and its REST API, plus any hosts the app names in a new authHeaderDomains configuration option.

This is a breaking change. Both native EditorHTTPClient constructors gain a required authorizationScope parameter. A GitHub code search finds no call to either constructor in WordPress-iOS or WordPress-Android.

Which requests carry the header

A request carries it when either of these holds:

  1. Its origin — scheme, host, and port — is the origin of siteURL or of siteApiRoot.
  2. It is over HTTPS and its host matches an entry in authHeaderDomains.

Each entry is taken exactly as written:

Entry Matches
s0.wp.com s0.wp.com only — not its subdomains, not wp.com
*.wp.com wp.com and every subdomain of it, at any depth
*.com every .com host

An entry is ignored when it is empty, or has a * anywhere but as the whole first label (.wp.com, s*.wp.com, *.*.com).

An app that names nothing keeps working against its own site. The list is empty by default, and the first rule needs no configuration. Because it compares origins, a lookalike host (https://example.com.vendor.net) and the site's own host over http are both refused.

The library infers nothing about WordPress.com. A site reached through WordPress.com is served from more than its own address, so the app names those hosts: *.wp.com and *.files.wordpress.com. Both demo apps do so for WordPress.com accounts, and name nothing for self-hosted sites.

Changes

  • Add EditorConfiguration.authHeaderDomains on both platforms — iOS [String] with setAuthHeaderDomains(_:), Android Set<String> — carried through toBuilder, equality, and hashing.
  • Add EditorAuthorizationScope on iOS and Android, and isWithinAuthorizationScope in src/utils/authorization-scope.js. Each platform's copy of the rule lives in that one place.
  • Scope the native clients. iOS EditorHTTPClient.configureRequest and Android EditorHTTPClient.perform / download attach the header only within the scope. iOS gains EditorHTTPClient(configuration:), which EditorService and EditorViewController now use.
  • Scope the manifest request of Android's org.wordpress.gutenberg.EditorAssetsLibrary, which opens its own HttpURLConnection rather than going through EditorHTTPClient. A custom editorAssetsEndpoint on another host no longer receives the header unless that host is named.
  • Scope tokenAuthMiddleware. A request by path is for the site's API and keeps the header. A request by url alone is checked. credentials: 'omit' is now set only when the header is attached.
  • Pass authHeaderDomains to the web editor through GBKitGlobal, so it applies the list the native side was configured with.
  • Document the rule in docs/code/authorization.md, with a short section in docs/integration.md.

jQuery AJAX requests (src/utils/ajax.js) were already limited to the site's origin and are unchanged.

What we explored

  1. Inferring WordPress.com from the API root — treating siteApiRoot on public-api.wordpress.com as permission to send the token to *.wp.com. Rejected: it put WordPress.com host names in library code and made the library decide what a WordPress.com site is. The app already knows, so the app says.
  2. Refusing a wildcard over a top-level domain by requiring two labels after *.. Removed: iOS and the web view have no public-suffix list, so the rule refused *.com but accepted *.co.uk. A wildcard is taken at its word instead, and the docs say to name the narrowest domain that will do.

Not in this PR

  • A host-supplied HTTP client is not covered. An app's own EditorHTTPClientProtocol implementation adds whichever headers it likes.
  • Requests the web view makes itself never carried the header, and still don't. Naming *.files.wordpress.com does not by itself make a private site's media display.
  • WordPress-iOS and WordPress-Android need to set authHeaderDomains for sites reached through WordPress.com when they take this release. Until they do, their token goes only to the site and to public-api.wordpress.com.

Test plan

  • iOS: swift test — 583 and 395 tests pass. make lint-ios reports no violations.
  • iOS: the library and the demo app both build for the simulator. This is the only check on the EditorViewController change, which the host build compiles out.
  • Android: detekt is clean, :Gutenberg:testDebugUnitTest passes 735 tests, and the demo app compiles.
  • Web: make test-web-unit passes 369 tests in 25 files. ESLint and Prettier are clean.
  • The new tests fail when the header is made unconditional again, on every path:
    • iOS EditorHTTPClientTests: 6 issues, e.g. a request to another party's host goes out without the Authorization header.
    • Android EditorHTTPClientAuthorizationTest: perform sends no Authorization header to another party's host and its download twin.
    • Android EditorAssetsManifestAuthorizationTest: the manifest request sends no Authorization header to an endpoint on another party's host.
    • Web api-fetch.test.js: 4 failures, e.g. should not send the auth header with a request by URL to a lookalike host.
  • In either demo app, open the editor for a self-hosted site with an application password: the editor loads, and plugin and theme blocks appear in the inserter. This is the path that names no domains.
  • In either demo app, open the editor for a WordPress.com site: the editor loads with the site's plugin blocks and theme styles, whose assets come from wp.com hosts.

Neither demo-app step has been run yet — this PR has had no device, simulator, or E2E run.

…e app names

The site's `Authorization` header was attached to every request the
editor made, whatever the host: iOS `EditorHTTPClient.configureRequest`,
Android `EditorHTTPClient.perform` and `download`, the manifest request
of Android's `org.wordpress.gutenberg.EditorAssetsLibrary`, and the web
editor's `tokenAuthMiddleware`. An asset on another party's host, or an
`apiFetch( { url } )` to another party's service, received the site's
credentials.

A request now carries the header when its origin (scheme, host and port)
is that of `siteURL` or `siteApiRoot`, or when it is over HTTPS and its
host matches an entry in the new `EditorConfiguration.authHeaderDomains`.
An entry is taken as written: `s0.wp.com` is that one host, and
`*.wp.com` is `wp.com` and every subdomain of it. The library infers
nothing about WordPress.com; both demo apps name `*.wp.com` and
`*.files.wordpress.com` for WordPress.com accounts.

The rule lives in `EditorAuthorizationScope` on iOS and Android and in
`isWithinAuthorizationScope` on the web, and `GBKitGlobal` carries
`authHeaderDomains` to the web editor. On the web, a request by `path`
is for the site's API and keeps the header; a request by `url` alone is
checked.

BREAKING CHANGE: both native `EditorHTTPClient` constructors take a
required `authorizationScope`. iOS gains
`EditorHTTPClient(configuration:)`, which derives it.
@github-actions github-actions Bot added the [Type] Breaking Change For PRs that introduce a change that will break existing functionality label Oct 2, 2026
@jkmassel jkmassel self-assigned this Oct 2, 2026
@wpmobilebot

Copy link
Copy Markdown

XCFramework Build

This PR's XCFramework is available for testing. Add the following to your Package.swift:

.package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/757")

Built from 07ed4d6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Android iOS [Type] Breaking Change For PRs that introduce a change that will break existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants