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 Текущий статус: базовый `verstak.browser-inbox` implemented as both a global
sidebar view and a workspace item. Workspace tabs keep their own pending queue; sidebar view and a workspace item. Workspace tabs keep their own pending queue;
the global sidebar view aggregates queues from all workspaces plus unscoped 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 global captures. The local receiver starts in paired mode: it generates an
`X-Verstak-Receiver-Token` before publishing browser capture events. Browser installation-local token and requires `X-Verstak-Receiver-Token` before
Inbox stores plugin-owned `domainBindings` and routes unscoped captures with an 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 exact domain match into the bound workspace queue. Its first conversion workflow
creates ordinary Markdown notes through the public Files API and publishes a creates ordinary Markdown notes through the public Files API and publishes a
`browser.capture.converted` event, which Activity records through its public `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 - Activity, Journal, Browser Inbox conversion workflows, indexed Search, and
Secrets now have baseline plugin implementations and public API contracts. Secrets now have baseline plugin implementations and public API contracts.
Their remaining work is product UX depth: richer Today aggregation, Their remaining work is product UX depth: richer Today aggregation,
actionable Activity to Journal review flows, capture/pairing discoverability, actionable Activity to Journal review flows, capture-from-clipboard/manual
production-grade reporting, and final polish. capture, production-grade reporting, and final polish.
- Templates plugin is not implemented yet. - Templates plugin is not implemented yet.
- File/image preview exists as a basic provider with bounded inline image - File/image preview exists as a basic provider with bounded inline image
rendering through the public Files API. rendering through the public Files API.
- Browser extension repository has protocol, queue, and Chromium/Firefox build - Browser extension repository has protocol, queue, and Chromium/Firefox build
scaffold; desktop has a local receiver and mounted-view inbox plugin; receiver scaffold; desktop has a bounded, token-paired local receiver and mounted-view
pairing, basic Browser Inbox domain binding, create-note conversion, and inbox plugin; receiver pairing settings, basic Browser Inbox domain binding,
create-note conversion, and
create-link conversion are implemented, text file attachment conversion is create-link conversion are implemented, text file attachment conversion is
implemented, bounded binary attachment conversion is implemented, and Activity implemented, bounded binary attachment conversion is implemented, and Activity
records conversions. Chunked large-file attachment capture remains future records conversions. Chunked large-file attachment capture remains future
@ -176,6 +177,9 @@ Tasks:
- [x] implement browser extension capture scaffold for URL, selected text, - [x] implement browser extension capture scaffold for URL, selected text,
page title, and link captures; page title, and link captures;
- [x] define local receiver permission/pairing model; - [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] add domain-to-workspace binding;
- [x] convert inbox entries into notes through public plugin APIs; - [x] convert inbox entries into notes through public plugin APIs;
- [x] record converted inbox entries in Activity through public plugin events; - [x] record converted inbox entries in Activity through public plugin events;
@ -189,10 +193,10 @@ Verification:
- local receiver API tests; - local receiver API tests;
- inbox plugin smoke/e2e tests. - inbox plugin smoke/e2e tests.
Status: baseline capture, routing, conversion, and Activity recording workflows Status: baseline capture, pairing, routing, conversion, and Activity recording
are implemented. Remaining work is UX follow-up: make extension pairing, workflows are implemented. Remaining work is UX follow-up:
capture-from-clipboard/manual capture, domain binding state, and conversion 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 ### Phase 6 - Secrets

View File

@ -4,14 +4,22 @@
**Goal:** Add a token-based pairing gate to the local browser capture receiver. **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. **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 ## Global Constraints
- Do not move Browser Inbox queues or conversion workflows into desktop core. - 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. - 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. - Use TDD: write the failing Go receiver test first, run it red, then implement.
- Commit and push each affected repository after meaningful changes. - Commit and push each affected repository after meaningful changes.
@ -27,11 +35,11 @@
**Interfaces:** **Interfaces:**
- Produces documented `X-Verstak-Receiver-Token` pairing contract. - 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. Write the design and this implementation plan.
- [ ] **Step 2: Verify docs** - [x] **Step 2: Verify docs**
Run: Run:
@ -42,7 +50,7 @@ git diff --check
Expected: exits 0. Expected: exits 0.
- [ ] **Step 3: Commit and push docs** - [x] **Step 3: Commit and push docs**
Run: Run:
@ -66,11 +74,11 @@ Expected: docs `main` is clean and pushed.
- `type Options struct { RequireToken bool; ReceiverToken string }` - `type Options struct { RequireToken bool; ReceiverToken string }`
- `func NewWithOptions(bus *events.Bus, options Options, providers ...WorkspaceProvider) *Receiver` - `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. Add tests proving missing/wrong token rejection and correct token acceptance.
- [ ] **Step 2: Run RED** - [x] **Step 2: Run RED**
Run: Run:
@ -81,12 +89,12 @@ go test ./internal/core/browserreceiver
Expected: fails because `Options` / `NewWithOptions` do not exist. 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 Add `Options`, `NewWithOptions`, header validation, and constant-time token
comparison. Keep `New` behavior unchanged. comparison. Keep `New` behavior unchanged.
- [ ] **Step 4: Run GREEN** - [x] **Step 4: Run GREEN**
Run: Run:
@ -98,7 +106,7 @@ go test ./internal/core/...
Expected: both commands exit 0. Expected: both commands exit 0.
- [ ] **Step 5: Commit and push desktop** - [x] **Step 5: Commit and push desktop**
Run: Run:
@ -123,7 +131,7 @@ remains unstaged.
- Consumes verified receiver token gate. - Consumes verified receiver token gate.
- Produces docs matching implemented pairing behavior. - 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: 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 - `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: Run:
@ -145,7 +153,7 @@ git push
Expected: extension `main` is clean and pushed. 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 In `05_Official_Plugins.md`, describe that Browser Inbox receives captures
through the local receiver token pairing model. In 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; - [x] define local receiver permission/pairing model;
``` ```
- [ ] **Step 4: Verify and commit docs** - [x] **Step 4: Verify and commit docs**
Run: Run:

View File

@ -2,31 +2,31 @@
## Purpose ## Purpose
The browser extension already sends captures to the desktop local receiver and The browser extension sends captures to the desktop local receiver with
can include `X-Verstak-Receiver-Token`. The desktop receiver currently accepts `X-Verstak-Receiver-Token`. The desktop runtime starts that receiver only in
captures without a pairing model. This slice defines and implements the local paired mode, without moving Browser Inbox queues or conversion behavior into
receiver token gate without moving Browser Inbox behavior into desktop core. desktop core.
## Scope ## Scope
This slice covers only the local receiver permission/pairing model: This slice covers only the local receiver permission/pairing model:
- receiver token validation; - generation and local persistence of a receiver token;
- clear HTTP responses for paired/unpaired requests; - receiver token validation and rotation without a server restart;
- documentation of how the extension presents the token. - 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 It does not implement browser UI for pairing QR codes or encrypted keyring
notes/files/activity, browser UI for pairing QR codes, or encrypted token storage. The current token is kept in the installation-local app settings file,
storage. Those remain later Phase 5 work. which the manager writes with mode `0600`.
## Model ## Model
The receiver has two modes: 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
- **Open legacy mode:** no receiver token is configured. This preserves current `~/.config/verstak/config.json`, and does not log it. If that token cannot be
development behavior and accepts captures without the token header. persisted, the local receiver is disabled rather than opened without a token.
- **Paired mode:** a receiver token is configured and enabled. Every capture Every capture request must include:
request must include:
```text ```text
X-Verstak-Receiver-Token: <token> 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 The receiver compares the supplied token to the configured token using a
constant-time comparison. It does not publish browser capture events when the 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 ## HTTP Contract
@ -76,7 +77,8 @@ Wrong token in paired mode:
Other validation behavior remains unchanged: invalid payloads return `400`, Other validation behavior remains unchanged: invalid payloads return `400`,
missing Browser Inbox consumers return `503`, and non-POST methods return 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 ## Runtime API
@ -93,6 +95,12 @@ func NewWithOptions(bus *events.Bus, options Options, providers ...WorkspaceProv
`New(bus, providers...)` remains the open legacy constructor. `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 ## Testing
`internal/core/browserreceiver/receiver_test.go` must prove: `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 missing tokens with `401`;
- paired receivers reject wrong tokens with `401`; - paired receivers reject wrong tokens with `401`;
- paired receivers accept correct tokens and publish the capture event; - 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.