# Content scripts run in isolated worlds

- **Pattern:** `ab-001330` (`extension-permissions-security.content-script-isolation.isolated-world`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001330
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001330)

## Why it matters

CWE-79 (XSS) and CWE-653 (Insufficient Compartmentalization) both apply when content scripts run in the MAIN world. A content script in the MAIN world shares the JavaScript heap with the host page — the page's JavaScript can read and modify the extension's variables, and vice versa. A compromised or malicious page can tamper with the extension's logic directly, override its functions, or read any data it has loaded. OWASP A03 (Injection) governs this. Chrome's ISOLATED world is the default for a reason: it creates a separate JavaScript environment, preventing the host page from accessing or polluting the extension's execution context. The Chrome content script documentation makes this the required default.

## Severity rationale

High because running in the MAIN world removes the JavaScript heap boundary between the extension and the host page, allowing the page to directly manipulate extension state and bypass isolation.

## Remediation

Remove any `world: "MAIN"` declarations from your `content_scripts` entries in `manifest.json`. Omitting the `world` property defaults to `ISOLATED`, which is correct.

```json
{
  "content_scripts": [{
    "matches": ["https://example.com/*"],
    "js": ["content.js"]
  }]
}
```

If you need to expose a function to the page's JavaScript, use `window.postMessage` from the isolated content script — the page sends a message, the content script receives and validates it, then responds.

## Detection

- **ID:** `isolated-world`
- **Severity:** `high`
- **What to look for:** Enumerate all `content_scripts` entries in `manifest.json`. For each, check the `world` property. In MV3, `world` can be `"ISOLATED"` (default) or `"MAIN"`. Count the number of scripts using `"MAIN"` world.
- **Pass criteria:** At least 100% of content scripts run in the `ISOLATED` world (property omitted defaults to isolated, which counts). No more than 0 scripts use `world: "MAIN"` without documented justification. Quote the world configuration for each content script entry.
- **Fail criteria:** `world: "MAIN"` is used without extremely strong justification.
- **Skip (N/A) when:** No content scripts defined in manifest.
- **Detail on fail:** `"Content script is configured to run in the MAIN world. It shares the same execution environment as the page, making it vulnerable to conflict and manipulation."`
- **Remediation:** Always use the isolated world in `manifest.json` unless you are building a specific tool that *requires* modifying the page's global JS variables (rare). If you need to expose APIs to the page, use `window.postMessage`.

  ```json
  { "content_scripts": [{ "matches": ["https://example.com/*"], "js": ["content.js"] }] }
  ```

## External references

- cwe CWE-79
- cwe CWE-653
- owasp A03
- external chrome-content-script-worlds — https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#isolated_world

Taxons: injection-and-input-trust, access-control

HTML version: https://auditbuffet.com/patterns/ab-001330
