Agent-readable docs index: /llms.txt. Full docs in one file: /llms-full.txt. Download /docs.zip to grep all markdown files locally.

Security Model and Sandboxed Execution

By default, everything runs on your machine. The relay binds to localhost:19988 and only accepts connections from the extension. No traffic leaves the machine unless you explicitly enable Remote control or configure remote access.

Architecture

┌──────────────────────────────────────────────────────────────────────────────────────────┐ │ YOUR MACHINE │ │ │ │ ┌─────────────┐ ┌──────────────────┐ ┌──────────────┐ │ │ │ Extension │ <────────> Relay Server │<────────> CLI / MCP │ │ │ │ (Chrome) │ WS │ localhost:19988 │ WS │ (Agent) │ │ │ └─────────────┘ └──────────────────┘ └──────────────┘ │ │ │ │ Nothing leaves localhost unless you explicitly enable a remote feature │ └──────────────────────────────────────────────────────────────────────────────────────────┘

Security guarantees

Local by default. The WebSocket server binds to localhost. Traffic leaves your machine only when you confirm Remote control for a tab or configure full relay access with a tunnel and auth token.
Origin validation. The extension WebSocket endpoint only accepts the Playwriter extension origin. Browsers cannot spoof the Origin header, so malicious websites cannot connect and control your browser. CLI and MCP connect as local Node.js clients on localhost; for remote access, always use token authentication.
Controlled tab scope. Existing tabs are controlled after you click the extension icon. Playwriter never opens a tab on its own. New tabs are created only when code calls context.newPage(). Playwriter does not attach to other existing tabs in the background.
Visible automation. The Chrome debugger banner is always visible on controlled tabs. You can see everything the agent does in real time.

Sandboxed filesystem

The require('node:fs') module in the execution sandbox is scoped. Write operations only succeed in:
Allowed pathDescription
Session cwdThe directory where playwriter CLI was invoked
/tmpSystem temp directory
os.tmpdir()OS-specific temp (e.g. /var/folders/.../T/ on macOS)
Writing to any other path throws EPERM: operation not permitted, access outside allowed directories. To save files elsewhere, write to a temp path first, then move the file using a shell command outside the sandbox.

Sandbox restrictions

The execution sandbox runs in a controlled environment:
  • No import statements. Use require() for Node.js modules.
  • No __dirname or __filename. Use process.cwd() or absolute paths.
  • Scoped require. Only safe Node.js built-in modules are available: path, url, querystring, crypto, buffer, util, assert, events, timers, stream, zlib, http, https, os, and scoped fs.
  • No process.chdir(). Use a new session with a different cwd.
  • No browser.close() or context.close(). These would disconnect all agents.

Remote access security

When using Remote control (see the full Remote control security page):
  • Explicit consent. A short confirmation, with a Read more link, explains that the recipient can control your browser before the tunnel starts.
  • Not a sandbox. The recipient gets the same access as a local Playwriter agent: every Playwriter tab, plus new tabs. It must be fully trusted. A short denylist blocks only whole-profile cookie APIs and cache clears.
  • Minimal handshake. The extension sends the browser name and Playwriter version, not the user's email, Google account ID, or install ID.
  • Revocable bearer link. Anyone with the secret URL can connect until the user clicks Stop sharing on the Remote ON dropdown of any Playwriter tab, or closes the tab where sharing started. Treat the URL like a password.
  • Viewer request omits the secret. Shared links are https://playwriter.dev/remote-control#{id}. The initial viewer-page request and Referer omit the fragment. The viewer then uses the id to connect to the tunnel hostname, which the tunnel service and Cloudflare necessarily process.
  • No payload storage. Tunnel frames are relayed in memory only, and response caching is off for these tunnels. Cloudflare can process connection metadata as the infrastructure provider.
  • Privacy disclosure. Remote traffic can contain screenshots, page content, URLs, input events, network data, cookies, authentication data, and browser storage. Read the privacy policy before sharing sensitive tabs.
When exposing the full relay through remote access:
  • Token authentication is required. The --token flag on playwriter serve enforces auth on all connections.
  • Encrypted transport. Traforo tunnels use Cloudflare's TLS infrastructure.
  • No port forwarding. No firewall rules or VPN needed; the tunnel handles everything.
# Host machine: serve with auth token npx traforo -p 19988 -- playwriter serve --token MY_SECRET # Remote machine: connect with token export PLAYWRITER_HOST=https://my-tunnel.traforo.dev export PLAYWRITER_TOKEN=MY_SECRET playwriter session new

Recorder start and stop

The toolbar Record Skill button does not fetch the relay from the page. The page posts a message to the content script. The service worker then calls POST /recorder/start.
CORS on those routes allows only the Playwriter extension origin. A website cannot pass the preflight, so it cannot start or stop a recording.
The worker fetch looks cross-site (chrome-extension:// to 127.0.0.1). /recorder/start and /recorder/stop skip the Sec-Fetch-Site block for that reason. /recorder/events and /recorder/status do not.
Never use Network.clearBrowserCookies via CDP. It's a profile-wide destructive operation that wipes ALL cookies across every domain in the user's Chrome profile; Gmail, GitHub, and every authenticated session.
Use scoped cookie operations instead:
const cdp = await getCDPSession({ page: state.page }) const { cookies } = await cdp.send('Network.getCookies', { urls: ['https://example.com'] }) // Delete individually for (const cookie of cookies) { await cdp.send('Network.deleteCookies', { name: cookie.name, domain: cookie.domain }) }