Home / Articles

Cloud Browser: A Secure Remote Browser Platform for Any AI Agent

A reference design for a model-neutral remote browser service: gateway, workers, WebRTC live view, approvals, SSRF guards, replay, and every config setting.

Publisher: REACTAPP.TOP32 min read7288 words

This article is published in English.

Sources: Building a Secure, Agent-Neutral Cloud Browser

AI agents have gotten decent at reasoning about web pages, yet driving a real browser remains harder than it looks. Clicking buttons is the easy part. A browser agent that people can depend on has to keep sessions alive, cope with popups and redirects, make sense of dynamic interfaces, deal with downloads and uploads, let a human grab the wheel, shield credentials, ask permission before consequential steps, and afterward show what actually happened.

Cloud Browser is a design for that whole problem: a standalone browser execution service that doesn't care which model sits on the other end.

A desktop agent, a command-line coding assistant, an MCP client or a custom application can all use it. The service contains no LLM at all. The agent decides what should happen, while Cloud Browser owns the browser, the safety boundary, execution, the live view, the evidence trail and cleanup.

That split is the most consequential architectural decision in the system. Swap the agent and the browser platform stays put. Upgrade a browser worker and the agent needs no rewrite. Security policy behaves the same way no matter which client calls in.

What follows covers the architecture, the trust boundaries, the interaction model, and the engineering choices that turn browser automation into something an enterprise could run.

The big picture

An AI agent operates a remote browser while a person can watch, step in, approve sensitive actions, look through screenshots and replay the task afterward. The top-level flow is deliberately simple:

User
  |
  v
AI agent or agent application
  |
  | HTTPS API or MCP
  v
Cloud Browser gateway
  |
  v
Isolated browser worker
  |
  v
Website

The person observes and steers the worker through a signed WebRTC live view.

Why Playwright isn't enough

Playwright is an excellent automation engine, but it isn't a multi-user browser product. A production service has to answer plenty of questions Playwright never asks:

  • Who owns a given browser?
  • Which task owns a given tab?
  • Could a second agent reuse a tab identifier it wasn't given?
  • What happens when a click lands but the network response disappears?
  • How does someone sign in without a password ever reaching the model?
  • What stops the browser from reaching cloud metadata endpoints or private networks?
  • Who is in control when the agent and the person are both connected?
  • How do screenshots get stored and downloaded safely?
  • What evidence survives when an action fails?
  • What gets cleaned up if a turn is cut short?

Wrapping the engine in an explicit service boundary lets the platform answer all of these the same way every time.

Execution without planning

The service carries out requests and never plans. It would not decide on its own that a purchase should go through or that a message should be sent. That keeps it useful across different agents and draws a clean line between two sets of duties.

On the agent side:

  • grasp what the user wants
  • work out the next step
  • interpret what the browser currently shows
  • ask the user when the intent isn't clear

On the Cloud Browser side:

  • authenticate whoever is calling
  • isolate sessions and tabs from each other
  • enforce browser policy
  • validate every target
  • carry out actions
  • demand any configured approvals
  • stream the live browser
  • keep a bounded body of evidence
  • release resources when done

Targets and stale references

Preferred targets are semantic: a button, a text field, a checkbox, a menu item or an accessibility node seen in the current document. Canvas applications and heavily customized interfaces still call for coordinates, which are less stable. In that case Cloud Browser hit-tests the location and reports bounded details about whichever element actually sits under the pointer.

An agent can also state the role or visible text it expects. If the element at the target doesn't match, the action fails closed rather than clicking something unintended.

Pages change all the time. A modal can slide in between observing and acting, a navigation can swap out the whole document, and a popup can become the active continuation. To cope, semantic references are tied to a document revision. Any mutation or navigation invalidates earlier references, and trying an old one returns a stale_target error. The result is a safe loop:

Observe -> choose one action -> validate -> execute -> observe again

Where policy lives

Approval and network rules can't sit only inside one user interface, or another client could connect directly and skip them. The gateway is the authoritative enforcement point. Agent applications may layer stricter policies on top, but none can loosen the service's own.

System architecture

The reference implementation has three main packages plus some supporting services.

Agent clients
  |
  +-- Desktop agent UI
  +-- Claude or another MCP client
  +-- Codex or another coding agent
  +-- Custom application or SDK
  |
  v
MCP adapter or direct HTTPS client
  |
  v
Cloud Browser gateway
  |
  +-- authentication and delegated identity
  +-- tenant and task isolation
  +-- request validation
  +-- idempotency
  +-- policy and approvals
  +-- session manager
  +-- signed live-view service
  +-- audit and observability
  |
  v
Worker scheduler
  |
  v
Browser worker
  |
  +-- Playwright and restricted CDP
  +-- tabs and popups
  +-- semantic state
  +-- browser actions
  +-- screenshots and artifacts
  +-- downloads and uploads
  +-- WebRTC video source
  +-- secure form filling
  |
  v
Approved web destinations

The gateway

The gateway exposes a versioned HTTPS contract. It authenticates the caller, works out the effective user identity, checks task ownership, applies quotas, enforces policy and passes the command to the right worker. Commands travel in a correlated envelope holding a protocol version, a request ID, a task ID, an optional session ID, the command name and bounded parameters. Commands that change state also need an idempotency key.

Results and errors come back structured. Browser internals aren't allowed to leak out through random stack traces or raw provider failures.

The MCP adapter

Agents that support MCP connect through a deliberately slim adapter. Chromium never runs inside it and it holds no browser policy. Its only job is mapping tool calls onto gateway requests: opening a tab (browser_open_tab), reading state, clicking, taking a screenshot, or finalizing a session through the matching browser_* tools. Several agent products can then share one gateway, and nothing vendor-specific creeps into the worker.

Signed live-view URLs are withheld from the model by the adapter. What the agent gets is harmless presentation data: whether a live view exists, which tab it shows, who has control and when it expires. Only the trusted user interface ever receives the signed viewer capability.

The browser worker

Each worker holds the real browser process or context. Tabs, navigation, semantic extraction, input, screenshots, downloads, uploads, clipboard handling, dialogs and browser events all pass through it. Playwright does most of the automation, and a few read-only operations may switch on restricted CDP capabilities. Open-ended CDP access is never provided as a catch-all.

The restriction matters because raw CDP can reach cookies, storage, debugging targets, arbitrary evaluation and browser internals. A safe product exposes the smallest capability a task needs.

The session manager

The session manager owns the chain of authority:

tenant
  -> principal
    -> task
      -> session
        -> tab
          -> operation

Opaque identifiers are scoped to that chain. A tab identifier issued under one task can't be used by another, even when both happen to share one underlying browser process. The manager also handles leases, idle timeouts, control state, retained tabs, the profile lifecycle and finalization.

Browser profiles and persistence

Profiles come in three flavors. An ephemeral profile exists for a short-lived session and disappears when the task ends, which gives the strongest isolation by default. A task profile lasts for one task and stays separate from every other task. A persistent profile lets the same user keep cookies and signed-in website sessions from one task to the next.

Chrome allows only one active process per profile directory, so tasks owned by the same person may share a browser process, while each task keeps its own tabs, selection, live frames, clipboard state and control leases.

Persistent profiles demand tighter operational controls:

  • The profile directory is readable by its owner only.
  • The underlying volume or storage service should encrypt data at rest.
  • Users need a way to sign out and wipe the stored profile.
  • Access is scoped to the exact tenant and principal.
  • Reading browsing history requires explicit approval.
  • Workers never hand raw cookies or auth tokens back to an agent.

The life of one command

Every action goes through the same predictable sequence:

  1. The service caller is authenticated.
  2. The effective end-user identity is resolved.
  3. The protocol envelope is validated.
  4. The task binding is loaded or created.
  5. Session and tab ownership are confirmed.
  6. Network and action policy are evaluated.
  7. The current document revision and the target are validated.
  8. The idempotency key is reserved.
  9. The browser action runs.
  10. The outcome afterward is verified.
  11. Bounded evidence is recorded.
  12. A correlated operation receipt goes back to the caller.

When a transport failure occurs after the browser may already have acted, the gateway answers with outcome_unconfirmed. The client then has to reconcile the live browser state rather than blindly repeating the step. That matters most for booking a reservation, submitting a form or sending a message, where an automatic retry can produce duplicate real-world effects.

Live view over WebRTC

It's hard to trust a cloud browser that nobody can see, so Cloud Browser provides an interactive live view over WebRTC. Video flows as a media track, while pointer, keyboard, clipboard, status, approval and handoff events ride an ordered DataChannel. HTTP appears only for bounded offer-and-answer signaling, and there's no secondary WebSocket image stream.

Sending screenshots through an agent protocol over and over would be slower, so the WebRTC route cuts latency and keeps rapid-fire interaction away from ordinary tool replies. Pointer coordinates are mapped through the true bounds of the video, letterbox bars included. Wheel events get normalized and coalesced, keyboard shortcuts follow the viewer platform, and acknowledgements tell queued, delivered, changed, rejected and still-pending inputs apart.

Handing control back and forth

The viewer starts in observe mode while the agent holds control. A person asks for control by clicking the browser surface or pressing the control button. The state machine is explicit:

Agent control
  -> handoff pending
    -> user control
      -> release pending
        -> agent control

Any state
  -> finalized

While the user owns control, agent input is blocked. Control is scoped to the task and follows the selected popup if a site opens a new window. A stale video frame can't grant control, and a delayed message can't quietly give it back to the agent, because the gateway stays the authority on current state.

Protecting the viewer itself

The live-view link is a short-lived HMAC-signed capability tied to a single tenant, principal, task, session and tab, and it dies when the task ends. Framing is off by default. To embed the viewer in a trusted agent application, a deployment lists each exact parent origin in its Content Security Policy; wildcards, paths, credentials, queries and fragments are refused as framing origins.

All of this guards against a hostile website embedding the viewer, laying an invisible layer on top and tricking the user into clicking controls like "Take control" or "Approve."

Signing in safely

A password must never go back to the model, land in an action timeline or end up in an audit log. Two sign-in patterns keep that promise.

One pattern is manual control: the agent passes the browser to the user, who logs in directly on the site, and then control comes back to the agent.

The other pattern is a dedicated secure-auth challenge. Here the worker locates the specific visible fields in the current document, and the trusted viewer shows a masked credential form naming the exact destination origin. What the user types goes from the viewer straight to the form-fill service and into the validated browser fields. A challenge is pinned to the origin, task, session and tab, document generation, frame path, selector, expected field type and a brief expiry window. Only non-secret status details are visible to the model.

QR-based sign-in can be handled safely too. The worker captures the visible QR image, proves it can be decoded, throws away the decoded payload and shows the image only in the secure live viewer. The bearer value never reaches the agent.

Approvals before consequential actions

Being allowed onto a website and being allowed to take a consequential action there are separate decisions. Letting an agent open a restaurant's page doesn't authorize a reservation, and access to an online shop doesn't authorize a purchase. Cloud Browser judges risk when the action executes. Approval can be required for things like:

  • booking a table or an appointment
  • buying something or paying
  • editing accounts or permissions
  • deleting anything
  • sending messages for the user
  • storing credentials
  • uploading files
  • granting browser permissions
  • transmitting sensitive data

Each approval is pinned to one exact action, target, destination, document and set of data categories, so it is useless after the page changes or against another button. Sending sensitive data is judged on its own, apart from the final action. Entering an email address into a third-party form could need approval tied to that destination before the closing "Submit" click asks for a separate confirmation. The user remains the one who approves; Cloud Browser enforces, and the agent application frames the decision.

Screenshots, artifacts and replay

Screenshots serve quick understanding, lasting evidence and failure diagnosis. A screenshot call hands back an image to display right away plus an opaque artifact ID, and no server filesystem path ever reaches the agent. Artifacts belong to the owning task, expire quickly, have size and count limits, come through signed routes, can be downloaded only into configured local roots, and are written with owner-only permissions without clobbering an existing file.

Successful browsing can also build a bounded screenshot timeline. Each entry has a timestamp, hostname, origin, action summary and an optional screenshot artifact. A slider lets the user inspect an earlier browser state, and choosing LIVE returns to the current WebRTC session.

Sensitive steps get special treatment. Credential entry is never recorded, and when an action transmits sensitive information, the timeline can keep minimal metadata while deliberately withholding the screenshot.

Replay goes beyond a visual nicety, because it answers practical questions:

  • What was on screen for the agent just before the click?
  • Did a popup appear along the way?
  • Could another element have been sitting on top of the target?
  • Did anything on the page visibly change afterward?
  • Was the task stalled on the site, or on the user's approval?

Popups, redirects and awkward pages

Real workflows seldom stay in one tab. A booking flow can open a popup, an identity provider can bounce across several origins, an office document can try to launch a desktop application, and a meeting page can offer both an app link and a web fallback. Cloud Browser treats such transitions as first-class events.

If an action opens a window, the platform reports the new tab, selects it for the task and wires it into the live view; when the user has control, control moves along to it. Should a page attempt to launch a native protocol, a meeting app for example, the browser states that the external application is unavailable instead of faking a successful click. If the site has a safe browser fallback, the agent or the user may choose it on purpose.

Nothing in the platform works around CAPTCHAs, browser security interstitials, paywalls, device-trust demands or similar safeguards. When one appears, it is reported as a limitation or turned into a handoff to the user.

Network security and SSRF

Since a remote browser also acts as a network client, unchecked navigation could make it an SSRF tool. Destinations are resolved and validated before the worker connects. By default these are denied:

  • loopback targets
  • link-local targets
  • the cloud metadata service
  • RFC1918-style private ranges
  • multicast and otherwise reserved space
  • any URL scheme outside the supported set

Per-tenant allowlists and blocklists can tighten this further.

Certain corporate setups run split DNS or secure web gateways that point approved internal hostnames at private or carrier-grade NAT addresses. Such exceptions must be explicit, narrow, limited to trusted domains and set through deployment configuration, so a matching hostname never grows into a blanket path to the private network. Redirects, popups, iframes, service-worker requests and DNS rebinding stay under the same network policy, because vetting only the first URL falls short.

Prompt injection and untrusted page content

The browser worker treats page content as data and never as instructions. A page can display "ignore your previous instructions" or publish a tool description claiming some operation is safe, and neither statement grants any authority.

Tools that a page advertises through WebMCP are bound to the page URL, the document generation, a registration identifier and the schema that was observed, and their descriptions are never trusted. A readOnlyHint supplied by the page cannot skip approval unless the deployment has explicitly trusted that exact origin for the purpose. Semantic snapshots follow the same rule: reasoning about content is up to the agent, but granting browser authority is up to the gateway alone.

Downloads, uploads and the clipboard

Downloaded files are kept as task-owned artifacts, so no worker path is revealed. An upload needs a file chooser tied to an event and a source path beneath a configured upload root. Symbolic links are resolved and refused if they point outside the approved directory, and before files are set the worker checks whether the chooser takes one file or many.

Each task has its own clipboard state. Text and bounded binary MIME items can sync through the live viewer, with browser permissions granted narrowly and dropped afterward. In a persistent browser context, clipboard permission granted to one task must not remain open for another.

Lifecycle and cleanup

Cleanup has to run even if the agent never finishes cleanly. The service takes an explicit end-of-turn signal that distinguishes completed, interrupted, stopped and subagent-stopped turns, and the cleanup it kicks off is bounded and idempotent. Every tab can carry one of three marks:

  • a working tab, closed when the turn ends
  • a deliverable, kept for the user
  • a handoff, left open for the user to continue

Marks scoped to a turn are cleared once honored, so a tab must be deliberately retained again in a later turn. Further layers of cleanup come from session leases, idle timeouts, artifact expiry, orphan-profile sweeping and graceful gateway shutdown.

Observability without leaking private data

The gateway logs bounded operational events covering session creation, navigation, policy decisions, control handoffs, mutations, failures and cleanup. An audit record should hold identifiers and results rather than credentials or page contents. URLs are cut down to safe parts where possible, and queries, form values, passwords, tokens and personal data are masked or dropped.

Useful operational signals include:

  • latency at the gateway and in workers
  • how many sessions and browsers exist
  • WebRTC peers connected
  • which categories of policy denial occur
  • how often targets turn stale
  • outcomes left unconfirmed
  • workers that fail to launch
  • whether live-view connections succeed
  • artifacts created and expired
  • cleanups that fail

The viewer reports when video actually reaches the playing state, because merely generating a signed URL doesn't prove the user can see anything.

Reliability and stress testing

The nastiest browser-automation failures are quiet ones: a click that accomplished nothing, input delivered twice, a popup nobody saw, or an action replayed after a timeout. A serious test suite therefore reaches well beyond happy-path navigation. Cloud Browser exercises these areas:

  • many users and sessions at once
  • separation between tasks and between artifacts
  • mutations that must be idempotent
  • references to outdated documents
  • following popups
  • targets inside iframes and shadow roots
  • WebRTC reconnects
  • races while control changes hands
  • pages that are busy or briefly frozen
  • keystrokes arriving in order
  • fields for one-time codes
  • the secure sign-in flow
  • approvals around sensitive data
  • screenshots withheld from replay
  • attempts to break out of the upload root
  • SSRF and navigation guards
  • recovering sessions
  • cleanup once a turn is interrupted

Stress runs ought to pair real Chromium workers with many WebRTC viewers at once. Unit tests cover policy and protocol logic well, but races in browser startup, video streaming, focus, popup selection and input delivery only show up under realistic load.

Deployment model

The reference architecture can run as one node, though its interfaces are meant for a bigger production control plane. A basic deployment chains these pieces:

TLS ingress
  -> Cloud Browser gateway
    -> isolated browser workers
      -> encrypted profile and artifact volume

The browser should run as an unprivileged user under a suitable seccomp profile, with bounded CPU and memory and enough shared memory for Chromium. Containers are a practical start, and higher-risk environments might choose one microVM per browser worker for stronger isolation.

Enterprise deployments commonly add:

  • OIDC sign-in for the standalone console
  • machine-to-machine authentication for agents
  • end-user identity passed along by trusted backends
  • Redis, to hold distributed leases and idempotency keys
  • PostgreSQL or a dedicated audit platform for lasting events
  • encrypted object storage for artifacts
  • a scheduler that places workers
  • TURN servers issuing short-lived WebRTC credentials
  • a secrets manager with key rotation
  • regional routing plus data-residency controls
  • autoscaling driven by active sessions and WebRTC peer counts

The gateway should be reachable from outside only over HTTPS. Plain HTTP suits nothing beyond explicit loopback development.

Configuration, variable by variable

An env file sets the gateway's network identity, browser behavior, storage, isolation and policy boundaries. Treat it as deployment configuration and not as application source. The example below is safe to show, because its values are illustrative and contain no real token or signing secret:

CB_HOST=127.0.0.1
CB_PORT=8787
CB_PUBLIC_URL=http://127.0.0.1:8787

CB_TOKENS=replace-with-a-long-random-token:tenant-example:agent-service:delegate
CB_MAX_SESSIONS_PER_PRINCIPAL=8
CB_LIVEVIEW_SECRET=replace-with-a-separate-random-signing-secret
CB_LIVEVIEW_FRAME_ANCESTORS=http://localhost:8088,http://127.0.0.1:8088

CB_HEADLESS=true
CB_SERVICE_WORKERS=allow
CB_BROWSER_CHANNEL=chrome
CB_EXTENSION_PATHS=

CB_PROFILE_MODE=persistent
CB_PROFILE_DIR=./var/profiles
CB_ARTIFACT_DIR=./var/artifacts
CB_AUDIT_FILE=./var/audit.log

CB_USER_AGENT=

CB_ALLOW_ORIGINS=
CB_BLOCK_ORIGINS=
CB_TRUSTED_PRIVATE_ORIGINS=*.company.example
CB_ALLOW_CGNAT_NAVIGATION=true

CB_REQUIRE_APPROVAL=false
CB_ALLOW_LOOPBACK_NAVIGATION=true
CB_SYNTHETIC_MEDIA_DEVICES=true

CB_HOST

This is the network interface the gateway listens on.

CB_HOST=127.0.0.1

With 127.0.0.1 the service answers only to the local machine, the safest choice while developing with the agent UI, MCP adapter and gateway on one computer. A container may need 0.0.0.0 so its network or an ingress proxy can connect, but that is no reason to publish the port to the internet; production gateways stay behind authenticated TLS ingress, firewall rules and service-level authorization. Note too that CB_HOST governs where the process listens, not the URLs in live-view links, which CB_PUBLIC_URL handles.

CB_PORT

This is the TCP port the gateway uses.

CB_PORT=8787

Any number from 1 to 65535 works, and the development default is 8787. It must be unused on the chosen interface. If a reverse proxy or container exposes some other external port, CB_PORT stays the internal listening port while CB_PUBLIC_URL gives the address outsiders see.

CB_PUBLIC_URL

The public base URL is what the gateway uses when it builds signed live-view links.

CB_PUBLIC_URL=http://127.0.0.1:8787

It's more than a label, since the user interface receives viewer capabilities derived from it, and so it must be reachable from the browser that shows the live view. Local development supports loopback HTTP:

CB_PUBLIC_URL=http://127.0.0.1:8787

A remote deployment needs an externally reachable HTTPS origin:

CB_PUBLIC_URL=https://browser.example.com

Leave paths, queries, embedded usernames, passwords and raw secrets out of this URL, and terminate TLS at a trusted ingress or at the gateway itself. One more pitfall: 127.0.0.1 always means the machine you are on. Suppose the agent UI runs on a laptop and the gateway sits on a server somewhere. A viewer link built with 127.0.0.1 then leads back to the laptop and never reaches the gateway.

CB_TOKENS

This variable lists the service credentials the gateway accepts. Every entry follows one format:

<bearer-token>:<tenant-id>:<service-principal>[:scope]

A delegated backend credential looks like this:

CB_TOKENS=replace-with-a-long-random-token:tenant-example:agent-service:delegate

Commas separate multiple entries. The first field is the bearer token, which arrives in the HTTP Authorization header; make it at least 20 characters, generate it with a cryptographically secure random source and store it in a secret manager. The tenant ID is the outermost isolation boundary, so sessions, tasks, profiles, tabs and artifacts never cross it. The principal names the service holding the credential, and without delegation, sessions run as that principal.

The optional delegate scope lets a trusted backend send an X-Cloud-Browser-Principal header naming the real end user. The session then belongs to that user, while the audit trail separately records which backend acted on their behalf. Only trusted server-side applications should get delegation. A desktop client or ordinary user token shouldn't, since it could otherwise claim another user's identity within the tenant.

Both configured and presented tokens are hashed before a constant-time comparison, which reduces timing leakage from token length but doesn't make a leaked token safe. Rotate a token at once if it shows up in a screenshot, repository, terminal recording or support ticket. And don't reuse it as CB_LIVEVIEW_SECRET, because the two protect different capabilities and need independent generation.

CB_MAX_SESSIONS_PER_PRINCIPAL

This caps the concurrent sessions one effective user can hold.

CB_MAX_SESSIONS_PER_PRINCIPAL=8

Allowed values run from 1 to 128, defaulting to 8. The limit protects against accidental browser storms and runaway agents, and keeps one user from using up global capacity; it stacks with the gateway-wide session cap. A good figure depends on worker memory, CPU, profile mode and expected concurrency. Even if a persistent profile shares a browser process across a user's tasks, every task still consumes tab, streaming, artifact and gateway resources.

CB_LIVEVIEW_SECRET

This is the HMAC key that signs live-view capabilities.

CB_LIVEVIEW_SECRET=replace-with-a-separate-random-signing-secret

Every claim in the signed token (tenant, principal, task, session, tab, expiry) is covered by the signature, so changing one invalidates it. A deployment reachable from outside needs its own secret of 32 characters or more; in practice that means at least 32 random bytes in hex or Base64, loaded from a secret manager. A leaked secret would let an attacker with enough session metadata forge viewer capabilities. Rotation invalidates newly verified signatures made with the old key, so line it up with live-view session expiry. Keep the value away from agents, browser pages, client bundles, logs and repositories.

CB_LIVEVIEW_FRAME_ANCESTORS

This decides which trusted web applications may embed the signed viewer in an iframe.

CB_LIVEVIEW_FRAME_ANCESTORS=http://localhost:8088,http://127.0.0.1:8088

The gateway converts the list into the frame-ancestors directive of the viewer's Content Security Policy. If the value is empty, the viewer sends frame-ancestors 'none' and X-Frame-Options: DENY, so it works as a standalone page but refuses embedding. If origins are listed, only those exact ones may frame it, and X-Frame-Options: DENY is left out because that older header cannot express several origins and would defeat the allowlist.

Entries have to be exact HTTP or HTTPS origins; paths, URL credentials, queries, fragments and wildcards get rejected. Scheme, host and port all matter, so the following three are distinct origins:

http://localhost:8088
http://127.0.0.1:8088
https://agent.example.com

This setting isn't CORS. It protects the powerful viewer page from clickjacking, so list only the interfaces that genuinely need to show the live browser.

CB_HEADLESS

This controls whether the worker launches the browser without a native desktop window.

CB_HEADLESS=true

With true, Chromium or Chrome runs headless, and users watch and steer through the WebRTC live view instead of a local Chrome window. Servers and containers normally want this, since it uses fewer desktop resources and keeps automation independent of a native display session. With false the browser launches headed and needs a working display, usually a virtual one like Xvfb or a desktop-streaming worker image on Linux, while on macOS it opens a separate browser window unless the worker architecture differs. Changing the flag never makes Cloud Browser take over a personal Chrome window that's already open. It only affects how the worker's own managed browser starts.

CB_SERVICE_WORKERS

This decides whether websites may register and run service workers.

CB_SERVICE_WORKERS=allow

Choose allow or block; block is the safe default. Progressive Web Apps, meeting clients, office apps and heavily cached sites often depend on service workers and may break, stall on a loading screen or look frozen without them. Enabling them helps compatibility but enlarges the browser's networking and persistence surface, so verify beforehand that service-worker requests still respect the navigation guard, egress rules, data-loss prevention and tenant policy. Pick allow for a validated application that needs it, never as a catch-all fix.

CB_BROWSER_CHANNEL

This picks a system-installed browser channel in place of Playwright's bundled Chromium.

CB_BROWSER_CHANNEL=chrome

Four channels are accepted: chrome, chrome-beta, chrome-dev and msedge. Leave it empty to use the configured Chromium executable or the Playwright bundle. Real Chrome or Edge builds are sometimes necessary when a company checks device trust, manages browsers centrally, pushes policies or issues client certificates. Choosing chrome does not pull in the normal Chrome profile of the user, since profiles still come from CB_PROFILE_MODE and CB_PROFILE_DIR, so Cloud Browser cannot silently attach to a personal interactive session. The channel has to be installed on the worker host, and device trust may also need the right profile, OS enrollment, certificates, policies or extensions; the setting itself creates no enterprise trust.

CB_EXTENSION_PATHS

A comma-separated list of unpacked browser-extension directories.

CB_EXTENSION_PATHS=

Leave it blank to load no unpacked extensions. Listed paths are handed to the managed browser through extension-loading flags, which only works with CB_HEADLESS=false and a CB_PROFILE_MODE of task or persistent; ephemeral contexts and plain headless runs cannot host this safely. Unpacked loading is for development and controlled tests. Production enterprises should force-install approved extensions via Chrome or Edge policy, pin identifiers and update sources, and audit permissions. Because an extension runs with browser privileges and can observe or change web activity, its origin, update path, permissions and owner all belong in the security review.

CB_PROFILE_MODE

This decides how browser state is isolated and how long sign-in state survives.

CB_PROFILE_MODE=persistent

The setting takes ephemeral, task or persistent. In ephemeral mode a throwaway browser context is created without a reusable profile directory, the strongest default isolation, which fits anonymous browsing or work that should leave no sign-in traces. task mode creates a profile directory for one task: cookies and state last as long as the task, and cleanup deletes the profile. persistent mode shares one profile across tasks of the same tenant and effective principal, so cookies and website sessions outlive new tasks and gateway restarts, though each task keeps separate tab ownership, selected tab, clipboard, live streams, artifacts and control leases. Persistent mode is the handiest and the riskiest; use encrypted storage, tight directory permissions, explicit sign-out and profile-clear flows, limited idle time and carefully scoped identities.

CB_PROFILE_DIR

The root directory under which profile-backed browser data lives.

CB_PROFILE_DIR=./var/profiles

A relative path resolves against the gateway process's working directory. In containers, mount a protected volume here if profiles need to survive replacing the container. The gateway builds tenant- and principal-specific subdirectories itself and never takes profile paths chosen by agents. Directories should be owner-only and never offered as downloads. Do not aim the setting at a real person's Chrome profile; copying or concurrently opening a live profile can damage it and exposes unrelated browsing data to the worker.

CB_ARTIFACT_DIR

Where task-owned screenshots, downloads, exported content, replay images and other bounded artifacts are stored.

CB_ARTIFACT_DIR=./var/artifacts

Relative paths resolve from the gateway's working directory once more. Agents receive opaque artifact IDs instead of paths, and each fetch checks tenant, principal, task, expiry and ownership. Make the directory writable only for the Cloud Browser service account, cap artifact lifetime, count and size, sweep expired files, encrypt the storage beneath and avoid keeping temporary artifacts in backups indefinitely. Profiles and artifacts have different purposes: a screenshot may reach a user, while a browser profile must never be handled as a downloadable artifact.

CB_AUDIT_FILE

The path of the bounded JSON Lines audit log.

CB_AUDIT_FILE=./var/audit.log

Each line is a structured event for something like session creation, command execution, a policy denial, a control handoff, the result of a live-view connection, session destruction or a cleanup failure. Record who and what happened, not form values or page contents, and redact or omit credentials, tokens, queries and sensitive user data. In production, treat local JSONL as a development or transport format and ship events to a durable audit platform offering retention policy, access control, integrity protection, alerting and tenant-aware investigations.

CB_USER_AGENT

An optional override for the browser's User-Agent string.

CB_USER_AGENT=

Best left empty. The worker builds a normal Chrome identity from the browser build and supplies matching client hints, avoiding disagreement between the request header, navigator.userAgent and navigator.userAgentData. A custom string overrides only the User-Agent, with no matching hints created, and some identity providers and bot detectors spot the mismatch. Do not use it to present an old browser as a newer one or to get around a site's security policy; keep it for a documented compatibility requirement and check the complete browser identity afterward.

CB_ALLOW_ORIGINS

The tenant's navigation allowlist.

CB_ALLOW_ORIGINS=

If the list is empty, public HTTP and HTTPS destinations pass unless some other policy intervenes, and private-network, metadata, link-local, reserved and unsupported destinations still hit the network guard. Once it has entries, every navigation has to match at least one rule, written as an exact origin like https://portal.example.com, a wildcard suffix like *.example.com or a bare hostname like portal.example.com. Where scheme or port matters, use an exact origin. A wildcard covers the base hostname plus its subdomains, so go easy on them. Appearing on the allowlist does not authorize a private IP, as DNS classification and trusted-private-origin policy are evaluated on their own.

CB_BLOCK_ORIGINS

The explicit navigation denylist.

CB_BLOCK_ORIGINS=

Rules use the same three forms as the allowlist, and this list is consulted first, so anything matching both lists is blocked. Typical uses are sensitive admin endpoints, unsupported applications, known-dangerous destinations or a subdomain that must be cut out of a wider allowlist. Network classification applies even if the list is empty.

CB_TRUSTED_PRIVATE_ORIGINS

Names corporate hostnames that may resolve to private or CGNAT addresses.

CB_TRUSTED_PRIVATE_ORIGINS=*.company.example

The reason it exists is split-DNS setups, where an approved company hostname resolves differently on the VPN or internal network. Rules share the navigation-list syntax. Restrict it to domains the organization owns, and resist adding a generic wildcard or a hostname merely because one application misbehaves. A trusted match may allow RFC1918 or CGNAT targets but never loopback, link-local, cloud metadata, multicast or reserved addresses. Review it like the SSRF exception it is; the platform needs control over DNS, egress and ownership of each trusted suffix.

CB_ALLOW_CGNAT_NAVIGATION

Permits navigation to the carrier-grade NAT range, 100.64.0.0/10.

CB_ALLOW_CGNAT_NAVIGATION=true

It defaults to false. In some company networks, a VPN or secure web gateway presents public or internal services at addresses inside CGNAT space. Turning the switch on permits them and may disable the browser's Local Network Access restrictions that would block a page from reaching the synthetic address. This is wider than trusting one hostname, so use CB_TRUSTED_PRIVATE_ORIGINS if only known company domains need it. RFC1918, loopback, link-local, metadata, multicast and reserved ranges stay closed, but network reach still grows considerably, so enable it only in a controlled corporate environment.

CB_REQUIRE_APPROVAL

Turns on the gateway's general approval gate for commands classified as consequential.

CB_REQUIRE_APPROVAL=false

With true, consequential commands are held until an exact, action-time approval comes in. With false, the broad gate is lifted, a fair choice if a trusted agent application already enforces something stricter. Safety is not disabled, though. Recognized risks such as sensitive-data transmission, deletion, payments, reservations, messages on someone's behalf, account or permission changes, history access and unclassified page tools still require approval. Control is a different axis: agent actions remain blocked while the user holds the browser. For a standalone product shared by several agents, keeping gateway enforcement on is usually wiser, since safety then does not depend on each client implementing policy correctly.

CB_ALLOW_LOOPBACK_NAVIGATION

A development switch allowing the worker browser to visit loopback addresses (localhost, 127.0.0.1 or ::1).

CB_ALLOW_LOOPBACK_NAVIGATION=true

Defaults to false. Automated tests and local web apps on the same machine as the worker benefit from it. It does nothing to let a remote browser reach a user's laptop, because on a remote worker 127.0.0.1 is the remote environment. Avoid enabling it casually in production: loopback access can reveal internal admin ports, dev servers, debugging interfaces and services built on the assumption that untrusted web content could not reach them. Even when enabled, the hostname must resolve to loopback addresses only, so a mixed DNS answer cannot stretch the exception into a general bypass.

CB_SYNTHETIC_MEDIA_DEVICES

Gives the managed browser a synthetic microphone and camera.

CB_SYNTHETIC_MEDIA_DEVICES=true

Also false by default. Headless browsers frequently show no media devices, and some meeting and recording sites check for a microphone or camera before continuing, so the page looks stuck when none is found. Synthetic devices make those checks pass: the microphone is silent and the camera shows a test pattern instead of real video. Nothing here captures the physical microphone or camera of anyone, and it should never be described that way. If the worker joins a real meeting, leave its camera off unless the test pattern should be broadcast. The site can still ask for permission, and granting that is a separate policy decision from offering a device.

Reading the example as one deployment

Put together, the sample configuration describes a local development setup:

  • The gateway accepts only local connections, on port 8787.
  • Signed viewer links also point back at the local machine.
  • Delegation to individual end users is available to a trusted backend.
  • Embedding of the viewer is permitted for the local agent UI, served from port 8088 under either hostname.
  • Workers start genuine Google Chrome without a visible window.
  • Both service workers and synthetic media devices are on, which keeps applications compatible.
  • Every user has a persistent browser profile.
  • Public sites are generally open.
  • A few company domains can resolve into private or CGNAT networks.
  • Navigation exceptions for loopback and CGNAT are enabled.
  • The broad approval flag is off. High-risk actions the classifier recognizes stay gated at the gateway regardless, and the agent UI may add approvals of its own.

This works as a local integration profile but is no production baseline. Before launch: move the public URL to HTTPS, rotate every secret and keep it outside the file, disable loopback, narrow the CGNAT and private-origin exceptions, review service-worker access, turn on gateway approval policy, put profiles and artifacts on encrypted managed storage, and forward audit events to a durable platform.

Connecting an agent

An MCP-capable agent can start the adapter as a local process and point it at the gateway. Real credentials stay out of source control, so use placeholders like these:

{
  "mcpServers": {
    "cloud-browser": {
      "command": "node",
      "args": ["/path/to/cloud-browser/packages/mcp/src/index.js"],
      "env": {
        "CB_GATEWAY_ENDPOINT": "https://browser.example.com/v1/browser",
        "CB_GATEWAY_TOKEN": "retrieve-from-your-secret-manager",
        "CB_DOWNLOAD_ROOTS": "/approved/output"
      }
    }
  }
}

A typical interaction then runs like this:

Agent: browser_open_tab(url)
Cloud Browser: tab identifier, page metadata, safe live-view metadata

Agent: browser_get_state(tab)
Cloud Browser: revision-bound semantic state

Agent: browser_click(fresh target)
Cloud Browser: confirmed receipt, updated state, evidence

User: take control through signed viewer
Cloud Browser: block agent input

User: release control
Cloud Browser: restore agent input

Agent: browser_finalize_session()
Cloud Browser: close working tabs, revoke authority, expire artifacts

How this differs from a browser tool inside one agent

Plenty of agent products ship their own browser tool. Cloud Browser is meant to be the browser platform underneath many agents, and its value comes from a combination of traits:

  • it stays neutral about which model is used
  • sessions persist remotely
  • interaction is both semantic and visual
  • WebRTC allows observation and control
  • credentials are handled securely
  • the gateway enforces approvals
  • screenshots provide evidence and replay
  • artifacts are scoped to tasks
  • cleanup follows a deterministic lifecycle
  • enterprise identity, policy and auditing boundaries are built in

The pitch boils down to this:

Demonstrate a workflow one time, then run it through any AI agent, with a human still able to stop consequential steps.

Lessons from building it

The browser is not the hard part; authority is. Six lessons came out of the design.

A live view is a security surface rather than just a video player: it holds controls, approvals, credentials and user intent, so its URL, framing policy, expiry and control state need the care an API capability gets.

Getting a message through is not the same as getting the interaction done. A click may arrive while the page simply ignores it, which is why input needs an acknowledgement, a look at the page afterward, and separate labels for "delivered," "changed" and "still waiting."

Blind retries are risky. Wherever a browser action can have real-world consequences, idempotency and reconciliation are required.

Persistent sessions make the product more useful and more risky at once. Isolate them by identity and pair them with sign-out, retention, history and cleanup controls.

Treat page content and page-supplied tools as untrusted. A site may describe an action; it has no power to approve it.

Lastly, bounded evidence makes automation easier to trust. With a screenshot timeline and action summaries, users, developers and operators can all understand a failure.

Where this leaves things

Agents need browsers, but enterprises want more than scripts. They want a platform with clear ownership, authority that expires, safe human handoff, protected sign-in, approval before anything has impact, outcomes that can be observed and dependable cleanup.

Cloud Browser makes Playwright and Chromium into that platform, keeping planning apart from execution, agents apart from credentials, and browser capability apart from user authority. That split is why the design is reusable: swap the agent, the model or the interface and the safety boundary around the browser stays put. It is the groundwork needed before people can really trust browser automation.