anyagent-bridge 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +81 -0
- package/LICENSE +21 -0
- package/README.md +289 -0
- package/bin/anyagent-bridge.js +127 -0
- package/client/index.html +525 -0
- package/config.example.json +69 -0
- package/docs/INSTALL.md +138 -0
- package/docs/ROADMAP.md +168 -0
- package/docs/SECURITY.md +85 -0
- package/docs/WALKTHROUGH.md +82 -0
- package/docs/screenshots/.gitkeep +3 -0
- package/docs/screenshots/01-startup-banner.png +0 -0
- package/docs/screenshots/02-terminal-view.png +0 -0
- package/docs/screenshots/03-agent-running.png +0 -0
- package/docs/screenshots/04-mobile.png +0 -0
- package/package.json +57 -0
- package/server/auth/index.js +20 -0
- package/server/auth/manager.js +448 -0
- package/server/auth/oauth.js +154 -0
- package/server/auth/providers/github.js +59 -0
- package/server/auth/providers/google.js +44 -0
- package/server/auth/sessions.js +160 -0
- package/server/auth/store.js +135 -0
- package/server/auth/totp.js +140 -0
- package/server/index.js +1779 -0
- package/server/safety/audit.js +139 -0
- package/server/safety/clientip.js +73 -0
- package/server/safety/index.js +17 -0
- package/server/safety/manager.js +507 -0
- package/server/safety/redact.js +153 -0
- package/server/safety/sandbox.js +130 -0
- package/server/tunnel/adapters/cloudflare-quick.js +40 -0
- package/server/tunnel/adapters/cloudflared-named.js +49 -0
- package/server/tunnel/adapters/devtunnel.js +54 -0
- package/server/tunnel/adapters/tailscale.js +42 -0
- package/server/tunnel/base-adapter.js +185 -0
- package/server/tunnel/detect.js +65 -0
- package/server/tunnel/index.js +15 -0
- package/server/tunnel/manager.js +321 -0
- package/server/tunnel/registry.js +31 -0
- package/test/stage4-boot.js +98 -0
- package/test/stage4-smoke.js +267 -0
package/docs/INSTALL.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
anyagent-bridge runs on **macOS, Windows, and Linux**. Pick one of the three
|
|
4
|
+
install paths below. For what the bridge is and how to use it once running, see
|
|
5
|
+
the top-level [README](../README.md); for the security model, read
|
|
6
|
+
[SECURITY.md](SECURITY.md) before exposing it to anything but localhost.
|
|
7
|
+
|
|
8
|
+
## Requirements
|
|
9
|
+
|
|
10
|
+
- **Node.js ≥ 18** (`node -v`). The bridge uses only Node's built-ins plus a few
|
|
11
|
+
small npm packages; the one native dependency is `node-pty`.
|
|
12
|
+
- **A C/C++ toolchain** so `node-pty` can build (only for the npx / from-source
|
|
13
|
+
paths — the Docker image builds it for you):
|
|
14
|
+
- **macOS** — Xcode Command Line Tools: `xcode-select --install`.
|
|
15
|
+
- **Linux** — `build-essential` + `python3` (Debian/Ubuntu:
|
|
16
|
+
`sudo apt-get install -y build-essential python3`).
|
|
17
|
+
- **Windows** — Visual Studio Build Tools (C++ workload) + Python 3. The
|
|
18
|
+
simplest route is to install Node via the official installer with the
|
|
19
|
+
"Tools for Native Modules" checkbox ticked.
|
|
20
|
+
- **The agent CLI(s) you want to drive** — e.g. `claude` (Claude Code) or
|
|
21
|
+
`codex` — installed and working in your own terminal. The bridge launches
|
|
22
|
+
whatever you register; it does not bundle any agent.
|
|
23
|
+
- **Optional:** Docker (only for the container path or for Stage 4 sandboxing),
|
|
24
|
+
and a tunnel CLI (`devtunnel`, `cloudflared`, or `tailscale`) for remote access.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Path A — `npx` (fastest way to try it)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx anyagent-bridge
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This downloads and runs the bridge in one step, then prints an access URL with a
|
|
35
|
+
token. Open it in your browser. Useful flags:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx anyagent-bridge --port 8080 # listen on a different port
|
|
39
|
+
npx anyagent-bridge --host 0.0.0.0 # expose on your LAN (token is the only gate)
|
|
40
|
+
npx anyagent-bridge --tunnel devtunnel # also open a remote tunnel
|
|
41
|
+
npx anyagent-bridge --help # all flags
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**State location caveat.** Run this way, the generated token and runtime state
|
|
45
|
+
live inside the package's install directory under npm's cache, not in your
|
|
46
|
+
current folder. That is fine for a quick try, but for daily or persistent use
|
|
47
|
+
prefer **Path B (from source)** or **Path C (Docker)**, where you control where
|
|
48
|
+
state lives.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Path B — From source (recommended for regular use)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
git clone https://github.com/elon-choo/anyagent-bridge.git
|
|
56
|
+
cd anyagent-bridge
|
|
57
|
+
npm ci # installs deps and builds node-pty
|
|
58
|
+
cp config.example.json config.json
|
|
59
|
+
npm start
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Edit `config.json` to register your agents, projects, and (optionally) auth or
|
|
63
|
+
tunnel settings — every field is documented inline in `config.example.json` and
|
|
64
|
+
in the README. Environment overrides go in a `.env` file (copy `.env.example`).
|
|
65
|
+
|
|
66
|
+
On first boot the server generates an access token and saves it to
|
|
67
|
+
`.data/auth.json`; the startup banner prints the full URL with the token. Re-runs
|
|
68
|
+
reuse the saved token.
|
|
69
|
+
|
|
70
|
+
To keep it running in the background, use your platform's process manager
|
|
71
|
+
(`pm2 start npm --name anyagent-bridge -- start`, a `systemd` unit, a `launchd`
|
|
72
|
+
agent, or Windows Task Scheduler).
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Path C — Docker / docker-compose (isolated, reproducible)
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git clone https://github.com/elon-choo/anyagent-bridge.git
|
|
80
|
+
cd anyagent-bridge
|
|
81
|
+
docker compose up -d --build
|
|
82
|
+
docker compose logs bridge # the startup banner prints your access token
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then open <http://127.0.0.1:3001> and paste the token. The compose file:
|
|
86
|
+
|
|
87
|
+
- Publishes the port on **127.0.0.1 only** (localhost). The bridge binds
|
|
88
|
+
`0.0.0.0` *inside* the container; the `ports:` mapping is what controls host
|
|
89
|
+
exposure — keep it on `127.0.0.1` unless you front it with an authenticated
|
|
90
|
+
tunnel/proxy.
|
|
91
|
+
- Persists the access token and audit log (both under `.data`) in a named volume
|
|
92
|
+
(`bridge-data`), so they survive `docker compose down && up`. (The session list
|
|
93
|
+
in `sessions.json` lives outside `.data` and is not in the volume.)
|
|
94
|
+
|
|
95
|
+
**Reading the token without the logs:** pin your own instead —
|
|
96
|
+
set `BRIDGE_AUTH_TOKEN` in the `environment:` block of `docker-compose.yml`.
|
|
97
|
+
|
|
98
|
+
**Agents inside the container.** The image ships the bridge plus a `bash` shell,
|
|
99
|
+
**not** the agent CLIs. To launch `claude`/`codex` from within the container you
|
|
100
|
+
must either:
|
|
101
|
+
|
|
102
|
+
1. derive your own image (`FROM anyagent-bridge:local`, then install the agent
|
|
103
|
+
CLI and bake in or mount its credentials), or
|
|
104
|
+
2. run the bridge on the host (Path A/B) where your agents already live, and use
|
|
105
|
+
Docker only for Stage 4's per-session sandbox (which runs each session in a
|
|
106
|
+
*separate*, operator-supplied agent image).
|
|
107
|
+
|
|
108
|
+
Plain shell access and the file API work in the container as-is.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Updating
|
|
113
|
+
|
|
114
|
+
- **npx** — always fetches the latest published version; nothing to update.
|
|
115
|
+
- **From source** — `git pull && npm ci`.
|
|
116
|
+
- **Docker** — `git pull && docker compose up -d --build`.
|
|
117
|
+
|
|
118
|
+
## Uninstalling
|
|
119
|
+
|
|
120
|
+
- **From source** — delete the cloned folder; it holds `.data/` (including your
|
|
121
|
+
token) and there is no Docker volume to clean up.
|
|
122
|
+
- **Docker** — `docker compose down -v` (the `-v` also deletes the `bridge-data`
|
|
123
|
+
volume and its token), then delete the cloned folder.
|
|
124
|
+
- **npx** — `npm cache clean --force` removes the cached package.
|
|
125
|
+
|
|
126
|
+
## Troubleshooting
|
|
127
|
+
|
|
128
|
+
- **`node-pty` build fails** — you are missing the toolchain above. Install it,
|
|
129
|
+
then `npm ci` again. On Windows, reopen the terminal after installing the
|
|
130
|
+
Build Tools so the new `PATH` is picked up.
|
|
131
|
+
- **Port already in use (`EADDRINUSE`)** — pick another port with `--port` /
|
|
132
|
+
`PORT=` / `config.json`.
|
|
133
|
+
- **The agent dropdown is empty or "command not found"** — the agent CLI is not
|
|
134
|
+
on the `PATH` of the process running the bridge. Verify it works in the same
|
|
135
|
+
shell, or set an absolute `command` in `config.json`.
|
|
136
|
+
- **Remote access not connecting** — see the README's "Remote access" section;
|
|
137
|
+
confirm the tunnel CLI is installed and logged in, and set
|
|
138
|
+
`auth.oauth.callbackBaseUrl` to your public URL when using OAuth behind a tunnel.
|
package/docs/ROADMAP.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
anyagent-bridge ships in stages. Stage 1 is a self-contained, useful tool on its own;
|
|
4
|
+
each later stage adds a layer for safer remote access without breaking the core. The
|
|
5
|
+
codebase deliberately leaves clean extension points (seams) for these stages.
|
|
6
|
+
|
|
7
|
+
## Stage 1 — Portable core ✅ (current)
|
|
8
|
+
|
|
9
|
+
The "it works" core, with nothing macOS- or person-specific:
|
|
10
|
+
|
|
11
|
+
- `TerminalSession` class with PTY spawn/respawn (automatic re-creation + backoff).
|
|
12
|
+
- Multi-viewer broadcast over a WebSocket protocol.
|
|
13
|
+
- Scrollback buffer (10,000 lines), heartbeat, and dead-connection detection.
|
|
14
|
+
- Session persistence (`sessions.json`).
|
|
15
|
+
- Universal agent profiles — register **any** command; launch via `startAgent`, drive via `sendAgent`/raw `input`.
|
|
16
|
+
- File-management API (browse/read/write/rename/move/delete/upload/download) behind a path whitelist.
|
|
17
|
+
- Crash guards (`uncaughtException`, `unhandledRejection`, `SIGINT`, `SIGTERM`).
|
|
18
|
+
- Token auth with constant-time comparison and a rate-limiting structure.
|
|
19
|
+
- Cross-platform shell selection and plain `fs.*` file access (no platform-specific brokers).
|
|
20
|
+
|
|
21
|
+
## Stage 2 — Free tunnel adapters ✅
|
|
22
|
+
|
|
23
|
+
Zero-/low-config remote access without paying for anything, via pluggable
|
|
24
|
+
adapters spawned as external CLIs (no new npm dependencies). Disabled by default;
|
|
25
|
+
when off, missing, or misconfigured the server runs exactly like Stage 1
|
|
26
|
+
(localhost-only) and never crashes.
|
|
27
|
+
|
|
28
|
+
- **Microsoft Dev Tunnels** (default) — one-time `devtunnel user login`; the URL rotates per run unless you pre-create a tunnel (`tunnelId`).
|
|
29
|
+
- **Cloudflare Quick Tunnel** (`cloudflare-quick`) — no account; ephemeral `*.trycloudflare.com` URL; testing-grade (≈200 req cap, no SSE).
|
|
30
|
+
- **Tailscale** (`tailscale`) — needs `tailscale up` + Funnel enabled in the tailnet ACL; stable `*.ts.net` URL; may need sudo.
|
|
31
|
+
- **cloudflared named tunnel** (`cloudflared-named`) — needs a Cloudflare account + zone, a pre-created tunnel and DNS route; stable custom hostname.
|
|
32
|
+
|
|
33
|
+
Config: the `tunnel` block in `config.json` (default `enabled:false`,
|
|
34
|
+
`provider:"devtunnel"`). Env overrides: `BRIDGE_TUNNEL_ENABLED`,
|
|
35
|
+
`BRIDGE_TUNNEL_PROVIDER`, `BRIDGE_TUNNEL_HOSTNAME`. Control at runtime:
|
|
36
|
+
`GET|POST /api/tunnel/{status,start,stop,restart}`. Add a 5th provider by
|
|
37
|
+
dropping an adapter under `server/tunnel/adapters/` and registering it in
|
|
38
|
+
`server/tunnel/registry.js`.
|
|
39
|
+
|
|
40
|
+
## Stage 3 — Authentication & sessions ✅
|
|
41
|
+
|
|
42
|
+
Login on top of Stage 1's static token, all opt-in. With OAuth off, no TOTP
|
|
43
|
+
enrolled, and `requireLogin` false, the bridge behaves exactly like Stage 2 (the
|
|
44
|
+
static token works everywhere). Zero new npm dependencies — built on Node's
|
|
45
|
+
`crypto` and the global `fetch`. Lives in `server/auth/`.
|
|
46
|
+
|
|
47
|
+
- **Signed sessions** — HMAC-SHA256 tokens, expiring and revocable, tracked by id
|
|
48
|
+
and persisted (`.data/auth-sessions.json`). Primary browser transport is an
|
|
49
|
+
httpOnly `aab_session` cookie (the WS upgrade and fetches carry it automatically);
|
|
50
|
+
`X-Session-Token` / `Authorization: Bearer` / `?session=` work for API clients.
|
|
51
|
+
- **TOTP 2FA** (RFC 6238) — enroll from the UI (`/api/auth/totp/setup` → `confirm`),
|
|
52
|
+
scan the `otpauth://` QR, get one-time recovery codes. A replay guard tracks the
|
|
53
|
+
last accepted step counter. Operator-only.
|
|
54
|
+
- **OAuth** — Google (auth-code + PKCE S256) and GitHub (auth-code + state). CSRF
|
|
55
|
+
`state` is single-use and TTL-bounded. Identity is checked against a per-provider
|
|
56
|
+
allowlist (`allowedEmails` / `allowedLogins`); an empty allowlist fails closed
|
|
57
|
+
unless `claimFirstUser` is on (first successful login claims the bridge, TOFU).
|
|
58
|
+
- **The one rule** — the static token is a *direct* credential UNLESS `requireLogin`
|
|
59
|
+
is set OR a TOTP secret is confirmed; then it becomes *login-only* (must be
|
|
60
|
+
exchanged, with the 2FA code, for a session). This is what makes 2FA real.
|
|
61
|
+
|
|
62
|
+
Config: the `auth` block in `config.json` (or `BRIDGE_*` env overrides — keep
|
|
63
|
+
OAuth client secrets in env). Routes: `POST /api/auth/login`, `/logout`,
|
|
64
|
+
`GET /api/auth/config|me|sessions`, `DELETE /api/auth/sessions/:id`,
|
|
65
|
+
`GET /api/auth/oauth/:provider/{start,callback}`, `/api/auth/totp/{status,setup,confirm,disable}`.
|
|
66
|
+
|
|
67
|
+
Defense-in-depth added here: a CSRF Origin check on cookie-authenticated writes
|
|
68
|
+
(bearer/token clients are exempt — they are not CSRF-able), an expanded file-API
|
|
69
|
+
denylist for secret-bearing dotfiles, and a bounded OAuth pending-state map.
|
|
70
|
+
|
|
71
|
+
Known residuals (deferred to Stage 4 hardening): `getClientIP` trusts
|
|
72
|
+
`X-Forwarded-For` unconditionally — behind an untrusted proxy the per-IP login
|
|
73
|
+
rate limit is evadable (the global cap still applies); set `callbackBaseUrl` when
|
|
74
|
+
OAuth is exposed so the redirect URI is not derived from request headers.
|
|
75
|
+
|
|
76
|
+
## Stage 4 — Sandboxing & safety ✅
|
|
77
|
+
|
|
78
|
+
Four opt-in safety layers in one subsystem, `server/safety/` (entry `index.js` exports
|
|
79
|
+
`createSafetyManager`; `manager.js` orchestrates; `sandbox.js` / `audit.js` / `redact.js`
|
|
80
|
+
/ `clientip.js` are pure leaves). All default **off** — with `safety.enabled` false the
|
|
81
|
+
server is byte-identical to Stage 3 (proven by `test/stage4-boot.js`). Zero new npm
|
|
82
|
+
dependencies: the Docker layer spawns the `docker` CLI (reusing `tunnel/detect`),
|
|
83
|
+
everything else is Node core. `server/index.js` is touched only at marked seams;
|
|
84
|
+
`auth._isOperator` is reused for the operator gate (auth internals untouched).
|
|
85
|
+
|
|
86
|
+
- **Docker isolation** — the whole session PTY runs `docker run` (the agent launches
|
|
87
|
+
inside the container, so `startAgent`/`sendToAgent` are unchanged). Only sessions
|
|
88
|
+
with a bounded **project dir** are sandboxed (never bind-mounts `$HOME`); the project
|
|
89
|
+
mounts at `/workspace`. Operator-supplied `image` (must contain the agent CLI).
|
|
90
|
+
`-it` for an in-container TTY; default limits (`--memory`/`--cpus`/`--pids-limit`/
|
|
91
|
+
`--security-opt no-new-privileges`); opt-in hardening (`--read-only`/`--cap-drop ALL`/
|
|
92
|
+
`--user`, the last Linux-only). Secrets reach the container as `-e NAME` (values off
|
|
93
|
+
the process table); the docker client env is a minimal allowlist (never the full
|
|
94
|
+
`process.env`); `BRIDGE_*`/token are denied even if listed. `network` defaults to
|
|
95
|
+
`bridge`. `onDockerMissing` = `host` (fall back, default) | `refuse`. Repeated fast
|
|
96
|
+
container exits auto-degrade to a host shell (no respawn storm). **Graceful, never
|
|
97
|
+
crashes.**
|
|
98
|
+
- **Kill-switch** — `POST /api/safety/kill/:id` (SIGKILL pty + `docker rm -f`),
|
|
99
|
+
`POST /api/safety/panic` (kill all + sweep `aab-<installId>-sess-*` strays + optional
|
|
100
|
+
tunnel stop + optional **lock**), `POST /api/safety/unlock`. The lock gates only new
|
|
101
|
+
agent launches (never the shell — no remote soft-brick) and persists across restart.
|
|
102
|
+
Operator-only on REST and WebSocket (`{type:"panic"|"kill"}`). Graceful `destroy()`
|
|
103
|
+
also reaps its container (no leak); a per-install container-name nonce so panic never
|
|
104
|
+
kills another install's containers.
|
|
105
|
+
- **Audit logging** — append-only JSONL under `.data/audit/` via one `res.on('finish')`
|
|
106
|
+
middleware (REST mutations) + WS seams (`agent.start`/`agent.send`); raw keystrokes
|
|
107
|
+
are intentionally not logged. Date + size rotation, retention prune by strict regex,
|
|
108
|
+
synchronous ordered append, `flushSync` on exit. Every field is redacted before write.
|
|
109
|
+
- **Secret redaction** — the audit log is **always** scrubbed (AWS/OpenAI/GitHub/Slack/
|
|
110
|
+
Google keys, JWT, PEM blocks, plus the bridge's own token + session secret by exact
|
|
111
|
+
match). Live PTY-stream redaction is opt-in (`redaction.liveStream`, default off so the
|
|
112
|
+
stream stays byte-identical) with a boundary hold-back for split tokens/PEM/ANSI and a
|
|
113
|
+
bounded `maxHoldBytes` (overflow masks the partial rather than leaking it).
|
|
114
|
+
|
|
115
|
+
**Stage 3 residual closed:** `trustProxy` (default off, opt-in) makes the client IP for
|
|
116
|
+
rate limiting + audit come from the direct socket peer instead of a spoofable
|
|
117
|
+
`X-Forwarded-For`; `true`/`N` trust the nearest / Nth proxy hop. (The OAuth
|
|
118
|
+
`redirect_uri`-from-Host residual remains a documented boot warning — hardening it would
|
|
119
|
+
require changing the shipped Stage-3 auth route, so it is deferred.)
|
|
120
|
+
|
|
121
|
+
Config: the `safety` block in `config.json` (`BRIDGE_SAFETY_ENABLED`, `BRIDGE_SANDBOX_*`,
|
|
122
|
+
`BRIDGE_AUDIT_ENABLED`, `BRIDGE_REDACT_LIVE`, `BRIDGE_TRUST_PROXY` env overrides).
|
|
123
|
+
Verified: `test/stage4-smoke.js` (32 unit) + `test/stage4-boot.js` (9 integrated,
|
|
124
|
+
byte-identical-off + safety-on + audit recording), **plus a live run against a real
|
|
125
|
+
Docker daemon (colima, 2026-06-26):** the real `spawnSpecFor` argv launches a sandbox
|
|
126
|
+
container (the project bind-mounted at `/workspace`, `--network none`, memory/cpu/pid
|
|
127
|
+
limits, `--security-opt no-new-privileges`, secrets passed by name), the session shell
|
|
128
|
+
runs *inside* it (`uname` → Linux), and `panic` kills + sweeps every
|
|
129
|
+
`aab-<installId>-sess-*` container with no leak while the lock gates new agent launches.
|
|
130
|
+
|
|
131
|
+
## Stage 5 — Packaging & distribution ✅
|
|
132
|
+
|
|
133
|
+
One-command installs plus the docs to run the bridge safely on any of the three
|
|
134
|
+
platforms. Zero new runtime dependencies and **no changes to the Stage 1–4 server** —
|
|
135
|
+
the launcher only sets environment variables the server already reads.
|
|
136
|
+
|
|
137
|
+
- **npx** — `bin/anyagent-bridge.js` (declared in `package.json`'s `bin`) boots the
|
|
138
|
+
server in-process and maps friendly flags (`--port` / `--host` / `--token` /
|
|
139
|
+
`--tunnel [provider]` / `--no-tunnel`) onto the existing `PORT` / `HOST` /
|
|
140
|
+
`BRIDGE_*` env vars. Because dotenv does not override an already-set variable, the
|
|
141
|
+
precedence is CLI flag > `.env` > `config.json`, and `npx anyagent-bridge` stays
|
|
142
|
+
equivalent to `node server/index.js`. Unknown flags and bad values fail with exit 1.
|
|
143
|
+
- **Docker** — a multi-stage `Dockerfile` builds `node-pty` in a toolchain stage and
|
|
144
|
+
ships a slim non-root runtime (`tini` as PID 1, a Node-only `/health` healthcheck,
|
|
145
|
+
`HOST=0.0.0.0` so the published port reaches it). `docker-compose.yml` publishes on
|
|
146
|
+
`127.0.0.1` only and persists the token / sessions / audit log in the `bridge-data`
|
|
147
|
+
named volume (read the token from `docker compose logs`). `.dockerignore` keeps host
|
|
148
|
+
state and secrets out of the build context. The image carries the bridge + a `bash`
|
|
149
|
+
shell but **not** the agent CLIs — documented, with two ways to add an agent.
|
|
150
|
+
- **Publishing hygiene** — a `files` allowlist in `package.json` publishes only the app
|
|
151
|
+
(`bin`/`server`/`client`/`docs`/`test` + `config.example.json` + `.env.example`); a
|
|
152
|
+
`npm pack --dry-run` confirms `config.json`, `.data/`, `.env`, and `sessions.json` are
|
|
153
|
+
never in the tarball (38 files, no secrets).
|
|
154
|
+
- **Docs** — `docs/INSTALL.md` (npx · from source · Docker, per-OS toolchain notes,
|
|
155
|
+
updating / uninstalling / troubleshooting), `docs/SECURITY.md` (threat model, safe
|
|
156
|
+
defaults, what to enable before exposing it, disclaimers), and `docs/WALKTHROUGH.md`
|
|
157
|
+
(end-to-end tour + screenshot capture guide). README gains a Quick start.
|
|
158
|
+
|
|
159
|
+
Verified offline: `node bin/anyagent-bridge.js --version/--help`, error paths exit 1, a
|
|
160
|
+
real boot through the launcher with `/health` returning **HTTP 200** and the banner
|
|
161
|
+
reflecting the `--port`/`--token` flags, `npm pack --dry-run` (file set above), and the
|
|
162
|
+
Stage 4 suites still green (`npm test` → 32 + 9 pass). **Verified live (Docker via colima,
|
|
163
|
+
2026-06-26):** `docker compose up --build` builds the multi-stage image, the container
|
|
164
|
+
runs **healthy**, `/health` returns 200 on the localhost-published port, it runs
|
|
165
|
+
**non-root** (`uid 1000`), `node-pty` loads and spawns a real PTY inside the container,
|
|
166
|
+
and the named volume persists the access token across `down && up` (the banner's token
|
|
167
|
+
source flips `generated`→`file`). Screenshot PNGs remain deferred to a live capture
|
|
168
|
+
(`docs/WALKTHROUGH.md` ships the text walkthrough + capture steps).
|
package/docs/SECURITY.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Security model & disclaimers
|
|
2
|
+
|
|
3
|
+
Read this before you expose anyagent-bridge to anything beyond your own machine.
|
|
4
|
+
|
|
5
|
+
## What you are running
|
|
6
|
+
|
|
7
|
+
anyagent-bridge gives a web browser **a real terminal and file access on the
|
|
8
|
+
machine it runs on**. Anyone who can reach the server *and* holds a valid token
|
|
9
|
+
or session can run commands, read and write files (within the configured path
|
|
10
|
+
whitelist), and drive your AI coding agents — with the same privileges as the
|
|
11
|
+
user running the bridge. Treat the access token like an SSH key.
|
|
12
|
+
|
|
13
|
+
## Safe by default
|
|
14
|
+
|
|
15
|
+
Out of the box the bridge is conservative:
|
|
16
|
+
|
|
17
|
+
- **Binds to `127.0.0.1`** (localhost only). Nothing on your network can reach it
|
|
18
|
+
until you explicitly set `host`/`HOST` to `0.0.0.0` or publish it.
|
|
19
|
+
- **No tunnel.** Remote access (Stage 2) is opt-in and off by default.
|
|
20
|
+
- **Token required.** A 32-byte random token is generated on first boot and
|
|
21
|
+
saved to `.data/auth.json` with `0600` permissions. There is never a blank or
|
|
22
|
+
default token. Token comparison is constant-time.
|
|
23
|
+
- **Path whitelist.** The file API is restricted to configured `allowedPaths`
|
|
24
|
+
(your home directory by default), and a denylist blocks secret-bearing dotfiles
|
|
25
|
+
(`.env`, `.npmrc`, SSH keys, cloud-credential files, …).
|
|
26
|
+
|
|
27
|
+
## Before you expose it (tunnel or `0.0.0.0`)
|
|
28
|
+
|
|
29
|
+
The token alone is a single shared secret. When the bridge is reachable beyond
|
|
30
|
+
localhost, layer on the opt-in protections:
|
|
31
|
+
|
|
32
|
+
- **Turn on authentication (Stage 3).** Set `requireLogin` so the static token
|
|
33
|
+
becomes login-only, enroll **TOTP 2FA**, and/or require **Google/GitHub OAuth**
|
|
34
|
+
with an email/login allowlist. With 2FA or `requireLogin` on, a leaked token is
|
|
35
|
+
no longer enough by itself.
|
|
36
|
+
- **Use a tunnel that has its own auth.** Microsoft Dev Tunnels can require a
|
|
37
|
+
sign-in; a Cloudflare/Tailscale path can sit behind their access controls.
|
|
38
|
+
Don't put a raw `0.0.0.0` bind directly on the public internet.
|
|
39
|
+
- **Pin `auth.oauth.callbackBaseUrl`** to your public URL when OAuth is on behind
|
|
40
|
+
a tunnel, so the redirect URI is not derived from (spoofable) request headers.
|
|
41
|
+
- **Set `trustProxy`** (Stage 4) only when you actually sit behind a known proxy,
|
|
42
|
+
so per-client rate limiting and the audit log record the real client IP instead
|
|
43
|
+
of a spoofable `X-Forwarded-For`. Leave it off otherwise.
|
|
44
|
+
- **Sandbox the sessions (Stage 4).** Run each session inside a Docker container
|
|
45
|
+
(`safety.sandbox`) so the agent touches only a mounted project directory, with
|
|
46
|
+
memory/CPU/pid limits and `no-new-privileges`. Use `--network none` for an
|
|
47
|
+
offline agent, `readOnlyRootfs`/`dropAllCaps` to harden further.
|
|
48
|
+
- **Enable the audit log (Stage 4).** Append-only JSONL of REST mutations and
|
|
49
|
+
agent commands, with secrets redacted. Pair it with the kill-switch
|
|
50
|
+
(`POST /api/safety/panic`) for an emergency stop that kills sessions and locks
|
|
51
|
+
new agent launches.
|
|
52
|
+
|
|
53
|
+
## Handling secrets
|
|
54
|
+
|
|
55
|
+
- **Keep OAuth client secrets in environment variables**, not in `config.json`
|
|
56
|
+
(which can be written back to disk at runtime). `config.json` is gitignored.
|
|
57
|
+
- The published npm package and the Docker image **exclude** `config.json`,
|
|
58
|
+
`.data/`, `.env`, and `sessions.json` — your token and secrets are never baked
|
|
59
|
+
into a distributable artifact.
|
|
60
|
+
- Audit logs are always secret-redacted; live PTY-stream redaction is available
|
|
61
|
+
opt-in (`redaction.liveStream`).
|
|
62
|
+
|
|
63
|
+
## Known limitations
|
|
64
|
+
|
|
65
|
+
- Stage 1 ships **no TLS** of its own. Terminate TLS at your tunnel/reverse proxy
|
|
66
|
+
(Dev Tunnels, Cloudflare, etc.) — never send a token over plain `http://` across
|
|
67
|
+
an untrusted network.
|
|
68
|
+
- Docker sandboxing is **defense-in-depth, not a security boundary you should bet
|
|
69
|
+
a hostile multi-tenant workload on**. Do not mount the Docker socket into a
|
|
70
|
+
sandboxed session (that is host-equivalent access), and do not bind-mount `$HOME`.
|
|
71
|
+
- The bridge is meant for **a single operator controlling their own machine**, not
|
|
72
|
+
as a multi-user SaaS. It is not a substitute for a VPN or a zero-trust gateway
|
|
73
|
+
in a sensitive environment.
|
|
74
|
+
|
|
75
|
+
## Reporting a vulnerability
|
|
76
|
+
|
|
77
|
+
Please report security issues **privately** — open a GitHub Security Advisory on
|
|
78
|
+
the repository (Security → Report a vulnerability) rather than a public issue, so
|
|
79
|
+
a fix can ship before details are public.
|
|
80
|
+
|
|
81
|
+
## Disclaimer
|
|
82
|
+
|
|
83
|
+
anyagent-bridge is provided **as-is, under the MIT License, with no warranty**.
|
|
84
|
+
You are responsible for how and where you expose it. Running it on a machine with
|
|
85
|
+
access to sensitive data or networks is at your own risk.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Walkthrough
|
|
2
|
+
|
|
3
|
+
A guided tour of using anyagent-bridge end to end. The screenshots below live in
|
|
4
|
+
[`docs/screenshots/`](screenshots/); to regenerate them yourself, see the
|
|
5
|
+
[capture instructions](#capturing-screenshots).
|
|
6
|
+
|
|
7
|
+
## 1. Start the bridge
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx anyagent-bridge # or: npm start / docker compose up -d --build
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The startup banner prints the access URL and token:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
===============================================================
|
|
17
|
+
AnyAgent Bridge — server running
|
|
18
|
+
===============================================================
|
|
19
|
+
URL: http://127.0.0.1:3001?token=ab12…ef
|
|
20
|
+
WebSocket: ws://127.0.0.1:3001/ws
|
|
21
|
+
Host: 127.0.0.1
|
|
22
|
+
Shell: /bin/zsh
|
|
23
|
+
Agents: claude, codex
|
|
24
|
+
...
|
|
25
|
+
Access token (generated): ab12…ef
|
|
26
|
+
===============================================================
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
## 2. Open the browser UI
|
|
32
|
+
|
|
33
|
+
Open the printed URL (the `?token=…` logs you in automatically), or visit
|
|
34
|
+
<http://127.0.0.1:3001> and paste the token. You land on the terminal view: a full
|
|
35
|
+
xterm.js terminal wired to a live shell on your machine, plus a toolbar with the
|
|
36
|
+
agent launcher, a file browser, and (if configured) project and tunnel controls.
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
## 3. Launch an AI agent
|
|
41
|
+
|
|
42
|
+
Pick an agent (e.g. **Claude Code**) from the dropdown and start it. The bridge
|
|
43
|
+
spawns the agent's CLI inside the session's PTY, so you interact with it exactly
|
|
44
|
+
as you would in your own terminal — streamed live to the browser. Type prompts,
|
|
45
|
+
send keys, and watch output in real time. Detaching the browser keeps the session
|
|
46
|
+
alive; reconnecting reattaches with full scrollback.
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
## 4. Browse and edit files
|
|
51
|
+
|
|
52
|
+
Use the file panel to browse, open, edit, upload, and download files within the
|
|
53
|
+
configured path whitelist. Image uploads are supported (handy for pasting a
|
|
54
|
+
screenshot to an agent).
|
|
55
|
+
|
|
56
|
+
## 5. Go remote (optional)
|
|
57
|
+
|
|
58
|
+
To reach the bridge from your phone or another machine, enable a tunnel — either
|
|
59
|
+
at startup (`--tunnel devtunnel`) or at runtime via the tunnel controls
|
|
60
|
+
(`POST /api/tunnel/start`). The banner and the UI show the public URL once it is
|
|
61
|
+
ready. Before exposing anything, read [SECURITY.md](SECURITY.md) and turn on
|
|
62
|
+
login / 2FA / OAuth.
|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Capturing screenshots
|
|
69
|
+
|
|
70
|
+
The images above were captured locally against a running bridge. To regenerate
|
|
71
|
+
them yourself:
|
|
72
|
+
|
|
73
|
+
1. Start the bridge and note the token: `npm start`.
|
|
74
|
+
2. Open the printed URL in a browser (desktop and a phone/responsive view for the
|
|
75
|
+
mobile shot).
|
|
76
|
+
3. Capture each state listed above and save it under `docs/screenshots/` with the
|
|
77
|
+
matching filename (`01-startup-banner.png`, `02-terminal-view.png`, …).
|
|
78
|
+
4. PNG, ~1400px wide, is plenty. Crop out anything sensitive — **the token in the
|
|
79
|
+
URL bar and any real file contents** — before committing.
|
|
80
|
+
|
|
81
|
+
Because the token grants full access, never publish a screenshot that shows a
|
|
82
|
+
live token, private file paths, or agent credentials.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "anyagent-bridge",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Control your local terminal and any CLI AI coding agent from a browser, anywhere.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "elon-choo",
|
|
7
|
+
"homepage": "https://github.com/elon-choo/anyagent-bridge#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/elon-choo/anyagent-bridge.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/elon-choo/anyagent-bridge/issues"
|
|
14
|
+
},
|
|
15
|
+
"main": "server/index.js",
|
|
16
|
+
"bin": {
|
|
17
|
+
"anyagent-bridge": "bin/anyagent-bridge.js"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"bin/",
|
|
24
|
+
"server/",
|
|
25
|
+
"client/",
|
|
26
|
+
"docs/",
|
|
27
|
+
"test/",
|
|
28
|
+
"config.example.json",
|
|
29
|
+
".env.example",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"keywords": [
|
|
34
|
+
"claude-code",
|
|
35
|
+
"codex",
|
|
36
|
+
"ai-agent",
|
|
37
|
+
"terminal",
|
|
38
|
+
"pty",
|
|
39
|
+
"websocket",
|
|
40
|
+
"remote",
|
|
41
|
+
"bridge",
|
|
42
|
+
"self-hosted",
|
|
43
|
+
"developer-tools"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"start": "node server/index.js",
|
|
47
|
+
"test": "node test/stage4-smoke.js && node test/stage4-boot.js"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"cors": "^2.8.5",
|
|
51
|
+
"dotenv": "^16.4.5",
|
|
52
|
+
"express": "^4.21.2",
|
|
53
|
+
"multer": "^1.4.5-lts.1",
|
|
54
|
+
"node-pty": "^1.0.0",
|
|
55
|
+
"ws": "^8.18.0"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AnyAgent Bridge — auth subsystem entry (Stage 3)
|
|
3
|
+
*
|
|
4
|
+
* The only module server/index.js imports for authentication. Creates an
|
|
5
|
+
* AuthManager and exposes the OAuth provider list for diagnostics. Sits on top
|
|
6
|
+
* of the Stage 1 static token; disabled features leave Stage 2 behavior intact.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const AuthManager = require('./manager');
|
|
10
|
+
const { PROVIDERS } = require('./oauth');
|
|
11
|
+
|
|
12
|
+
function createAuthManager(authConfig, deps) {
|
|
13
|
+
return new AuthManager(authConfig, deps);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function listOAuthProviders() {
|
|
17
|
+
return Object.keys(PROVIDERS);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = { createAuthManager, listOAuthProviders };
|