From 12405cff4b2bf5db8fb0488eeedb63c4aace640d Mon Sep 17 00:00:00 2001 From: arturovt Date: Sat, 23 May 2026 13:16:54 -0700 Subject: [PATCH] fix(service-worker): verify event source before processing INITIALIZE message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, the `INITIALIZE` action in `onMessage` was handled before the `isClient()` guard, allowing any source — including cross-origin iframes or unknown `MessagePort` handles — to trigger SW initialization without verification. The fix explicitly checks that the message source is either the SW's own registration or a legitimate client before proceeding with `ensureInitialized`. **Example of the previous behavior:** A cross-origin iframe with a `postMessage` handle to the SW could trigger initialization at an arbitrary time: ```js // attacker-controlled iframe on https://evil.com navigator.serviceWorker.getRegistration('/').then(reg => { reg.active.postMessage({ action: 'INITIALIZE' }); }); ``` This would bypass the `isClient()` check that guards all other actions and invoke `ensureInitialized()` from an untrusted source, racing with cache writes during first-load initialization. **After the fix**, messages with `action: 'INITIALIZE'` are accepted only from the SW's own registration (`self.registration.active/installing/waiting`) or from a verified client. All other sources are silently dropped, consistent with how all other actions are handled. --- packages/service-worker/worker/src/driver.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/service-worker/worker/src/driver.ts b/packages/service-worker/worker/src/driver.ts index c48a63c230a2..7ebef65a7a60 100644 --- a/packages/service-worker/worker/src/driver.ts +++ b/packages/service-worker/worker/src/driver.ts @@ -309,9 +309,22 @@ export class Driver implements Debuggable, UpdateSource { event.waitUntil( (async () => { - // Initialization is the only event which is sent directly from the SW to itself, and thus - // `event.source` is not a `Client`. Handle it here, before the check for `Client` sources. + // INITIALIZE is sent by the SW to itself on activation, so event.source is + // the SW registration (not a Client). We still verify this explicitly to + // prevent arbitrary external sources from triggering initialization. if (data.action === 'INITIALIZE') { + // Accept INITIALIZE only from the SW itself (non-Client source) or from + // a legitimate Client — reject anything else (e.g. cross-origin iframes). + const isOwnSW = + event.source === this.scope.registration.active || + event.source === this.scope.registration.installing || + event.source === this.scope.registration.waiting; + const isLegitimateClient = this.adapter.isClient(event.source); + + if (!isOwnSW && !isLegitimateClient) { + return; + } + return this.ensureInitialized(event); }