# Agent handshake — Firefox Agent Bridge Use this when a script or coding agent needs to **create, update, move, or delete Firefox bookmarks while Firefox is open**. Do not edit `places.sqlite` if this bridge is reachable. ## Preconditions 1. Native host installed: `powershell -NoProfile -File tools/install-native-host.ps1` 2. Add-on loaded (temporary via `about:debugging`, or a signed install) 3. A **client secret** issued from the add-on UI: toolbar icon → **Manage clients** → Generate secret 4. Firefox **running** If `http://127.0.0.1:17634/health` fails, the host is not up. If `/health` works but calls 401, you do not have a current `FAB_TOKEN`. ## Auth This is a normal API token, not an SSH key. 1. In Firefox, open the Agent Bridge toolbar icon → **Manage clients**. 2. Name the client (e.g. `cursor-agent`) and click **Generate secret**. 3. Copy the `fab_…` value **once**. Store it however you already store secrets: env var, Vaultwarden, `.env`, CI secret, Cursor env. 4. Give tooling only that string: ```text set FAB_TOKEN=fab_… python tools/client.py meta.methods ``` `FAB_TOKEN_FILE` pointing at a file that contains only the token is also fine. `--token` on the CLI overrides both. The add-on stores **SHA-256(token)** only. Revoke from the same page. A local process that never received the secret cannot call the API. CLI fallback (same store, prints the token once): ```text python tools/register_client.py add --name cursor-agent ``` ## Preferred caller ```text python tools/client.py METHOD [ARGS_JSON] ``` `ARGS_JSON` is a JSON **array** matching the WebExtension function arguments. ```text python tools/client.py bookmarks.search "[{\"title\":\"10.132.x.x\"}]" ``` HTTP: ```http POST /v1/call Authorization: Bearer fab_… Content-Type: application/json {"method": "bookmarks.search", "args": [{"title": "10.132.x.x"}]} ``` ## Allowlisted methods (v0.2) | Method | Args | Notes | |--------|------|--------| | `meta.ping` | (none) | Bridge alive | | `meta.methods` | (none) | This table | | `bookmarks.getTree` | (none) | Full tree | | `bookmarks.getSubTree` | `id` | | | `bookmarks.getChildren` | `id` | | | `bookmarks.get` | `id` or `[id, …]` | | | `bookmarks.search` | `{title? url? query?}` or string | | | `bookmarks.create` | `{parentId?, title, url?}` | Omit `url` to create a folder | | `bookmarks.update` | `id`, `{title? url?}` | | | `bookmarks.move` | `id`, `{parentId? index?}` | | | `bookmarks.remove` | `id` | Bookmark or empty folder | | `bookmarks.removeTree` | `id` | Folder and descendants | | `bookmarks.getRecent` | `numberOfItems` | | Do not invent other `browser.*` names. Adding an API means editing `ALLOWED` in `extension/background.js` and reloading the extension. REST aliases use the same Bearer token. `GET /health` has no token (loopback liveness only). ## Typical folder replace 1. `bookmarks.search` `{title: "…"}` — take the hit **without** a `url`. Fail if zero or many. 2. `bookmarks.getChildren` on that id. 3. `bookmarks.remove` each bookmark; `bookmarks.removeTree` each child folder. 4. `bookmarks.create` each new item with `parentId` set. `examples/replace_named_folder.py` does that (`FAB_TOKEN` must be set). ## Failure modes | Symptom | Cause | What to do | |---------|--------|------------| | Connection refused on `:17634` | Firefox closed or add-on/host missing | Open Firefox; load add-on; install native host | | 401 `Bearer token required` | Unsigned request | Set `FAB_TOKEN` | | 401 `unknown token` | Revoked, typo, or never generated | Manage clients → generate again | | 502 `method not allowed` | Typo or API not in `ALLOWED` | `meta.methods` | | Temporary add-on gone after restart | Unsigned on Firefox Release | Load again, or sign via AMO | ## Security boundaries - No `runtime.onMessageExternal`. Other add-ons cannot call the dispatcher. - Native host `allowed_extensions` is pinned to this add-on's gecko id. - HTTP is `127.0.0.1` only; browser `Origin` headers are rejected. - Secrets are ordinary Bearer tokens. Do not write them under `%LOCALAPPDATA%\firefox-agent-bridge`. - Another add-on with the `bookmarks` permission already has Places. - Do not add `tabs`, `history`, ``, or `onMessageExternal` without a new threat review. ## What this project is not - Not a bidirectional sync implementation - Not a general Firefox remote-control surface - Not a reason to keep editing `places.sqlite` on a live profile