docs: document required browser receiver pairing

This commit is contained in:
mirivlad 2026-07-10 03:50:24 +08:00
parent 2ac7dd52ec
commit c3ee4cc21f
4 changed files with 68 additions and 43 deletions

View File

@ -250,9 +250,12 @@ search.provider
Текущий статус: базовый `verstak.browser-inbox` implemented as both a global
sidebar view and a workspace item. Workspace tabs keep their own pending queue;
the global sidebar view aggregates queues from all workspaces plus unscoped
global captures. The local receiver now has an opt-in paired mode that requires
`X-Verstak-Receiver-Token` before publishing browser capture events. Browser
Inbox stores plugin-owned `domainBindings` and routes unscoped captures with an
global captures. The local receiver starts in paired mode: it generates an
installation-local token and requires `X-Verstak-Receiver-Token` before
publishing browser capture events. The Browser Inbox settings panel exposes the
receiver URL and token, and rotates the token through the dangerous
`browser.receiver.manage` permission. Browser Inbox stores plugin-owned
`domainBindings` and routes unscoped captures with an
exact domain match into the bound workspace queue. Its first conversion workflow
creates ordinary Markdown notes through the public Files API and publishes a
`browser.capture.converted` event, which Activity records through its public

View File

@ -53,14 +53,15 @@ Known remaining gaps:
- Activity, Journal, Browser Inbox conversion workflows, indexed Search, and
Secrets now have baseline plugin implementations and public API contracts.
Their remaining work is product UX depth: richer Today aggregation,
actionable Activity to Journal review flows, capture/pairing discoverability,
production-grade reporting, and final polish.
actionable Activity to Journal review flows, capture-from-clipboard/manual
capture, production-grade reporting, and final polish.
- Templates plugin is not implemented yet.
- File/image preview exists as a basic provider with bounded inline image
rendering through the public Files API.
- Browser extension repository has protocol, queue, and Chromium/Firefox build
scaffold; desktop has a local receiver and mounted-view inbox plugin; receiver
pairing, basic Browser Inbox domain binding, create-note conversion, and
scaffold; desktop has a bounded, token-paired local receiver and mounted-view
inbox plugin; receiver pairing settings, basic Browser Inbox domain binding,
create-note conversion, and
create-link conversion are implemented, text file attachment conversion is
implemented, bounded binary attachment conversion is implemented, and Activity
records conversions. Chunked large-file attachment capture remains future
@ -176,6 +177,9 @@ Tasks:
- [x] implement browser extension capture scaffold for URL, selected text,
page title, and link captures;
- [x] define local receiver permission/pairing model;
- [x] require an installation-local pairing token and expose its rotation through
Browser Inbox settings;
- [x] bound browser receiver payloads and file content before publishing events;
- [x] add domain-to-workspace binding;
- [x] convert inbox entries into notes through public plugin APIs;
- [x] record converted inbox entries in Activity through public plugin events;
@ -189,10 +193,10 @@ Verification:
- local receiver API tests;
- inbox plugin smoke/e2e tests.
Status: baseline capture, routing, conversion, and Activity recording workflows
are implemented. Remaining work is UX follow-up: make extension pairing,
Status: baseline capture, pairing, routing, conversion, and Activity recording
workflows are implemented. Remaining work is UX follow-up:
capture-from-clipboard/manual capture, domain binding state, and conversion
outcomes obvious in the visible app flow.
outcomes in the visible app flow.
### Phase 6 - Secrets

View File

@ -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:

View File

@ -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.