Skip to content

Fix exchange url injection - #104

Merged
shangl merged 1 commit into
GoogleChromeLabs:mainfrom
shangl:fix-exchange-url-injection
Sep 16, 2026
Merged

shangl merged 1 commit into
GoogleChromeLabs:mainfrom
shangl:fix-exchange-url-injection

Conversation

@shangl

@shangl shangl commented Sep 16, 2026 •

Copy link
Copy Markdown
Collaborator

Validate exchange URL origin to prevent injection into signed bundles

Exchange URLs in Web Bundles were constructed by string-concatenating
baseURL with relative asset paths derived from on-disk filenames.
Because ':' is a legal character in POSIX directory and filenames, an
attacker placing a directory structure like 'static/https:/attacker.test/p.js'
with an empty baseURL (the default) caused the plugin to add an exchange
for 'https:/attacker.test/p.js'. Under WHATWG URL parsing rules, this
normalizes to 'https://attacker.test/p.js', embedding an arbitrary
foreign-origin exchange into the emitted (and developer-signed) bundle.

This patch adds assertExchangeUrlOrigin(exchangeUrl, baseURL) to ensure:

  • Any exchange URL that parses as an absolute URL has its origin verified
    against baseURL.
  • If baseURL is empty or relative, absolute exchange URLs are rejected.
  • If baseURL is an absolute URL, the protocol, host, and port must match
    (supporting both standard web schemes and non-standard schemes like
    isolated-app:).
  • Relative exchange URLs remain allowed when baseURL is relative or empty.

Unit tests are added across shared utilities, rollup plugin, and webpack plugin.

Bug: 530773137

@shangl
shangl requested a review from GrapeGreen September 16, 2026 13:57
@shangl
shangl force-pushed the fix-exchange-url-injection branch 2 times, most recently from 8b088c6 to 086f960 Compare September 16, 2026 14:18
Exchange URLs in Web Bundles were constructed by string-concatenating
baseURL with relative asset paths derived from on-disk filenames.
Because ':' is a legal character in POSIX directory and filenames, an
attacker placing a directory structure like 'static/https:/attacker.test/p.js'
with an empty baseURL (the default) caused the plugin to add an exchange
for 'https:/attacker.test/p.js'. Under WHATWG URL parsing rules, this
normalizes to 'https://attacker.test/p.js', embedding an arbitrary
foreign-origin exchange into the emitted (and developer-signed) bundle.

This patch adds assertExchangeUrlOrigin(exchangeUrl, baseURL) to ensure:
- Any exchange URL that parses as an absolute URL has its origin verified
  against baseURL.
- If baseURL is empty or relative, absolute exchange URLs are rejected.
- If baseURL is an absolute URL, the protocol, host, and port must match
  (supporting both standard web schemes and non-standard schemes like
  isolated-app:).
- Relative exchange URLs remain allowed when baseURL is relative or empty.

Unit tests are added across shared utilities, rollup plugin, and webpack plugin.

Bug: https://b.corp.google.com/issues/530773137
@shangl
shangl force-pushed the fix-exchange-url-injection branch from 086f960 to fef73dc Compare September 16, 2026 14:56
@shangl
shangl merged commit af90d14 into GoogleChromeLabs:main Sep 16, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants