# Missing frame-ancestors enables framing

Pages or extensions lacking frame-ancestors or X-Frame-Options, especially on predictable subdomains

_Area: Web infrastructure and server-side requests. Seen in 2 disclosed reports; 2 confirmed bounties: $1,337 and $3,134; latest 2024-09-19. Updated 2026-10-23._

## How it works
A page or extension assumes only its own UI will embed it, so it trusts messages or data it receives. That assumption breaks when the page has no `frame-ancestors` (or `X-Frame-Options`) restriction: any origin can put it in an iframe, drive it to load a chosen resource, and read what it posts back. The missing header is the root cause; the trust in `postMessage` or in a local IPC channel is what turns it into data loss or code execution.

## Where it shows up
- Extension `extension_pages` CSP without `frame-ancestors 'self'` [E2](https://bugbountylibrary.com/r/blog%3Andevtk-github-io-writeups-2024-09-19-drive.md).
- Sandbox or output iframes with no CSP `frame-ancestors` [E3](https://bugbountylibrary.com/r/blog%3Andevtk-github-io-writeups-2023-06-23-outputframes.md).
- `postMessage` handlers using `targetOrigin: '*'` or not checking `event.origin` [E2](https://bugbountylibrary.com/r/blog%3Andevtk-github-io-writeups-2024-09-19-drive.md).
- Local websocket/IPC servers with no Origin validation [E1](https://bugbountylibrary.com/r/h1%3A873614.md).
- Electron `BrowserWindow` with `nodeIntegration: true` accepting load/URL commands [E1](https://bugbountylibrary.com/r/h1%3A873614.md).
- Per-user subdomains derived predictably from a user ID [E3](https://bugbountylibrary.com/r/blog%3Andevtk-github-io-writeups-2023-06-23-outputframes.md).

## How to review
The invariant: any page that receives or acts on cross-origin messages must be non-framable by untrusted origins, and any local server must reject requests whose Origin is not its own. Check the CSP for `frame-ancestors` on every extension page and sandbox; check `postMessage` senders pin a specific `targetOrigin` and receivers validate `event.origin`; check local websocket handlers validate Origin before acting; check Electron windows for `nodeIntegration` and command handlers that accept arbitrary URLs [E1](https://bugbountylibrary.com/r/h1%3A873614.md). For per-user subdomains, ask whether the subdomain is guessable or leakable [E3](https://bugbountylibrary.com/r/blog%3Andevtk-github-io-writeups-2023-06-23-outputframes.md).

## Variants seen
- Desktop app local websocket with no Origin check plus `nodeIntegration`, giving RCE from any website [E1](https://bugbountylibrary.com/r/h1%3A873614.md).
- Chrome extension page framable, leaking private document contents via `postMessage` [E2](https://bugbountylibrary.com/r/blog%3Andevtk-github-io-writeups-2024-09-19-drive.md).
- Colab output sandbox framable on a predictable per-user subdomain, read via opener [E3](https://bugbountylibrary.com/r/blog%3Andevtk-github-io-writeups-2023-06-23-outputframes.md).

## Impact and severity
Led to remote code execution on the host [E1](https://bugbountylibrary.com/r/h1%3A873614.md) and disclosure of private documents [E2](https://bugbountylibrary.com/r/blog%3Andevtk-github-io-writeups-2024-09-19-drive.md)[E3](https://bugbountylibrary.com/r/blog%3Andevtk-github-io-writeups-2023-06-23-outputframes.md). Severity rises when the framed page handles sensitive data, when the subdomain is predictable, and when the local server runs with elevated privileges.

## Why fixes fail
Adding `frame-ancestors` alone is insufficient if `postMessage` still uses `'*'` or skips origin checks [E2](https://bugbountylibrary.com/r/blog%3Andevtk-github-io-writeups-2024-09-19-drive.md); both the framing restriction and the message validation must hold.

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A873614.md
- E2: https://bugbountylibrary.com/r/blog%3Andevtk-github-io-writeups-2024-09-19-drive.md
- E3: https://bugbountylibrary.com/r/blog%3Andevtk-github-io-writeups-2023-06-23-outputframes.md
