docs: document required browser receiver pairing

This commit is contained in:
2026-07-10 03:50:24 +08:00
parent 2ac7dd52ec
commit c3ee4cc21f
4 changed files with 68 additions and 43 deletions
@@ -4,14 +4,22 @@
**Goal:** Add a token-based pairing gate to the local browser capture receiver.
**Architecture:** Keep Browser Inbox as a plugin. Add a transport-level token option to `internal/core/browserreceiver`, preserve the open constructor for current development behavior, and document the extension header contract.
**Architecture:** Keep Browser Inbox as a plugin. The desktop runtime generates
and requires an installation-local receiver token, while the plugin and browser
extension expose the settings transfer and rotation flow.
**Tech Stack:** Go desktop core package tests, browser extension protocol docs, Markdown docs.
## Completion Status (2026-07-10)
Completed. The implementation also added bounded ingress validation, token
persistence and rotation, the `browser.receiver.manage` SDK permission, a
Browser Inbox settings panel, and extension popup token persistence.
## Global Constraints
- Do not move Browser Inbox queues or conversion workflows into desktop core.
- Preserve existing receiver behavior when no token is configured.
- Production startup must fail closed when a token cannot be persisted.
- Paired mode must not publish capture events for missing or wrong tokens.
- Use TDD: write the failing Go receiver test first, run it red, then implement.
- Commit and push each affected repository after meaningful changes.
@@ -27,11 +35,11 @@
**Interfaces:**
- Produces documented `X-Verstak-Receiver-Token` pairing contract.
- [ ] **Step 1: Write spec and plan**
- [x] **Step 1: Write spec and plan**
Write the design and this implementation plan.
- [ ] **Step 2: Verify docs**
- [x] **Step 2: Verify docs**
Run:
@@ -42,7 +50,7 @@ git diff --check
Expected: exits 0.
- [ ] **Step 3: Commit and push docs**
- [x] **Step 3: Commit and push docs**
Run:
@@ -66,11 +74,11 @@ Expected: docs `main` is clean and pushed.
- `type Options struct { RequireToken bool; ReceiverToken string }`
- `func NewWithOptions(bus *events.Bus, options Options, providers ...WorkspaceProvider) *Receiver`
- [ ] **Step 1: Write the failing tests**
- [x] **Step 1: Write the failing tests**
Add tests proving missing/wrong token rejection and correct token acceptance.
- [ ] **Step 2: Run RED**
- [x] **Step 2: Run RED**
Run:
@@ -81,12 +89,12 @@ go test ./internal/core/browserreceiver
Expected: fails because `Options` / `NewWithOptions` do not exist.
- [ ] **Step 3: Implement token gate**
- [x] **Step 3: Implement token gate**
Add `Options`, `NewWithOptions`, header validation, and constant-time token
comparison. Keep `New` behavior unchanged.
- [ ] **Step 4: Run GREEN**
- [x] **Step 4: Run GREEN**
Run:
@@ -98,7 +106,7 @@ go test ./internal/core/...
Expected: both commands exit 0.
- [ ] **Step 5: Commit and push desktop**
- [x] **Step 5: Commit and push desktop**
Run:
@@ -123,7 +131,7 @@ remains unstaged.
- Consumes verified receiver token gate.
- Produces docs matching implemented pairing behavior.
- [ ] **Step 1: Update extension README**
- [x] **Step 1: Update extension README**
Change the receiver token header description from optional future work to:
@@ -131,7 +139,7 @@ Change the receiver token header description from optional future work to:
- `X-Verstak-Receiver-Token: <token>` required when the desktop receiver is in paired mode
```
- [ ] **Step 2: Verify and commit extension docs**
- [x] **Step 2: Verify and commit extension docs**
Run:
@@ -145,7 +153,7 @@ git push
Expected: extension `main` is clean and pushed.
- [ ] **Step 3: Update platform docs**
- [x] **Step 3: Update platform docs**
In `05_Official_Plugins.md`, describe that Browser Inbox receives captures
through the local receiver token pairing model. In
@@ -155,7 +163,7 @@ through the local receiver token pairing model. In
- [x] define local receiver permission/pairing model;
```
- [ ] **Step 4: Verify and commit docs**
- [x] **Step 4: Verify and commit docs**
Run:
@@ -2,31 +2,31 @@
## Purpose
The browser extension already sends captures to the desktop local receiver and
can include `X-Verstak-Receiver-Token`. The desktop receiver currently accepts
captures without a pairing model. This slice defines and implements the local
receiver token gate without moving Browser Inbox behavior into desktop core.
The browser extension sends captures to the desktop local receiver with
`X-Verstak-Receiver-Token`. The desktop runtime starts that receiver only in
paired mode, without moving Browser Inbox queues or conversion behavior into
desktop core.
## Scope
This slice covers only the local receiver permission/pairing model:
- receiver token validation;
- clear HTTP responses for paired/unpaired requests;
- documentation of how the extension presents the token.
- generation and local persistence of a receiver token;
- receiver token validation and rotation without a server restart;
- bounded capture payload validation before an event is published;
- Browser Inbox and extension settings for transferring the token.
It does not implement domain-to-workspace binding, inbox conversion to
notes/files/activity, browser UI for pairing QR codes, or encrypted token
storage. Those remain later Phase 5 work.
It does not implement browser UI for pairing QR codes or encrypted keyring
storage. The current token is kept in the installation-local app settings file,
which the manager writes with mode `0600`.
## Model
The receiver has two modes:
- **Open legacy mode:** no receiver token is configured. This preserves current
development behavior and accepts captures without the token header.
- **Paired mode:** a receiver token is configured and enabled. Every capture
request must include:
The production receiver always starts in paired mode. On startup it generates a
32-byte random token when none exists, stores it outside the vault in
`~/.config/verstak/config.json`, and does not log it. If that token cannot be
persisted, the local receiver is disabled rather than opened without a token.
Every capture request must include:
```text
X-Verstak-Receiver-Token: <token>
@@ -34,7 +34,8 @@ X-Verstak-Receiver-Token: <token>
The receiver compares the supplied token to the configured token using a
constant-time comparison. It does not publish browser capture events when the
token is missing or wrong.
token is missing or wrong. The package keeps the open constructor for embedded
legacy callers, but `main.go` does not use it.
## HTTP Contract
@@ -76,7 +77,8 @@ Wrong token in paired mode:
Other validation behavior remains unchanged: invalid payloads return `400`,
missing Browser Inbox consumers return `503`, and non-POST methods return
`405`.
`405`. Payloads over 12 MiB return `413`; capture text, file metadata, encoded
binary data, and decoded file content are capped before publication.
## Runtime API
@@ -93,6 +95,12 @@ func NewWithOptions(bus *events.Bus, options Options, providers ...WorkspaceProv
`New(bus, providers...)` remains the open legacy constructor.
The Wails bridge exposes `PluginBrowserReceiverPairing` and
`PluginRotateBrowserReceiverToken` only to plugins declaring the dangerous
`browser.receiver.manage` permission. `verstak.browser-inbox` presents the
receiver URL, token copy action, and rotation control; the browser extension
stores the values in its local settings.
## Testing
`internal/core/browserreceiver/receiver_test.go` must prove:
@@ -100,4 +108,6 @@ func NewWithOptions(bus *events.Bus, options Options, providers ...WorkspaceProv
- paired receivers reject missing tokens with `401`;
- paired receivers reject wrong tokens with `401`;
- paired receivers accept correct tokens and publish the capture event;
- open legacy receivers still accept captures without a token.
- a token persists across settings reload and changes immediately on rotation;
- oversized or malformed capture payloads do not publish an event;
- Browser Inbox and extension smoke tests cover the settings transfer path.