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 SQL-injection-style template literal injection happens in JavaScript DOM rendering and how to fix it

A Semgrep rule (`utils.custom.sql-injection-template-literal`) flagged `src/export/SheetMusicView.js` for building a query/markup string out of a JavaScript template literal with untrusted values interpolated directly into it. In this case the sink was an `<option value="${s.id}">${s.name}</option>` string used to build the snippet picker, meaning any snippet name containing `"` or `<` could break out of the attribute and inject arbitrary HTML. The fix introduces an `_escapeHtml()` helper and ro

critical

How Unvalidated External Data Fetch happens in React and how to fix it

The Datasets.jsx component fetched a remote manifest from snapshots.qdrant.io and rendered its contents directly into React state without validating response status, JSON shape, or field types. A compromised or spoofed endpoint could have injected malicious payloads straight into the UI; the fix adds strict validation and type coercion before the data ever reaches the render tree.

medium

How Cross-Site Scripting Happens in JavaScript Parsers and How to Fix It

A cross-site scripting vulnerability in JSXGraph's JessieCode parser allowed attackers to inject JavaScript through maliciously crafted input that appeared in error messages. The fix ensures proper output encoding when user-controlled data is included in parser error reporting.

critical

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

A critical XSS vulnerability was discovered in the `sanitizeInput()` function in script.js, where only angle brackets were being escaped while quotes, ampersands, and backticks remained unprotected. This incomplete sanitization allowed attackers to craft payloads using event handlers and template literals that bypassed the security controls entirely. The fix implements comprehensive HTML entity encoding for all XSS-relevant characters.

high

How DOM-based Cross-Site Scripting happens in JavaScript and how to fix it

A high-severity DOM-based XSS vulnerability in `public/audio_match_demo/index.html` allowed attackers to inject malicious JavaScript through manipulated song metadata from API responses. The fix replaces dangerous HTML string concatenation with secure DOM API methods that automatically escape content.

critical

How Cross-Site Scripting happens in fast-xml-parser and how to fix it

CVE-2026-25896 is a critical Cross-Site Scripting vulnerability in fast-xml-parser stemming from improper DOCTYPE entity handling, which could allow attackers to inject malicious scripts through crafted XML payloads. The fix upgrades the vulnerable dependency from version 4.4.1 to patched versions 5.3.5 and 4.5.4, eliminating the unsafe parsing behavior while preserving all legitimate XML processing functionality.