Skip to content

alerubbio/chrome-agent-bridge

Repository files navigation

chrome-agent-bridge

Drive your real, logged-in Chrome from CLI and AI agents. A Chrome MV3 extension plus a local Node relay expose a standard CDP endpoint, so any Chrome DevTools Protocol client (Playwright's CDP connect, agent-browser, or your own) can automate the browser you actually use: your sessions, your cookies, no re-login.

Built around two hard requirements:

  1. No --remote-debugging-port. The extension connects out to the relay through chrome.debugger, so this works even where Chrome's debug port is blocked by MDM or enterprise policy, and you never run your daily browser in debug mode.
  2. The agent never takes over. Agent tabs are created unfocused in a labeled tab group inside your current window. Your focus is never stolen, and your tabs are never listed, attached, or closed by the agent.
CDP client (agent-browser, Playwright over CDP, ...)
        |  ws://127.0.0.1:9334/devtools/browser/<uuid>
        v
  relay/relay.mjs           Node relay, 127.0.0.1:9334
        |  ws://127.0.0.1:9334/extension   (extension dials OUT to the relay)
        v
  extension/background.js   MV3 service worker
        |  chrome.debugger API
        v
     Chrome tab

Why this exists

Headless browsers and fresh profiles can't do authenticated work: banking dashboards, internal tools, webmail, anything behind SSO. The usual alternative, relaunching your real Chrome with --remote-debugging-port, exposes every tab to every local process, breaks under enterprise policy, and means running your daily driver in debug mode. This bridge gives agents a narrow, supervised path into the real browser instead.

Features

  • Standard CDP surface. /json/version, /json/list, browser- and page-level WebSocket endpoints. Existing CDP clients connect without modification.
  • Agent tab isolation (AGENT_OWN_TAB, default on). Each connecting client gets a fresh tab, created inactive, grouped under a cyan "⚡ Agent" tab group in your window. The relay hides your tabs from the client entirely: they don't appear in Target.getTargets, can't be attached, and closeTab refuses anything the agent didn't create.
  • Opt-in session driving (CLAIM_URL). When you want the agent in a logged-in tab, CLAIM_URL=github.com node relay/relay.mjs hands it the first tab matching that URL substring, and nothing else.
  • No focus stealing. Tab activation is a no-op by policy: the extension acknowledges Target.activateTarget without switching what you're looking at, and window focus is never taken. Navigation, evaluation, and reads all work on background tabs. The trade-off: Chrome only delivers synthetic input (mouse clicks, keystrokes) to a window's active tab, so on background tabs drive interactions via Runtime.evaluate (element.click(), setting field values) — or opt in below.
  • Opt-in mouse input (MOUSE_ACTIVATE=on). For flows that need real synthetic mouse events, the relay activates the agent tab within its own window (never focusing the window) once before dispatching mouse input. The click e2e runs in this mode.
  • No debugger banner. Chrome's yellow "started debugging this browser" infobar isn't just noise: its cancel path detaches every chrome.debugger session at once. The included launcher, Dock wrapper, and flag-watch watchdog keep Chrome running with --silent-debugger-extension-api (see below).
  • Self-healing. launchd keeps the relay alive, a 20s alarm keeps the MV3 service worker alive, a 15s grace period bridges extension restarts, and browser-doctor.sh checks and repairs the whole chain.
  • Clean teardown. On client disconnect the relay detaches every tab that client attached and closes every tab it created. User tabs survive, always.

Quick start (macOS)

# 1. Install dependencies + the relay as a login LaunchAgent (autostarts, KeepAlive)
npm install
./install.sh

# 2. Load the extension (one-time, Chrome can't do this from a script):
#    chrome://extensions -> Developer mode -> Load unpacked -> select extension/

# 3. Verify
curl -s 127.0.0.1:9334/status     # expect "extensionConnected":true

# 4. Drive
agent-browser connect 9334
agent-browser open https://example.com
agent-browser screenshot /tmp/shot.png

Anything that speaks CDP works; agent-browser is just the client used in the examples and tests.

To run the relay in the foreground instead: npm run relay (or PORT=9334 node relay/relay.mjs). Note the extension dials ws://127.0.0.1:9334/extension; if you change the relay port, update RELAY_URL in extension/background.js to match.

The debugger banner, killed properly

--silent-debugger-extension-api is the only mechanism that removes the banner (there is no enterprise policy or preference for it), and it only applies at process start. Three pieces make it stick:

  • launch-chrome.sh launches Chrome with the flag (refuses if Chrome is already running, since flags can't be injected into a live process).
  • wrapper/build-wrapper.sh builds "Google Chrome (No Banner).app": a Dock-able launcher wearing Chrome's icon that declares http/https, so you can set it as your default browser. Link clicks while Chrome is closed then launch it flagged instead of bare.
  • flag-watch.sh (a LaunchAgent installed by install.sh) catches everything else: login reopen, Spotlight, open -a. If it finds Chrome running without the flag it shows one dialog per Chrome process offering a one-click relaunch; --restore-last-session brings your tabs back.

Reliability notes (hard-won)

  • macOS TCC will kill launchd jobs that read from ~/Desktop, ~/Documents, or ~/Downloads, with exit code 78 and an empty error log (writing the log is blocked too). install.sh therefore deploys the relay runtime to ~/Library/Application Support/chrome-agent-bridge and points launchd there; the repo stays wherever you cloned it. The unpacked extension can load from the repo, since Chrome is a GUI app with Desktop access.
  • Chrome caches an unpacked extension's code until you press the reload button on chrome://extensions. Browser restarts do not pick up edits. If behavior doesn't match the code, that's why.
  • ./browser-doctor.sh checks LaunchAgent → relay → extension → flag, repairs what it safely can, and ./browser-doctor.sh --deep drives a page end-to-end.

Security model

Read this before installing; the whole point of the tool is programmatic access to your real browser.

  • Local trust boundary. The relay binds 127.0.0.1 only and is never reachable from the network. But any local process can connect to it and drive an agent tab in your browser, in your session context. This is the same trust model as Chrome's own --remote-debugging-port, narrowed: by default a connector gets a fresh blank tab, not your tabs.
  • What isolation does and doesn't protect. AGENT_OWN_TAB keeps clients from seeing or touching your existing tabs. It does not sandbox the session: an agent tab shares your profile's cookies, so a malicious local client could open a site you're logged into in its own tab. If your threat model includes hostile local processes, don't run this (or any CDP endpoint).
  • CLAIM_URL is the deliberate escape hatch. It grants one existing tab, matched by URL substring, to the client. Set it per-invocation, not permanently.
  • The extension enforces its own guards independent of the relay: chrome:// and extension pages can't be attached, and close requests for non-agent tabs are refused.
  • Logs stay local. The relay writes relay/relay.log (tab titles/URLs it touched, debug breadcrumbs). It's gitignored; treat it as sensitive.

Testing

Three end-to-end suites, each on an isolated port with a throwaway Chrome-for-Testing profile, so they never touch your real browser or a live relay:

node test/e2e.mjs                 # navigate + isolation: agent lands on its own tab, user tabs untouched
node test/e2e-createtarget.mjs    # Target.createTarget path (agent-browser tab new)
node test/e2e-click.mjs           # synthetic mouse clicks land on backgrounded tabs

They need Chrome for Testing (agent-browser install, or point CHROME_BIN at any CfT binary) and agent-browser on PATH (override with AGENT_BROWSER).

Requirements

  • macOS (launchd, TCC handling, and the wrapper are Mac-specific; the relay and extension themselves are portable)
  • Node 18+ (only runtime dependency: ws)
  • Chrome or Chromium with MV3 support

License

MIT

About

Drive your real, logged-in Chrome from CLI and AI agents — MV3 extension + local CDP relay, no --remote-debugging-port, no focus stealing, no debugger banner

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages