Log into any service once. A0 uses it forever.
An Agent Zero plugin that opens a remote browser so you can authenticate to any web service from your own browser.
A0 inherits those sessions, learns site patterns, and replays recorded workflows autonomously.
- Playbook recorder fixed — the CDP observer was losing event subscriptions after WebSocket reconnects (Chrome drops domain registrations when the connection resets). Now tracks enabled domains and re-enables them automatically. Also re-registers the
__phantomBridgeCDP binding after navigation context destruction. - Play/Delete buttons in sidebar — each saved playbook now has a ▶ Play button (triggers autonomous replay) and a ✕ Delete button (with confirmation) directly in the Phantom Bridge sidebar panel.
- Replay toolbar in noVNC popup — collapsible "Playbooks" overlay in the remote browser viewer window so you can trigger replays without switching back to the sidebar.
- Recording instructions in system prompt — A0 now understands the record/replay flow: when you say "record this", it calls
bridge_recordand waits while you browse in noVNC, instead of trying to browse itself. - Path traversal fix —
delete_playbookAPI now sanitizes names and verifies resolved paths stay insidedata/playbooks/. - Install from inside A0 — README now has a 2-command section for when A0 installs the plugin itself (no Docker needed from inside the container).
- Reverts to the canonical Agent Zero plugin install pattern. v1.4.0–v1.4.2 tried to ship a prebuilt Docker image (
ghcr.io/notabotchef/phantom-bridge) but the image was built FROMfrdel/agent-zero-run:latest, an outdated base — the current A0 base isagent0ai/agent-zero-base:latest. The build pipeline never produced a working image; users following v1.4.0/v1.4.1/v1.4.2 instructions hitpull access deniedor a stale image. - New Quick Start (3 commands) — clones the plugin into
./a0-data/usr/plugins/phantom_bridge, drops in the compose override (port 6080 + plugin volume mount), and restarts. Then click Execute on the Phantom Bridge entry in A0's Plugins UI to runexecute.py. This is the standard A0 plugin install path that has worked for the entire 1.x line. execute.pyhardened — auto-bootstrapspython3-pipviaaptif the base image doesn't ship it, uses--break-system-packages(PEP 668), and--ignore-installedso pip doesn't fight with apt-managed packages likepython3-cryptography. Also checkscryptographyimport in addition towebsockets.docker-compose.override.ymlno longer references the broken image. It now only adds the noVNC port and the plugin volume mount — your existing A0 image stays in use.- Heads up: if you tried installing v1.4.0, v1.4.1, or v1.4.2, you got a broken stack. Pull the latest
docker-compose.override.ymland re-run the new Quick Start.
- One-liner installer —
install.shwith security warning, 5-second countdown, SHA256 verification, A0 dir auto-detection, Docker and manual modes, post-installbridge_doctorcheck. Shellcheck clean. - Release workflow —
.github/workflows/release.ymlpublishes install.sh SHA256 to GitHub Release notes so users can verify before running. - README "Install via script" section placed after Quick Start with explicit security warning.
- Prebuilt Docker image —
ghcr.io/notabotchef/phantom-bridge:latesthas x11vnc, novnc, xvfb, xdotool, chromium, websockets, and cryptography pre-installed. No apt or pip steps. - Smart entrypoint —
docker-entrypoint-phantom.shdetects whether you have a git-cloned plugin mounted and skips the baked copy if so. Yourgit pullworkflow is preserved. - Drop-in compose override —
docker-compose.override.ymldrops next to your existing compose file and auto-merges. No edits to your original file needed. - GitHub Actions publish pipeline —
.github/workflows/docker-publish.ymlbuilds multi-arch (amd64 + arm64) on tag push and publishes to ghcr.io withpackages: write. - Quick Start section — README now leads with the 3-command Docker path, with the manual install preserved below as a fallback.
- Diagnostics & pre-flight —
bridge_doctortool runs 5 health checks (noVNC port, system binaries, DISPLAY env, cookie key, Python deps) and prints copy-paste fix commands for every failure. - Pre-flight in
bridge_open— After the bridge starts,probe_novncchecks the noVNC endpoint and prepends an actionable hint to the response if the viewer is unreachable — no more silent blank screens. - State-aware WebUI — The sidebar panel shows an amber/red banner with the exact fix hint when
health_state != healthy. - Verified
execute.py— Afterapt-get install, the installer now re-checks thatx11vncandwebsockifyactually landed on PATH and exits 1 with a clear error if they didn't.
Older versions (v1.2.x and earlier) → see the full changelog on GitHub Releases.
A0 runs inside a Docker container with its own Chromium. When it needs to access authenticated services, the traditional options are:
- Export cookies from your host browser → import into the container → watch them get invalidated because fingerprints don't match
- Build OAuth integrations for every service → weeks of work, partner approvals, API waitlists
- Hard-code credentials → security nightmare, breaks on 2FA
None of these scale. Every new service is another integration project.
Phantom Bridge flips the model. Instead of moving credentials into the container, you use the container's browser directly:
1. Tell A0: "open the browser bridge"
2. A remote browser viewer appears — you're looking at A0's Chromium
3. Log into anything. Google, NotebookLM, X, GitHub, AWS, Jira.
4. Close the bridge. A0's browser agent inherits every session.
5. Show A0 a workflow once — it replays it forever.
Sessions persist across container restarts. No export/import. No fingerprint mismatch. No API keys.
Full native browser control via noVNC — not screenshots, not iframes, real VNC.
- Keyboard, mouse, clipboard — everything works, including captchas
- Draggable modal inside A0's UI with minimize, resize, and pop-out
- Screencast fallback — works through A0's port when noVNC port isn't exposed
The bridge and A0's browser_agent share the same Chromium profile directory. When you log into Google via the bridge, A0's browser agent has those cookies immediately. No transfer step.
Bridge Chromium ──→ data/profile/ ←── A0's browser_agent
(shared cookies, localStorage, sessions)
Cookies are stored as encrypted per-domain files at data/cookies/<domain>.json. Cookie values are encrypted at rest using Fernet symmetric encryption — names and metadata stay in plaintext so A0 can inspect structure without decrypting:
[
{ "name": "SID", "encrypted_value": "gAAAAABn...", "domain": ".google.com", "httpOnly": true, "secure": true, "expires": 1756684800 },
{ "name": "HSID", "encrypted_value": "gAAAAABn...", "domain": ".google.com", "httpOnly": true, "secure": false }
]- Encrypted at rest — session tokens are never stored in plaintext on disk
- Per-domain files — A0 only loads cookies for the domain it needs (cheaper token calls)
- On-demand decryption — A0 uses the
bridge_decrypt_cookiestool to get plaintext cookies in memory when needed for HTTP requests - Live cookie counts per domain in the sidebar panel
- Delete All button to wipe every session instantly
Three observation layers watch silently while you browse:
| Layer | What it does | Data file |
|---|---|---|
| Auth Registry | Detects logins via cookie diffing + auth URL patterns | data/auth_registry.json |
| Sitemap Learner | Maps URL patterns and features per domain | data/sitemaps/*.json |
| Playbook Recorder | Records replayable navigation sequences | data/playbooks/*.json |
The plugin injects context into A0's system prompt (additive — never replaces the core prompt):
- When to suggest the bridge — A0 proactively suggests opening the bridge when it detects authentication failures, login redirects, or requests for authenticated services
- Live session state — A0 knows which domains are authenticated and when sessions expire
- Playbook awareness — A0 knows what workflows have been recorded and suggests replay when appropriate
"I need authenticated access to NotebookLM. Would you like to
open the Phantom Bridge so you can log in? I'll be able to use
that session afterward."
Teach A0 once, it does it forever:
- Open the bridge
- Tell A0: "Record this — I'll show you how to generate images on Gemini" (or click Start Recording in the sidebar)
- Walk through the workflow in the remote viewer
- Tell A0: "Stop recording" (or click Stop Recording in the sidebar)
- From now on: "Generate images on Gemini like I showed you"
A0 replays the recorded workflow autonomously using Playwright with the shared browser profile. Each step captures 6 locator strategies — so when CSS selectors break (dynamic classes, hashed IDs, SPA re-renders), replay falls back through text, ARIA role, aria-label, placeholder, and label matches automatically.
Your Browser ──→ A0 Web UI (:5050)
│
├── Sidebar Panel ── status, cookies, sitemaps, playbooks
│
└── Phantom Bridge Modal (draggable, resizable)
│
noVNC iframe (:6080)
│
websockify ──→ x11vnc ──→ Xvfb display
│
Chromium (system)
│
CDP WebSocket
│
┌───────────┴───────────┐
│ Observer Layers │
│ ┌─ Auth Registry │
│ ├─ Sitemap Learner │
│ └─ Playbook Recorder │
└────────────────────────┘
│
data/profile/ (shared)
│
A0's browser_agent
The _30_browser_bridge_profile.py extension runs at message_loop_start and patches browser_agent.State.get_user_data_dir() to return the bridge's profile directory instead of an ephemeral one. It also patches __del__ to prevent profile deletion. This means A0's browser agent uses the exact same cookies, localStorage, and sessions you created via the bridge.
The fastest path: one file download, one command, done. No apt, no pip, no execute.py.
Phantom Bridge installs the same way every other Agent Zero plugin does: drop the code into usr/plugins/, expose port 6080, click Execute in A0's Plugins UI.
# 1. Clone the plugin into your A0 plugins directory
git clone https://github.com/notabotchef/phantom-bridge.git \
./a0-data/usr/plugins/phantom_bridge
# 2. Drop in the compose override (adds port 6080 + plugin volume mount)
curl -O https://raw.githubusercontent.com/notabotchef/phantom-bridge/main/docker-compose.override.yml
# 3. (Re)start your A0 stack — Compose auto-merges the override
docker compose up -dThen open A0 at http://localhost:5050, go to Plugins, find Phantom Bridge, and click Execute. That runs execute.py inside the container, which installs x11vnc, novnc, xvfb, xdotool, chromium and the Python deps. After it finishes, click the Phantom Bridge icon in the sidebar to open the remote browser viewer.
Different noVNC port? Set
PHANTOM_NOVNC_PORT=6081in your.envfile before runningdocker compose up -d.
Updating?
git pullinside./a0-data/usr/plugins/phantom_bridgeand restart A0. The volume mount means new code is picked up immediately.
If you ask A0 to install this plugin, it runs commands inside the container where docker is not available. Skip the compose steps — the plugin loads automatically once cloned into the right path:
# 1. Clone the plugin
git clone https://github.com/notabotchef/phantom-bridge.git /a0/usr/plugins/phantom_bridge
# 2. Install system dependencies (x11vnc, novnc, xvfb, chromium, Python deps)
python /a0/usr/plugins/phantom_bridge/execute.pyThat's it. A0 hot-loads the plugin on clone — profile sharing is wired immediately. The execute.py step installs the display stack so the remote browser viewer works. Port 6080 must still be exposed in your docker-compose.yml on the host side for the noVNC viewer to be accessible from your browser.
Read the script before running. Piping curl to bash runs code you haven't reviewed. Prefer the Quick Start above unless you have a specific reason. Script source:
install.shin this repo. Pinned SHA published per release.
Two equivalent forms — the second lets you inspect before executing:
# Form 1: pipe directly (shows a 5-second countdown + Y/n confirm)
bash <(curl -fsSL https://raw.githubusercontent.com/notabotchef/phantom-bridge/main/install.sh)
# Form 2: download, inspect, then run (recommended)
curl -fsSL https://raw.githubusercontent.com/notabotchef/phantom-bridge/main/install.sh -o install.sh
less install.sh # read it
bash install.shOptions:
bash install.sh --dry-run --yes # preview what would happen, no changes
bash install.sh --mode=manual # git clone + execute.py instead of compose
bash install.sh --path=/your/a0/dir # override auto-detected A0 directoryVerify the script SHA before running (SHA is published in each GitHub Release):
echo "<sha-from-release> install.sh" | sha256sum --checkThe traditional install path — use this if you prefer full control, already have a custom A0 setup, or cannot use Docker Compose overrides.
# Clone into A0's plugin directory
git clone https://github.com/notabotchef/phantom-bridge.git /path/to/a0/usr/plugins/phantom_bridge
# Or copy if you already have the files
cp -r phantom_bridge /path/to/a0/usr/plugins/From A0's Plugins UI, click Execute on Phantom Bridge. Or run manually inside the container:
# Replace "a0" with your container name if different (see step 3)
docker exec -it a0 python /a0/usr/plugins/phantom_bridge/execute.pyThis installs: x11vnc, novnc, xvfb, xdotool, chromium
Port 6080 is the noVNC remote viewer — it's what lets you see and control A0's browser.
The data/ volume mount ensures cookies, sessions, and recorded playbooks survive container rebuilds.
A ready-to-use docker-compose.yml is included at the repo root. Copy it and adjust paths:
cp docker-compose.yml /path/to/your/a0/docker-compose.ymlOr add these two lines to your existing compose file:
services:
agent-zero: # ← your container's service name; change if different
ports:
- "5050:5000"
- "6080:6080" # Phantom Bridge remote viewer
volumes:
- ./a0-data/usr:/a0/usr # persists plugins, sessions, cookiesThen restart: docker compose up -d
docker run -d \
--name a0 \
-p 5050:5000 \
-p 6080:6080 \
-v "$(pwd)/a0-data/usr:/a0/usr" \
frdel/agent-zero-run:latestContainer name:
docker exectargets the container name, not the Compose service name. The container name is set bycontainer_name:indocker-compose.yml(defaulting to something likea0-agent-zero-1if omitted) or by--nameindocker run. The includeddocker-compose.ymlsetscontainer_name: a0, sodocker exec -it a0 ...works as shown. If you omitcontainer_name:or use a different value, either update the exec commands to match, or usedocker compose exec <service> ...(e.g.docker compose exec agent-zero ...) which targets the service name directly and always works.
No port 6080? The plugin still works — it falls back to a screencast mode that streams through A0's existing port (5050). No extra ports needed. The sidebar panel automatically detects which mode is available.
Just talk to A0:
- "Open the browser bridge"
- "I need to log into Google"
- "Record this workflow"
- "Replay the export I showed you"
Or click the phantom icon in A0's chat bar.
| Tool | Description |
|---|---|
browser_bridge_open |
Start the bridge + remote viewer |
browser_bridge_close |
Stop the bridge (sessions persist) |
browser_bridge_status |
Check status, pages, authenticated domains |
bridge_auth |
Query authenticated domains + session expiry |
bridge_health |
Test if a session is still valid |
bridge_sitemap |
Learned URL patterns per domain |
bridge_record |
Start/stop recording a workflow |
bridge_replay |
Replay a saved workflow autonomously |
bridge_decrypt_cookies |
Decrypt stored cookies for a domain (for HTTP requests) |
Edit default_config.yaml or configure via A0's plugin settings:
| Setting | Default | Description |
|---|---|---|
enabled |
true |
Enable/disable the plugin |
remote_debug_port |
9222 |
CDP port (internal) |
novnc_port |
6080 |
Remote viewer port |
profile_dir |
data/profile |
Browser profile location |
headless |
false |
Set true to disable display rendering |
window_width |
1280 |
Browser viewport width |
window_height |
900 |
Browser viewport height |
All persistent data lives in data/ (survives container restarts when /usr is volume-mounted):
data/
├── profile/ # Chromium user data (cookies, localStorage, sessions)
├── .cookie_key # Fernet symmetric encryption key (auto-generated)
├── cookies/ # Per-domain encrypted cookie files
│ ├── google.com.json
│ ├── github.com.json
│ └── ...
├── auth_registry.json # Authenticated domains with expiry metadata
├── sitemaps/ # Learned URL patterns per domain
└── playbooks/ # Recorded workflows for autonomous replay
- Cookie values are encrypted at rest using Fernet symmetric encryption. The key is auto-generated at
data/.cookie_keyon first export. Cookie names and metadata remain in plaintext for structure inspection. - On-demand decryption only. Plaintext cookie values are never written to disk — the
bridge_decrypt_cookiestool returns them in memory. - Port 6080 gives full browser control. Only expose on trusted networks.
- The entire
data/directory is gitignored. Never commit it. - All cookie data stays inside the container. Nothing is sent externally.
| Service | What A0 Can Do After You Log In |
|---|---|
| Access Gmail, Drive, Calendar, any Google service | |
| NotebookLM | Query knowledge bases, generate content |
| X / Twitter | Post content, monitor mentions, engage |
| Threads | Publish posts, read feeds |
| GitHub | Manage repos, review PRs, triage issues |
| AWS Console | Monitor resources, check billing, manage services |
| Jira / Linear | Track sprints, update tickets, manage backlogs |
| Vercel / Netlify | Deploy previews, check build logs, manage domains |
| Any web app | If you can log into it, A0 can use it |
| Approach | Setup | Captchas | Session Persistence | Fingerprint Match |
|---|---|---|---|---|
| Cookie export/import | Manual | Fails | Fragile | No |
| OAuth integration | Weeks per service | N/A | Depends | N/A |
| Credential injection | Security risk | Fails on 2FA | Fragile | No |
| Phantom Bridge | 5 minutes | Works | Persistent | Perfect |
phantom_bridge/
├── plugin.yaml # A0 plugin manifest
├── bridge.py # Core BrowserBridge singleton — Chromium + noVNC lifecycle
├── cookie_crypt.py # Fernet encryption for cookie values at rest
├── screencast.py # CDP screencast manager (zero-config fallback)
├── execute.py # Dependency installer
├── hooks.py # A0 framework lifecycle hooks
├── default_config.yaml # Plugin defaults
├── observer/ # Three-tier CDP observation system
│ ├── cdp_client.py # WebSocket client with pub/sub + auto-reconnect
│ ├── auth_registry.py # L1: cookie-based auth detection
│ ├── sitemap_learner.py # L2: URL pattern learning
│ ├── playbook_recorder.py # L3: workflow recording
│ └── manager.py # Orchestrates all observer layers
├── tools/ # A0 tool implementations (one per file)
├── api/ # HTTP API handlers
├── extensions/ # A0 extension hooks
│ ├── system_prompt/ # Injects bridge awareness into A0's prompt
│ ├── python/ # Profile sharing patch
│ ├── prompts/ # Tool usage examples for A0
│ └── webui/ # Chat bar button + modal injection
└── webui/ # Alpine.js sidebar panel + bridge viewer
Run the diagnostic tool from inside the container:
docker exec -it a0 python /a0/usr/plugins/phantom_bridge/tools/bridge_doctor.pyOr ask A0 directly: "Run bridge_doctor"
bridge_doctor checks 5 things and prints a copy-paste fix for each failure:
| Check | What it detects | Fix |
|---|---|---|
| noVNC port | Port 6080 not mapped in compose | Add "6080:6080" to ports:, then docker compose up -d |
| System binaries | x11vnc, websockify, Xvfb, etc. missing |
apt-get install -y x11vnc novnc xvfb xdotool chromium |
| DISPLAY env | Xvfb not running | Check ps aux | grep Xvfb; run bridge_open to restart |
| Cookie key | data/.cookie_key unreadable |
chmod 600 data/.cookie_key |
| Python deps | websockets or cryptography not installed |
pip install -r requirements.txt |
# exit 0 = healthy, exit 1 = something is wrong
docker exec a0 python /a0/usr/plugins/phantom_bridge/tools/bridge_doctor.py --quiet
echo "bridge health: $?"MIT
Built for Agent Zero by @notabotchef
