Replace Ed25519 with hashed API tokens and an in-Firefox client manager.

This commit is contained in:
alexveley
2026-09-22 07:24:35 -04:00
parent 55f7e863dc
commit 9242c01015
16 changed files with 487 additions and 339 deletions
+21 -36
View File
@@ -1,8 +1,8 @@
# Firefox Agent Bridge
A small Firefox extension that exposes an **allowlisted** WebExtension API to local scripts and agents.
A small Firefox add-on that exposes an **allowlisted** WebExtension API to local scripts and agents — after you issue them a secret from inside Firefox.
This is **not** a bookmark sync engine. Firefox already has `browser.bookmarks`. The extension is a bridge: while Firefox is open, a script on the same machine can call those functions over `http://127.0.0.1:17634`.
This is **not** a bookmark sync engine. Firefox already has `browser.bookmarks`. The add-on is a bridge: while Firefox is open, a script on the same machine can call those functions over `http://127.0.0.1:17634` with a Bearer token.
Agents: start at [AGENTS.md](AGENTS.md).
@@ -10,16 +10,7 @@ Agents: start at [AGENTS.md](AGENTS.md).
Firefox will not let an outside process talk to Places. Editing `places.sqlite` while the browser is running loses deletes. The supported path is `browser.bookmarks` inside an extension, plus [native messaging](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging) so a localhost HTTP host can reach that extension.
## Pieces
| Piece | Role |
|-------|------|
| `extension/` | WebExtension (`bookmarks` + `nativeMessaging`) |
| `host/` | Native host: stdio to Firefox, HTTP to scripts |
| `tools/client.py` | Signed Python caller (`FAB_KEY_FILE`) |
| `examples/replace_named_folder.py` | Sample "replace this folder" script |
## Install (this workstation)
## Setup
1. Register the native host (once):
@@ -27,42 +18,36 @@ Firefox will not let an outside process talk to Places. Editing `places.sqlite`
powershell -NoProfile -File tools\install-native-host.ps1
```
That registers
`HKCU\Software\Mozilla\NativeMessagingHosts\com.easygoingaming.firefox_agent_bridge`.
2. Load the add-on (`about:debugging` temporary, or a signed `.xpi` — [docs/signing.md](docs/signing.md)).
Then create a client key **outside** that state directory and give the file to tooling:
3. Click the toolbar icon → **Manage clients** → name a client → **Generate secret**. Copy the `fab_…` token into whatever secret store you already use.
```powershell
python tools\register_client.py add --name cursor-agent --write-key $HOME\.fab\cursor-agent.json
$env:FAB_KEY_FILE = "$HOME\.fab\cursor-agent.json"
```
2. Load the extension. Firefox Release will not keep an unsigned add-on across restarts:
- `about:debugging#/runtime/this-firefox`
- **Load Temporary Add-on**
- pick `extension\manifest.json`
Permanent install: Mozilla-signed `.xpi` — [docs/signing.md](docs/signing.md). Start with `--channel=unlisted`.
3. Leave Firefox open. The extension starts the host; `GET http://127.0.0.1:17634/health` should return JSON.
4. Leave Firefox open. `GET http://127.0.0.1:17634/health` should return JSON.
## Call it
```powershell
$env:FAB_KEY_FILE = "$HOME\.fab\cursor-agent.json"
$env:FAB_TOKEN = "fab_…" # the value from the Manage clients modal
python tools/client.py meta.methods
python tools/client.py bookmarks.search "[{\"title\":\"10.132.x.x\"}]"
python tools/client.py bookmarks.search '[{"title":"10.132.x.x"}]'
```
See [AGENTS.md](AGENTS.md) for the method list, HTTP surface, and failure modes.
`curl` works the same: `Authorization: Bearer fab_…`.
## Security
- Binds **127.0.0.1 only**. Browser `Origin` headers are rejected; `Host` must be loopback.
- Calls (except `/health`) must be signed by a **registered Ed25519 client**. The host keeps public keys only.
- Only the methods in `extension/background.js` `ALLOWED` run. No history, cookies, tabs, or `onMessageExternal` in v0.1.
- Another add-on with `bookmarks` already has Places; this bridge does not give it a new path in.
- Binds **127.0.0.1** only. Browser `Origin` headers are rejected.
- Each caller is an issued token. The add-on stores a hash, not the secret.
- Only `bookmarks.*` (plus `meta.*`) in `extension/background.js`. No history, cookies, tabs, or `onMessageExternal`.
## Pieces
| Piece | Role |
|-------|------|
| `extension/` | Add-on, popup, Manage clients page |
| `host/` | Native host: stdio to Firefox, HTTP to scripts |
| `tools/client.py` | Bearer caller (`FAB_TOKEN`) |
| `examples/replace_named_folder.py` | Sample folder replace |
## License