Skip to content
45 changes: 45 additions & 0 deletions src/content/changelog/browser-run/2026-09-14-guardrails.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: "Control which hostnames Browser Run sessions can access"
description: Browser Run guardrails restrict HTTP and HTTPS requests by hostname.
products:
- browser-run
date: 2026-09-14
---

import { TypeScriptExample } from "~/components";

[Browser Run](/browser-run/) now supports [guardrails](/browser-run/features/guardrails/), which limit a browser session's HTTP and HTTPS requests to permitted hostnames.

Use guardrails when you need to:

- Keep a browser workflow limited to a specific website and its subdomains.
- Load only known third-party APIs, scripts, images, and fonts.
- Generate a screenshot or PDF from HTML you provide while preventing it from loading external content.

Set guardrails when starting a session with Puppeteer, Playwright, or the REST API. With a browser binding named `MYBROWSER`, pass `guardrails` when launching Puppeteer:

<TypeScriptExample>

```ts
import puppeteer from "@cloudflare/puppeteer";

interface Env {
MYBROWSER: Fetcher;
}

export async function startGuardedSession(env: Env) {
return puppeteer.launch(env.MYBROWSER, {
guardrails: {
allowedDomains: ["example.com", "*.example.com"],
},
});
}
```

</TypeScriptExample>

In addition to session guardrails, Browser Run now supports a read-only mode for [Live View](/browser-run/features/live-view/). Live View lets you watch and interact with an active Browser Run session in real time. A read-only link lets someone watch without clicking, typing, navigating, or running JavaScript.

To create a read-only link, set `{ mode: "readonly" }` when generating the Live View URL. This setting affects only the person using that link. The session's hostname restrictions remain unchanged.

Refer to the [guardrails documentation](/browser-run/features/guardrails/) for more information.
240 changes: 240 additions & 0 deletions src/content/docs/browser-run/features/guardrails.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
---
pcx_content_type: how-to
title: Guardrails
description: Restrict HTTP and HTTPS requests by destination hostname.
sidebar:
order: 7
badge: Beta
products:
- browser-run
---

import { CURL, TypeScriptExample } from "~/components";

Guardrails limit a Browser Run session's HTTP and HTTPS requests to permitted hostnames.

This allows you to:

- **Keep automation focused** — Limit each session to hostnames needed for its task.
- **Support page dependencies** — Include required third-party APIs, scripts, images, and fonts.
- **Run self-contained pages** — Prevent external HTTP and HTTPS requests.

Session guardrails apply to browser sessions created with [Puppeteer](/browser-run/puppeteer/), [Playwright](/browser-run/playwright/), or [Chrome DevTools Protocol (CDP)](/browser-run/cdp/). They are unavailable for [Quick Actions](/browser-run/quick-actions/).

## Set up guardrails

Add the hostname the browser will visit and any hostnames required for redirects, APIs, scripts, images, or fonts. For common or shared hostnames, use a domain set instead of listing each hostname individually.

Set guardrails when you start a new session. The policy remains fixed for the lifetime of that session.

Choose a property based on how you maintain the allowlist:

| Property | Type | Use when | Limit |
| ------------------- | ---------- | ------------------------------------------------------------------------ | ------------ |
| `allowedDomains` | `string[]` | Your workflow needs a short, stable list with exact hostname control. | 50 entries |
| `allowedDomainSets` | `string[]` | Many sessions share a longer list, or your team maintains one centrally. | Four entries |

Both properties are optional and form one allowlist. If you omit both, HTTP and HTTPS requests remain unrestricted.

### Check policy requirements

Before starting a session, make sure your guardrail policy meets these requirements:

- Add no more than 50 entries to `allowedDomains`.
- Add no more than four entries to `allowedDomainSets`.
- Write hostname patterns without a scheme, port, or path.
- Use no more than one wildcard in each hostname pattern.

If a policy does not meet these requirements, Browser Run rejects the session request with a `400` response.

### Start a guarded session with Puppeteer

This function starts a session that permits `example.com`, its subdomains, and hostnames from the `common-cdns` domain set.

The example assumes a browser binding named `MYBROWSER`.

<TypeScriptExample filename="src/index.ts">

```ts
import puppeteer from "@cloudflare/puppeteer";

interface Env {
MYBROWSER: Fetcher;
}

export async function startGuardedSession(env: Env) {
const browser = await puppeteer.launch(env.MYBROWSER, {
guardrails: {
allowedDomains: ["example.com", "*.example.com"],
allowedDomainSets: ["common-cdns"],
},
});

return browser;
}
```

</TypeScriptExample>

[Playwright](/browser-run/playwright/) accepts the same `guardrails` object through its `launch()` options.

### REST API

Use the REST API to acquire a guarded session outside Workers. This request assumes `$ACCOUNT_ID` is set and `$CLOUDFLARE_API_TOKEN` has Browser Rendering Write permission.

<CURL
url="https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/browser-rendering/devtools/browser"
method="POST"
headers={{
Authorization: "Bearer $CLOUDFLARE_API_TOKEN",
}}
json={{
guardrails: {
allowedDomains: ["example.com", "*.example.com"],
allowedDomainSets: ["common-cdns"],
},
}}
/>

:::caution[Compatibility]
Use `@cloudflare/puppeteer` 1.4.0 or later. Use `@cloudflare/playwright` 1.3.6 or later.

Guardrails are not supported with [Kitesurf](/browser-run/kitesurf/).
:::

## Add allowed hostnames

Use `allowedDomains` to specify which hostnames the browser can request.

Each entry must contain only a hostname. Do not include a protocol such as `https://`, a port such as `:443`, or a path such as `/api`.

- `example.com` allows only `example.com`.
- `*.example.com` allows subdomains such as `www.example.com` and `api.example.com`, but not `example.com`.

You can use one `*` wildcard in each entry to match variations of a hostname:

| Pattern | Matches | Does not match |
| ------------------- | --------------------------------------------------- | ------------------------------------- |
| `example.com` | `example.com` | `www.example.com`, `evil-example.com` |
| `*.example.com` | `www.example.com`, `api.v1.example.com` | `example.com`, `evilexample.com` |
| `*example.com` | `example.com`, `www.example.com`, `evilexample.com` | `example.net` |
| `api.*.example.com` | `api.v1.example.com`, `api.staging.example.com` | `api.example.com` |

:::caution[Prefer subdomain wildcards]
A prefix wildcard such as `*example.com` also matches lookalike hostnames that an attacker can register, such as `evilexample.com`. Use `*.example.com` instead, and list the apex domain separately if you need it.
:::

## Use a domain set

Domain sets help you reuse shared hostname lists across sessions. The `allowedDomainSets` property accepts the `common-cdns` set name and HTTPS URLs.

### Allow common CDN hostnames

Use the Cloudflare-maintained `common-cdns` set when your page depends on assets served by common content delivery network (CDN) hostnames:

```json
{
"allowedDomains": ["example.com"],
"allowedDomainSets": ["common-cdns"]
}
```

Cloudflare maintains the `common-cdns` set and may change it over time. Use `allowedDomains` or a hosted hostname list when you need a fixed set of permitted hostnames.

### Use a hosted hostname list

Use an HTTPS URL for hostname patterns specific to your pages and dependencies:

```json
{
"allowedDomainSets": ["https://example.com/browser-run-hostnames.txt"]
}
```

For example, `browser-run-hostnames.txt` could contain:

```txt
example.com
*.example.com

# Third-party API
api.example.net
```

The hosted list must meet these requirements:

| Requirement | Value |
| ------------ | --------------------------------------------------- |
| Protocol | HTTPS |
| Content type | `text/plain` |
| Line format | One hostname pattern per line |
| Comments | Lines starting with `#` and blank lines are ignored |
| Validation | One invalid line rejects the entire hosted list |

Cloudflare caches a hosted list for up to one hour. Updates after the cache refresh affect only newly started sessions, not existing sessions.

## Block all web requests

An empty `allowedDomains` array blocks all HTTP and HTTPS requests. Use it for self-contained pages, such as rendering inline HTML to a screenshot or PDF.

Inline content can render, but the browser cannot request external APIs or assets. Do not include any domain sets with this policy.

Use this policy object as the `guardrails` value across supported integrations:

```json
{
"allowedDomains": []
}
```

## Verify blocked requests

Use Puppeteer to request a hostname outside the allowlist. This example checks the response status and guardrail headers.

<TypeScriptExample>

```ts
import puppeteer from "@cloudflare/puppeteer";

interface Env {
MYBROWSER: Fetcher;
}

export async function verifyGuardrails(env: Env) {
const browser = await puppeteer.launch(env.MYBROWSER, {
guardrails: {
allowedDomains: ["example.com"],
},
});

try {
const page = await browser.newPage();
const response = await page.goto("https://example.org");
const status = response?.status();
const headers = response?.headers() ?? {};

if (
status !== 403 ||
headers["cf-mitigated"] !== "guardrails" ||
headers["cf-brapi-guardrails-reason"] !== "not-in-allowlist"
) {
throw new Error("Expected Browser Run guardrails to block the request");
}
} finally {
await browser.close();
}
}
```

</TypeScriptExample>

A blocked request returns a `403` response with these headers:

| Header | Value | Meaning |
| ---------------------------- | ------------------ | --------------------------------------- |
| `cf-mitigated` | `guardrails` | Confirms guardrails blocked the request |
| `cf-brapi-guardrails-reason` | `not-in-allowlist` | Requested hostname was not permitted |

## Use guardrails with Live View

Session guardrails remain active when you use [Live View](/browser-run/features/live-view/). The `{ mode: "readonly" }` Live View setting controls viewer interaction and does not change the session hostname allowlist.
Loading
Loading