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).