Back to Blog
critical SEVERITY7 min read

How Wildcard postMessage Origins Happen in Chrome Extensions and How to Fix Them

A critical cross-origin message injection vulnerability was discovered in `offscreen.js`, where a wildcard `"*"` origin in `postMessage` calls and a missing source validation check allowed any webpage to send arbitrary messages to the extension's iframe. The fix adds an explicit source check and replaces the wildcard with `"null"` to restrict communication to the trusted iframe only. This change prevents malicious websites from hijacking the extension's offscreen message channel.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published August 26, 2026•Reviewed August 26, 2026

Answer Summary

This vulnerability is a Cross-Origin Message Injection (CWE-346: Origin Validation Error) in a Chrome Extension's `offscreen.js` file. The `iframe.contentWindow.postMessage(request.data, "*")` call on line 20 used a wildcard target origin, and the `handleMessage` event listener accepted messages from any source without validating `event.source`. The fix adds an `event.source !== iframe.contentWindow` guard at the top of `handleMessage` and replaces `"*"` with `"null"` in the `postMessage` call, ensuring only the trusted sandboxed iframe can exchange messages with the extension background.

Vulnerability at a Glance

cweCWE-346
fixReplace "*" with "null" in postMessage and add event.source === iframe.contentWindow guard
riskMalicious websites can inject arbitrary messages into a Chrome extension's offscreen iframe communication channel
languageJavaScript
root causepostMessage called with wildcard target origin "*" and no event.source validation in the message listener
vulnerabilityCross-Origin Message Injection via Wildcard postMessage

How Wildcard postMessage Origins Happen in Chrome Extensions and How to Fix Them

Summary

A critical cross-origin message injection vulnerability was found in offscreen.js, where a wildcard "*" target origin in postMessage and a missing event.source check in the message listener allowed any webpage to inject arbitrary messages into a Chrome extension's offscreen iframe. Two targeted lines of code — adding a source validation guard and replacing "*" with "null" — close the attack surface entirely.


Introduction

The offscreen.js file manages communication between a Chrome extension's background service worker and a sandboxed <iframe> used for offscreen processing. This pattern is common in Manifest V3 extensions that need access to DOM APIs unavailable in service workers. But a flaw in the handleMessage function and the postMessage call on line 20 created an open channel that any malicious website could exploit.

The specific problem: iframe.contentWindow.postMessage(request.data, "*") broadcast messages to any origin, and handleMessage accepted responses from any source — including attacker-controlled frames. For developers building Chrome extensions with offscreen documents, this is a subtle but high-impact mistake that's easy to introduce and hard to spot in code review.


The Vulnerability Explained

The Wildcard Origin Problem

When you call postMessage with "*" as the target origin, you're telling the browser: "deliver this message to the target frame regardless of what origin it's on." For a sandboxed iframe (which has a "null" origin), the correct target is "null", not "*".

Here is the vulnerable line (line 20, before the fix):

// VULNERABLE: Any origin can intercept or spoof this message
iframe.contentWindow.postMessage(request.data, "*");

Using "*" means:
1. The message is delivered without origin restriction.
2. Any frame that happens to be the iframe.contentWindow target receives the data, but more critically...
3. The listener side was also unguarded.

The Missing Source Validation Problem

The handleMessage function, which processes responses from the iframe, had no check on where the message came from:

// VULNERABLE: Accepts messages from ANY origin, ANY source
function handleMessage(event) {
    if (pendingResponse) {
        pendingResponse(event.data);
        pendingResponse = null;
    }
}

There is no event.source check. There is no event.origin check. Any window, frame, or tab that can fire a message event at this listener can call pendingResponse(event.data) with attacker-controlled data.

Attack Scenario

Consider this concrete exploitation path:

  1. A user has the vulnerable extension installed.
  2. The attacker hosts https://evil.example.com which embeds or references the extension's offscreen page.
  3. The attacker's page fires:
    javascript // Attacker's page window.postMessage({ type: "OAUTH_RESPONSE", token: "attacker-token" }, "*");
  4. Because handleMessage accepts messages from any source, pendingResponse is called with the attacker's crafted event.data.
  5. The extension processes the forged response as if it came from its own trusted iframe — potentially storing a fake OAuth token, triggering privileged actions, or corrupting extension state.

In an extension that handles OAuth tokens or API keys (as described in the broader vulnerability context), this could directly lead to credential theft or account takeover.


The Fix

Two surgical changes were made to offscreen.js:

Change 1: Add event.source Validation (Line 9)

// BEFORE
function handleMessage(event) {
    if (pendingResponse) {
        pendingResponse(event.data);
        pendingResponse = null;
    }
}

// AFTER
function handleMessage(event) {
    if (event.source !== iframe.contentWindow) return;  // ← NEW GUARD
    if (pendingResponse) {
        pendingResponse(event.data);
        pendingResponse = null;
    }
}

The new first line if (event.source !== iframe.contentWindow) return; immediately discards any message that didn't originate from the specific iframe element the extension controls. Even if an attacker manages to send a message event to this listener, it will be silently dropped because event.source will be the attacker's window, not iframe.contentWindow.

Change 2: Replace Wildcard with "null" (Line 20)

// BEFORE
iframe.contentWindow.postMessage(request.data, "*");

// AFTER
iframe.contentWindow.postMessage(request.data, "null");

Sandboxed iframes — including Chrome extension offscreen documents — have an origin of "null". By specifying "null" as the target origin, the browser will only deliver the message if the iframe's actual origin matches "null". This prevents the message from being delivered to any other frame that might be substituted or spoofed in place of the expected iframe.

Why Both Changes Are Necessary

These two fixes work together:

Fix What it prevents
event.source !== iframe.contentWindow guard Prevents external windows from injecting fake responses into handleMessage
postMessage(data, "null") instead of "*" Prevents the extension's outbound messages from being delivered to wrong-origin frames

Either fix alone reduces the attack surface, but both together enforce the complete principle of least privilege for this message channel.


Key Takeaways

  • postMessage(data, "*") in offscreen.js was the root cause — using the wildcard target origin in a Chrome extension's offscreen document is never appropriate and creates an open message channel.
  • handleMessage had no source guard — the absence of an event.source !== iframe.contentWindow check meant any window could trigger the pendingResponse callback with forged data.
  • Sandboxed iframes have "null" origin, not "*" — the correct postMessage target for a sandboxed iframe is the string "null", a detail that's easy to get wrong.
  • Both the sender and receiver must be hardened — fixing only the postMessage call or only the listener would leave a partial vulnerability; both changes are required for complete protection.
  • This pattern is common in Manifest V3 extensions — any extension using offscreen documents for OAuth, crypto, or DOM work should audit its postMessage calls immediately.

How Orbis AppSec Detected This

  • Source: Chrome extension background service worker passing request.data via chrome.runtime.onMessage into iframe.contentWindow.postMessage
  • Sink: iframe.contentWindow.postMessage(request.data, "*") on offscreen.js:20, and the unguarded pendingResponse(event.data) call in handleMessage
  • Missing control: No event.source validation in the message event listener, and no explicit target origin restriction in the postMessage call
  • CWE: CWE-346 — Origin Validation Error
  • Fix: Added if (event.source !== iframe.contentWindow) return; as the first line of handleMessage, and changed the postMessage target from "*" to "null"

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

The wildcard origin vulnerability in offscreen.js is a textbook example of how a single character — the "*" in postMessage — can open a significant attack surface in an otherwise well-structured Chrome extension. The fix is minimal: two lines of code that enforce the principle of least privilege for cross-origin communication. For developers building Manifest V3 extensions with offscreen documents, auditing every postMessage call and every message event listener for proper origin and source validation should be a standard part of the security review checklist.

Cross-origin messaging is a powerful browser feature, but it demands explicit trust boundaries. When those boundaries are left open, attackers will find them.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #191

Related Articles

critical

fast-xml-parser 4.5.0 XSS: DOCTYPE Entity Injection in XML.parse()

fast-xml-parser versions 4.5.0 and earlier fail to sanitize DOCTYPE entity declarations during XML parsing, allowing attackers to inject JavaScript through crafted XML payloads. The vulnerability permits reflected XSS in applications that process untrusted XML input. Upgrading to version 5.7.0 eliminates the unsafe entity expansion path.

high

sanitizeBangumiHtml() XSS Fix: insertAdjacentHTML RCE via JSON

A high-severity cross-site scripting vulnerability existed where untrusted JSON data from bangumis.json was rendered directly into the DOM using insertAdjacentHTML without sanitization. An attacker who could modify this external data source could execute arbitrary JavaScript in visitors' browsers. The fix introduces a dedicated sanitizeBangumiHtml() function that strips script tags, event handlers, and javascript: URLs before insertion.

high

form.js jQuery Selector Construction: CWE-79 Quote-Breaking Fix

A defense-in-depth fix in the form.js initialization routine eliminates a jQuery selector injection vector where user-controlled category data was concatenated directly into a string literal. The vulnerable pattern at lines 47-48 of the form handler could allow malicious content to break out of the selector's quoted context and execute arbitrary jQuery methods.

high

ItemPicker `_commitTraitInput` XSS via Unescaped Trait Chip Rendering

The `_commitTraitInput` function in ItemPicker accepted arbitrary user input for trait values without sanitization, then rendered those values directly into HTML chip elements through `_updateList`. An attacker could inject malicious JavaScript payloads that executed when trait chips were displayed. The fix applies a strict whitelist filter removing all non-alphanumeric characters except spaces and hyphens.

high

formatObject() Leaves localStorage Keys Unescaped Before innerHTML

The `formatObject()` pretty-printer used to build the key-configuration storage viewer interpolated object keys and array key/value pairs directly into an HTML string that is later assigned with `innerHTML`. Only string *values* were passed through `escapeHtml()`, so a crafted key name persisted in `localStorage` could execute script every time a user opened the storage viewer. The fix wraps the index-pair branch and the object-key branch in `escapeHtml()`, keeping the raw path only for non-colo

high

Voice Assistant Widget XSS: Unsanitized Bot Messages Execute in

The voice assistant widget's `appendMessage` function had a critical cross-site scripting (XSS) vulnerability where bot messages were inserted directly into the DOM without sanitization, while user messages were escaped. An attacker controlling bot responses could inject and execute arbitrary JavaScript in the user's browser context. The fix applies HTML escaping to all message types uniformly.