# How Cypress Handles the Application Under Test (AUT) Iframe and Cross-Origin Communication

> Learn how Cypress manages the AUT iframe and cross-origin communication using its spec bridge and postMessage for secure, isolated testing.

- Repository: [Cypress.io/cypress](https://github.com/cypress-io/cypress)
- Tags: internals
- Published: 2026-07-12

---

**Cypress isolates the tested application inside a dedicated AUT iframe that communicates bidirectionally with the test runner via a spec bridge and `postMessage`, using the `X-Cypress-Is-AUT-Frame` header to manage cross-origin security boundaries.**

The cypress-io/cypress repository implements a sophisticated iframe-based architecture to sandbox the Application Under Test (AUT) while maintaining robust control over cross-origin navigation and DOM manipulation. This design allows Cypress to test applications running on different domains without violating browser security policies, leveraging specific message protocols and custom HTTP headers to coordinate between the runner and the isolated iframe.

## AUT Iframe Architecture and Isolation

Cypress creates a strict boundary between its own UI and the application being tested by mounting the AUT inside a separate iframe element. This isolation prevents style collisions and JavaScript conflicts while enabling the driver to inject necessary automation scripts.

### Creating the AUT Iframe

When a test begins, the `AutIframe` class defined in **[`packages/app/src/runner/aut-iframe.ts`](https://github.com/cypress-io/cypress/blob/main/packages/app/src/runner/aut-iframe.ts)** instantiates an iframe with the ID `aut-iframe` (or `aut-iframes-container` for multi-frame scenarios). The runner serves the iframe content from the static HTML template located at **[`packages/server/lib/html/iframe.html`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/html/iframe.html)**. The **[`packages/driver/src/cypress.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cypress.ts)** file coordinates this instantiation, ensuring the iframe is appended to the runner's DOM only after the Cypress server is ready to proxy requests.

The `AutIframe` class handles mounting, remounting, and tearing down the iframe between tests, maintaining a clean state for each spec without reloading the entire runner window.

### The Spec Bridge Injection

To establish communication between the runner and the AUT, Cypress injects a script known as the **Spec Bridge** into the iframe. This bridge, defined in **[`packages/server/lib/html/spec-bridge-iframe.html`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/html/spec-bridge-iframe.html)**, exposes a `Cypress` object on the AUT's `window` and establishes a connection to `window.parent` via `postMessage`. The bridge acts as a proxy, forwarding commands from the test code (`cy.*` methods) into the isolated iframe and returning results back to the runner.

## Cross-Origin Communication Protocol

Because the AUT frequently runs on a different origin than the Cypress UI (e.g., `localhost:3000` vs. `localhost:8080`), the framework must navigate Same-Origin Policy restrictions while maintaining control over navigation and network stubbing.

### The X-Cypress-Is-AUT-Frame Header

Cypress identifies requests originating from the AUT iframe by injecting a custom HTTP request header: **`X-Cypress-Is-AUT-Frame`**. According to the source code in **[`packages/server/test/unit/browsers/chrome_spec.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/test/unit/browsers/chrome_spec.ts)**, this header is added to every request initiated by resources loaded inside the AUT iframe. Before proxying the request to the actual target server, Cypress middleware strips this internal header to prevent leakage to external systems while using it internally to route responses correctly through the automation proxy.

### Message Passing via postMessage

Once both the runner and the AUT iframe are loaded, they exchange JSON messages via `window.postMessage`. The server-side controller in **[`packages/server/lib/controllers/iframes.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/controllers/iframes.ts)** implements handlers for specific protocol messages including:

- **`get:aut:url`** – Retrieves the current URL of the AUT iframe
- **`reload:aut:frame`** – Triggers a reload of the AUT iframe
- **`navigate:aut:history`** – Manipulates the history API within the AUT
- **`get:aut:title`** – Fetches the document title from the AUT

These messages allow the driver to control navigation and state without direct access to cross-origin `contentDocument` properties.

## Security Boundaries and DOM Access

Cypress maintains security boundaries by restricting DOM manipulation to the iframe's content document only when same-origin policies permit, or by using the spec bridge for cross-origin interactions. Test commands resolve against the AUT iframe's document rather than the runner's DOM.

```typescript
// Example: Accessing the AUT iframe from test code
cy.get('iframe.aut-iframe')
  .its('0.contentDocument.body')
  .then(cy.wrap)               // Wrap the AUT's body for Cypress commands
  .within(() => {
    cy.get('#login').type('admin')
    cy.get('button.submit').click()
  })

```

```typescript
// Internal message passing to the AUT (simplified from controllers/iframes.ts)
window.top.postMessage({ 
  event: 'get:aut:url', 
  data: {} 
}, '*')

```

```typescript
// Server-side header handling (conceptual implementation)
if (req.headers['x-cypress-is-aut-frame']) {
  // Strip the internal header before proxying
  delete req.headers['x-cypress-is-aut-frame']
}
proxy.web(req, res, { target: targetUrl })

```

## Summary

- **Isolation**: Cypress mounts the AUT in a dedicated iframe (`aut-iframe`) managed by the `AutIframe` class in **[`packages/app/src/runner/aut-iframe.ts`](https://github.com/cypress-io/cypress/blob/main/packages/app/src/runner/aut-iframe.ts)**, using templates from **[`packages/server/lib/html/iframe.html`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/html/iframe.html)**.
- **Bridge**: The Spec Bridge injected via **[`packages/server/lib/html/spec-bridge-iframe.html`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/html/spec-bridge-iframe.html)** exposes the Cypress API inside the AUT and handles `postMessage` communication.
- **Cross-Origin**: The `X-Cypress-Is-AUT-Frame` header identifies AUT requests for special handling, as tested in **[`packages/server/test/unit/browsers/chrome_spec.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/test/unit/browsers/chrome_spec.ts)**.
- **Protocol**: Messages like `get:aut:url` and `reload:aut:frame` are processed by **[`packages/server/lib/controllers/iframes.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/controllers/iframes.ts)** to control navigation without violating same-origin policies.
- **Security**: Direct DOM access is scoped to the iframe's `contentDocument`, with cross-origin interactions mediated through the message bridge.

## Frequently Asked Questions

### How does Cypress prevent the AUT iframe from accessing the runner's DOM?

Cypress runs the AUT in a sandboxed iframe with a different origin or strict isolation context. The spec bridge only exposes specific `postMessage` handlers rather than the full parent window, preventing the AUT from traversing up to `window.parent` and accessing Cypress internals. Additionally, the runner's UI runs in a separate JavaScript context from the AUT iframe's content.

### What is the purpose of the X-Cypress-Is-AUT-Frame header?

This header allows the Cypress proxy server to distinguish requests made by resources inside the AUT iframe from requests made by the runner itself. According to the cypress-io/cypress source code, the header is added by the browser automation layer and then stripped by the server middleware before forwarding to the target application, ensuring the header never reaches external servers while enabling internal routing logic.

### How does Cypress handle cross-origin navigation in the AUT iframe?

When the AUT navigates to a different origin, Cypress intercepts the navigation via the `navigate:aut:history` message protocol implemented in **[`packages/server/lib/controllers/iframes.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/controllers/iframes.ts)**. The driver communicates with the AUT through the spec bridge using `postMessage` rather than direct `contentWindow` access, bypassing same-origin restrictions that would otherwise block cross-domain iframe manipulation.

### Can I access elements inside the AUT iframe directly from my test code?

Yes, but only through Cypress commands that internally resolve to the iframe's content. You should use `cy.get()` to target the iframe, then use `.its('0.contentDocument.body')` and `cy.wrap()` to scope subsequent commands within the AUT's DOM, as shown in the code examples above. This pattern ensures Cypress's automatic waiting and retry logic remains active while respecting the iframe boundary.