diff --git a/src/content/changelog/browser-run/2026-09-14-guardrails.mdx b/src/content/changelog/browser-run/2026-09-14-guardrails.mdx
new file mode 100644
index 00000000000..1d9a8dd6422
--- /dev/null
+++ b/src/content/changelog/browser-run/2026-09-14-guardrails.mdx
@@ -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:
+
+
+
+```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"],
+ },
+ });
+}
+```
+
+
+
+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.
diff --git a/src/content/docs/browser-run/features/guardrails.mdx b/src/content/docs/browser-run/features/guardrails.mdx
new file mode 100644
index 00000000000..4193263fa3e
--- /dev/null
+++ b/src/content/docs/browser-run/features/guardrails.mdx
@@ -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`.
+
+
+
+```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;
+}
+```
+
+
+
+[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.
+
+
+
+:::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.
+
+
+
+```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();
+ }
+}
+```
+
+
+
+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.
diff --git a/src/content/docs/browser-run/features/live-view.mdx b/src/content/docs/browser-run/features/live-view.mdx
index 4d8f0842ffe..cbaaef1ca6b 100644
--- a/src/content/docs/browser-run/features/live-view.mdx
+++ b/src/content/docs/browser-run/features/live-view.mdx
@@ -1,7 +1,7 @@
---
pcx_content_type: how-to
title: Live View
-description: View and interact with remote Browser Run sessions in real time using the hosted DevTools UI or native Chrome DevTools.
+description: Watch and control active Browser Run sessions from the dashboard, a generated Live View URL, or Chrome DevTools.
sidebar:
order: 1
badge: Beta
@@ -9,15 +9,17 @@ products:
- browser-run
---
-import { CURL, DashButton } from "~/components";
+import { CURL, DashButton, TypeScriptExample } from "~/components";
Live View lets you see and interact with a remote Browser Run session in real time. This is useful for debugging automation scripts, monitoring what a browser is doing, or manually stepping in when a task requires human intervention (see [Human in the Loop](/browser-run/features/human-in-the-loop/)).
-Live View is available for any [Browser Session](/browser-run/#integration-methods), including sessions created with [Puppeteer](/browser-run/puppeteer/), [Playwright](/browser-run/playwright/), or the [CDP](/browser-run/cdp/) endpoints.
+Live View is available for any [Browser Session](/browser-run/#integration-methods), including sessions created with [Puppeteer](/browser-run/puppeteer/), [Playwright](/browser-run/playwright/), or the [Chrome DevTools Protocol (CDP)](/browser-run/cdp/) endpoints.
-## How to access Live View
+A browser session is one remote Chrome instance. A session can contain multiple tabs. CDP calls each debuggable item a target, and a page target usually represents one browser tab. Live View connects to a page target.
-There are three ways to access Live View: through the Cloudflare dashboard, via the hosted user interface (UI) at `live.browser.run`, or using native Chrome DevTools.
+## Access Live View
+
+Open Live View from the Cloudflare dashboard or with a generated `devtoolsFrontendUrl`. A generated URL opens Cloudflare's hosted interface in any browser. Chrome users can instead open the connection in Chrome DevTools.
### Cloudflare dashboard
@@ -29,35 +31,37 @@ In the Cloudflare dashboard, go to the **Browser Run** page and select the **Liv
Sessions created from the dashboard default to a five-minute inactivity timeout (`keep_alive`), compared to the one-minute default when creating sessions through the API. You can adjust the timeout up to 10 minutes — in the dashboard, use the timeout field when creating a session, or in the API and Workers Bindings, use the [`keep_alive` option](/browser-run/puppeteer/#keep-alive).
:::
-### Hosted UI (any browser)
+### Generated URL (any browser)
-When you create a session or list targets through the [CDP](/browser-run/cdp/) endpoints, the API response includes a `devtoolsFrontendUrl` for each target (tab). Open this URL in any browser to load the DevTools UI hosted at `live.browser.run`, which streams the remote session to your browser.
+When you create a session with `targets=true` or list a session's targets, the API response includes a `devtoolsFrontendUrl` for each page target. Open this URL in any browser to watch or control that tab through Cloudflare's hosted interface. The generated URL uses `live.browser.run`; you do not visit that hostname directly.
-The hosted UI supports two viewing modes, controlled by the `mode` parameter in the URL:
+The hosted UI supports three viewing modes, controlled by the `mode` parameter in the URL:
-| Mode | URL pattern | Description |
-| --------- | -------------------------------------------------------- | ----------------------------------------------------------- |
-| Tab | `https://live.browser.run/ui/view?mode=tab&wss=...` | Standalone page view |
-| Full | `https://live.browser.run/ui/view?mode=full&wss=...` | Multi-tab page view |
-| Inspector | `https://live.browser.run/ui/view?mode=devtools&wss=...` | DevTools inspector panel (Elements, Console, Network, etc.) |
+| Mode | URL pattern | Description |
+| ---------- | -------------------------------------------------------- | ---------------------------------------------------------------------- |
+| `tab` | `https://live.browser.run/ui/view?mode=tab&wss=...` | Shows and controls one selected page without DevTools panels |
+| `full` | `https://live.browser.run/ui/view?mode=full&wss=...` | Shows the browser interface and its open page tabs |
+| `devtools` | `https://live.browser.run/ui/view?mode=devtools&wss=...` | Opens DevTools panels for one page, including Elements and the Console |
### Native Chrome DevTools (Chrome only)
-Because Browser Run speaks standard CDP, you can connect Chrome's built-in DevTools directly to a remote session. Replace the `https://live.browser.run/ui/inspector?wss=` prefix in the `devtoolsFrontendUrl` with the `devtools://` protocol:
+Browser Run supports CDP, the protocol that powers Chrome DevTools. If a generated `devtoolsFrontendUrl` starts with `https://live.browser.run/ui/inspector?wss=`, replace that prefix with `devtools://devtools/bundled/inspector.html?wss=`:
```txt
devtools://devtools/bundled/inspector.html?wss=live.browser.run/api/devtools/browser/SESSION_ID/page/TARGET_ID?jwt=...
```
-Paste this URL into Chrome's address bar to connect native DevTools to the remote browser session. You will get the same DevTools interface you use for local debugging. The `devtools://` protocol is Chrome-only and limited to inspector viewing mode.
+Paste the updated URL into Chrome's address bar. Chrome opens its built-in DevTools interface for the remote tab. The `devtools://` protocol works only in Chrome and supports only the `devtools` viewing mode.
:::caution[URL validity]
-The `devtoolsFrontendUrl` is valid for five minutes by default. You can change this by setting the `expiresInMs` parameter when sending the `Cloudflare.getLiveView` CDP command (max 1 hour). If you do not open the URL within this timeframe, list the targets again or send a new `Cloudflare.getLiveView` command to get a fresh URL. Once the DevTools connection is established, it remains active as long as the browser session is alive.
+The `devtoolsFrontendUrl` is valid for five minutes by default. This is the deadline for starting a connection, not the duration of an established connection or browser session. Set `expiresInMs` when generating a custom URL to change the deadline, up to one hour. If the URL expires before you connect, generate a new one. An established connection remains active while the browser session is alive.
:::
+The API examples in the following sections assume `$ACCOUNT_ID` is set and `$CLOUDFLARE_API_TOKEN` has Browser Rendering Write permission.
+
## View a new session
-1. Create a browser session with `targets=true` to include target URLs in the response:
+1. Create a browser session with `targets=true` to include its current page targets and generated Live View URLs in the response:
t.type === "page" && t.url.includes("example.com")) ||
- targetInfos[0];
+| Parameter | Type | Default | Description |
+| ------------- | -------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------- |
+| `mode` | `string` | `devtools` | Viewing mode: `devtools`, `tab`, or `full` |
+| `expiresInMs` | `number` | `300,000` (five minutes) | Deadline for starting a connection, in milliseconds. Minimum `60,000` and maximum `3,600,000` |
+| `targetId` | `string` | Current CDP target or first page target | Page target to view |
+| `guardrails` | `object` | — | REST API only. Viewer restrictions for this connection. Set `{ "mode": "readonly" }` to block interaction |
-if (!target) {
- throw new Error("Target not found");
-}
+The REST API response includes `id`, `options`, `devtoolsFrontendUrl`, and `webSocketDebuggerUrl`. Open the frontend URL in a browser or use the WebSocket URL with a CDP client. The `Cloudflare.getLiveView` CDP command returns `devtoolsFrontendUrl`.
+
+:::caution[Treat Live View URLs as credentials]
+A Live View URL contains a signed `jwt` value. Anyone with the complete URL can access the permitted connection. Share it only through trusted channels. A read-only link still exposes visible page data.
-const targetId = target.targetId;
-console.log(`Selected target: ${targetId}`);
+Generate Live View URLs from trusted server-side code. Never expose `$CLOUDFLARE_API_TOKEN` in browser code.
+:::
+
+A read-only Live View guardrail applies only to the generated connection. Other connections and automation scripts can still control the session. Session [guardrails](/browser-run/features/guardrails/) restrict HTTP and HTTPS destinations for the entire session and remain active for every Live View connection.
+
+### REST API
-// Get the Live View URL for the selected target
-const { devtoolsFrontendUrl } = await cdp.send("Cloudflare.getLiveView", {
- targetId, // use the target ID from above, this is optional, if omitted the current target will be used
- mode: "tab", // Options: "devtools", "tab", or "full"
- expiresInMs: 300000, // optional, default is 5 minutes (max 1 hour)
-});
+Use the [Live View endpoint](/api/resources/browser_rendering/subresources/devtools/subresources/browser/subresources/live_view/methods/create/) to generate a URL:
-console.log(`Live View URL: ${devtoolsFrontendUrl}`);
+
+
+#### Share a view-only link
+
+To block viewer interaction, set `guardrails` to `{ "mode": "readonly" }`:
+
+
+
+The link streams the session but blocks navigation, input, and JavaScript evaluation. The tab title starts with `READ ONLY - `.
+
+:::caution
+A view-only link is for humans, not automation. Puppeteer and Playwright cannot drive a read-only connection because the required commands are blocked.
+:::
+
+### CDP
+
+CDP is Chrome's remote debugging protocol. `Cloudflare.getLiveView` is a Cloudflare extension that generates a Live View URL over an existing CDP connection. This Puppeteer example generates a URL for the current page:
+
+
+
+```ts
+import type { Page } from "@cloudflare/puppeteer";
+
+export async function getLiveViewUrl(page: Page): Promise {
+ const cdp = await page.createCDPSession();
+
+ const { devtoolsFrontendUrl } = await cdp.send("Cloudflare.getLiveView", {
+ mode: "tab",
+ expiresInMs: 300000,
+ });
+
+ return devtoolsFrontendUrl;
+}
```
-Refer to the [Human in the Loop reference](/browser-run/features/human-in-the-loop/#cloudflaregetliveview) for full parameter and return details.
+
+
+To use Live View for a human operator handoff, refer to the [Human in the Loop workflow](/browser-run/features/human-in-the-loop/#cloudflaregetliveview).