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
Published August 26, 2026Reviewed 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.


Prevention & Best Practices

1. Never Use "*" as a postMessage Target Origin in Extensions

The "*" wildcard should be reserved only for cases where you genuinely don't care which origin receives the message — which is almost never the right answer in a security-sensitive context like an extension.

// ❌ Bad
iframe.contentWindow.postMessage(data, "*");

// ✅ Good (sandboxed iframe)
iframe.contentWindow.postMessage(data, "null");

// ✅ Good (known origin)
iframe.contentWindow.postMessage(data, "https://your-extension-origin.com");

2. Always Validate Both event.source and event.origin

window.addEventListener("message", (event) => {
    // Check source identity
    if (event.source !== trustedIframe.contentWindow) return;
    // Check origin (for non-sandboxed iframes)
    if (event.origin !== "https://trusted-origin.example.com") return;

    // Safe to process event.data
    handleTrustedMessage(event.data);
});

3. Validate and Sanitize event.data

Even with source and origin checks, treat event.data as untrusted input. Validate its structure and type before acting on it:

function handleMessage(event) {
    if (event.source !== iframe.contentWindow) return;

    // Validate data shape
    if (typeof event.data !== "object" || !event.data.type) return;
    if (!ALLOWED_MESSAGE_TYPES.includes(event.data.type)) return;

    // Now safe to process
    processMessage(event.data);
}

4. Use Content Security Policy (CSP)

Chrome extensions should use a strict CSP in their manifest to limit what scripts can run and what origins can communicate with the extension:

{
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self';"
  }
}

5. Relevant Standards


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.


References

Frequently Asked Questions

What is a wildcard postMessage origin vulnerability?

It occurs when postMessage is called with "*" as the target origin, meaning any webpage can receive or send messages to the target frame without restriction, violating the principle of least privilege for cross-origin communication.

How do you prevent wildcard postMessage vulnerabilities in JavaScript?

Always specify an explicit target origin string in postMessage calls instead of "*", and validate event.source and event.origin in every message event listener before processing the message data.

What CWE is wildcard postMessage origin?

CWE-346: Origin Validation Error, which covers failures to properly verify the origin of a message or request before acting on it.

Is checking event.origin enough to prevent postMessage injection?

Checking event.origin is important, but for sandboxed iframes (which have a "null" origin), you should also verify event.source matches the expected iframe's contentWindow to ensure the message comes from the correct frame.

Can static analysis detect wildcard postMessage vulnerabilities?

Yes. Static analysis tools like Semgrep can detect postMessage("*") patterns and missing event.origin/event.source checks. Orbis AppSec's multi-agent AI scanner flagged this exact pattern in offscreen.js.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #191

Related Articles

high

How Cross-Site Scripting happens in jsPDF and how to fix it

CVE-2026-31938 is a critical cross-site scripting vulnerability in jsPDF versions prior to 4.2.1, where unsanitized output options could allow attackers to inject malicious scripts into PDF generation workflows. The fix upgrades jsPDF from 3.0.4 to 4.2.1 in both `package.json` and `pnpm-lock.yaml`, closing the attack surface in Handsontable's export-to-PDF feature. Developers using jsPDF in any web application should upgrade immediately, as this vulnerability is assessed as likely exploitable.

critical

How Cross-Site Scripting happens in XML parsing libraries and how to fix it

CVE-2026-25896 is a critical Cross-Site Scripting vulnerability in the `fast-xml-parser` npm package caused by improper handling of DOCTYPE entity declarations. The flaw was discovered in the `mail-worker` service's dependency tree and patched by upgrading to version 5.3.5/4.5.4 and enforcing the fix via a pnpm override to `5.7.0`. Left unpatched, this vulnerability could allow attackers to inject malicious scripts through crafted XML payloads processed by the mail pipeline.

high

How React Router SSR XSS in ScrollRestoration Happens and How to Fix It

CVE-2026-21884 is a high-severity cross-site scripting (XSS) vulnerability in React Router's ScrollRestoration component that affects server-side rendering (SSR) implementations. The vulnerability was introduced through unsafe handling of scroll position data that could be influenced by untrusted input. This fix upgrades react-router from version 7.9.5 to 8.3.0, replacing the vulnerable `cookie` dependency with `cookie-es` and removing the `set-cookie-parser` dependency entirely.

critical

How Stored Cross-Site Scripting (Stored XSS) Happens in JavaScript Map Components and How to Fix It

A critical vulnerability in the content-map component allowed attackers to inject malicious JavaScript through unsanitized title and description fields displayed in map marker popups. By implementing proper HTML entity escaping on both Leaflet and Google Maps implementations, the vulnerability was completely eliminated while preserving all legitimate functionality.

critical

How DOM-Based XSS Happens in jQuery tagsInput() and How to Fix It

A DOM-based Cross-Site Scripting (XSS) vulnerability was discovered in the VvvebJs web editor's `inputs.js` file where the jQuery `tagsInput()` function at line 932 directly inserted user-controlled data into the DOM without sanitization. The fix applies HTML entity encoding to all string values before they reach the DOM, preventing malicious script injection while preserving legitimate tag functionality.

critical

How Cross-Site Scripting (XSS) happens in JavaScript innerHTML and how to fix it

A critical Cross-Site Scripting (XSS) vulnerability was discovered in `js/main.js` where commit messages fetched from the GitHub API were directly interpolated into `innerHTML` without any sanitization. An attacker with repository write access could push a commit with a malicious message like `<img src=x onerror=alert(document.cookie)>`, causing arbitrary JavaScript execution in every visitor's browser. The fix applies HTML entity encoding to all five dangerous characters before rendering.