Merge origin/main into garrytan/browserharness

Resolves three conflicts:

- VERSION: kept 1.19.0.0 (this branch's consolidated Phase 1 + 2a ship);
  main brought 1.17.0.0 (setup-gbrain wireup) on top of 1.16.0.0 (tunnel
  allowlist), this branch leaps past with a gap (CLAUDE.md allows version
  gaps).
- package.json: synced to 1.19.0.0 to match VERSION (HEAD had 1.16.0.0
  stale from the prior release commit; this fixes the drift).
- CHANGELOG.md: kept the v1.19.0.0 entry on top, then main's v1.17.0.0
  + v1.16.0.0 entries below.

browse/src/server.ts auto-merged: main's TUNNEL_COMMANDS expanded from
17→26 (export const) with the canDispatchOverTunnel pure gate is intact
alongside this branch's LOCAL_LISTEN_PORT addition for $B skill run.

Verification: 858/0 pass across browser-skill-write + browser-skill-commands
+ browser-skills-storage + skill-token + dual-listener + tunnel-gate-unit
+ skill-validation + gen-skill-docs (covers both this branch's Phase 2a
and main's tunnel allowlist work).

Regenerated all SKILL.md files (--host all) to absorb main's setup-gbrain
template changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-04-28 01:47:26 -07:00
21 changed files with 1646 additions and 128 deletions
+2 -2
View File
@@ -32,7 +32,7 @@ GStack Browser Server Any AI agent
The daemon binds two HTTP sockets. The **local listener** serves the full command surface to 127.0.0.1 only and is never forwarded. The **tunnel listener** is bound lazily on `/tunnel/start` (and torn down on `/tunnel/stop`) with a locked path allowlist. ngrok forwards only the tunnel port.
A caller who stumbles onto your ngrok URL cannot reach `/health`, `/cookie-picker`, `/inspector/*`, or `/welcome` — those paths don't exist on that TCP socket. Root tokens sent over the tunnel get 403. The tunnel listener accepts only `/connect`, `/command` (with a scoped token + the 17-command browser-driving allowlist), and `/sidebar-chat`.
A caller who stumbles onto your ngrok URL cannot reach `/health`, `/cookie-picker`, `/inspector/*`, or `/welcome` — those paths don't exist on that TCP socket. Root tokens sent over the tunnel get 403. The tunnel listener accepts only `/connect`, `/command` (with a scoped token + the 26-command browser-driving allowlist), and `/sidebar-chat`.
See [ARCHITECTURE.md](../ARCHITECTURE.md#dual-listener-tunnel-architecture-v1600) for the full endpoint table.
@@ -165,7 +165,7 @@ Each agent owns the tabs it creates. Rules:
## Security Model
- **Physical port separation.** Local listener and tunnel listener are separate TCP sockets. ngrok only forwards the tunnel port. Tunnel callers cannot reach bootstrap endpoints at all (404, wrong port).
- **Tunnel command allowlist.** `/command` over the tunnel only accepts 17 browser-driving commands (goto, click, fill, snapshot, text, etc.). Server-management commands (tunnel, pair, token, useragent, eval, js) are denied on the tunnel.
- **Tunnel command allowlist.** `/command` over the tunnel only accepts 26 browser-driving commands (goto, click, fill, snapshot, text, newtab, tabs, back, forward, reload, closetab, etc.). Server-management commands (tunnel, pair, token, useragent, js) are denied on the tunnel.
- **Root token is tunnel-blocked.** A request bearing the root token over the tunnel listener returns 403 with a pairing hint. Only scoped session tokens work over the tunnel.
- **Setup keys** expire in 5 minutes and can only be used once.
- **Session tokens** expire in 24 hours (configurable).
+7 -3
View File
@@ -43,9 +43,13 @@ The command:
3. Pushes an initial commit with just the config.
4. Writes `~/.gstack-brain-remote.txt` (URL-only, no secrets —
safe to copy to another machine).
5. Registers GBrain as a reader if `GBRAIN_URL` + `GBRAIN_TOKEN` are
configured. Otherwise you can add readers later with
`gstack-brain-reader add <name> --ingest-url <url> --token <token>`.
5. Wires the gstack-brain repo into your local gbrain as a federated
source (via `gbrain sources add` + `git worktree`) so `gbrain search`
can index your synced learnings, plans, and designs. Implementation
lives in `bin/gstack-gbrain-source-wireup`. The old
`gstack-brain-reader add --ingest-url ...` HTTP path was removed in
v1.15.1.0 — it depended on a `/ingest-repo` endpoint gbrain never
shipped.
After init, the **next skill you run** will ask you ONE question about
privacy mode: