diff --git a/05_Official_Plugins.md b/05_Official_Plugins.md index 847dc76..b958dc3 100644 --- a/05_Official_Plugins.md +++ b/05_Official_Plugins.md @@ -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 diff --git a/07_Full_Implementation_Roadmap.md b/07_Full_Implementation_Roadmap.md index b39a1b7..2215af5 100644 --- a/07_Full_Implementation_Roadmap.md +++ b/07_Full_Implementation_Roadmap.md @@ -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 diff --git a/docs/superpowers/plans/2026-06-29-browser-receiver-pairing.md b/docs/superpowers/plans/2026-06-29-browser-receiver-pairing.md index cf6c75b..b84ff7c 100644 --- a/docs/superpowers/plans/2026-06-29-browser-receiver-pairing.md +++ b/docs/superpowers/plans/2026-06-29-browser-receiver-pairing.md @@ -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: ` 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: diff --git a/docs/superpowers/specs/2026-06-29-browser-receiver-pairing-design.md b/docs/superpowers/specs/2026-06-29-browser-receiver-pairing-design.md index 69a8a66..c0807ac 100644 --- a/docs/superpowers/specs/2026-06-29-browser-receiver-pairing-design.md +++ b/docs/superpowers/specs/2026-06-29-browser-receiver-pairing-design.md @@ -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: @@ -34,7 +34,8 @@ X-Verstak-Receiver-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.