Pull Request Overview
- Opened on September 15, 2026
- Status Merged
- Commit count 15 with first commit July 28, 2026
Total Delta
Open Days
Test Delta
How long has this pull request spent in each phase of its lifecycle?
Data pending calculation for pull request
Local API hardening: stdin secret, gateway downloads, and local-link approval
Two related pieces of local API hardening, plus the local-link approval flow this branch started as.
Local API secret (heart's Part 1)
The account-bootstrap RPCs let any process that reached 127.0.0.1 re-key the wallet and mint a Full-scope token. Main now mints 32 CSPRNG bytes per launch, writes secret <base64url> as the first line on the helper's stdin before it can serve, and the dispatcher attaches local-api-secret to every call β heart requires it on the bootstrap subset and ignores it elsewhere, so one rule is correct everywhere.
Verified against a gate-enabled helper: with the secret, gated calls answer 0 and startup takes 3.2s; without it, UNAUTHENTICATED "missing or invalid local api secret" and a 5s permissive stall.
Web mode drops ANYTYPE_PARENT_LIFELINE instead β a browser page can never hold the parent secret β so its cleanup now also runs on SIGHUP, on normal exit, and when vite exits on its own.
File downloads through the gateway (heart's Part 2)
Bytes travel over the gateway, not the RPC layer, so heart has no process behind a download and emits no Event.Process.*. The renderer owns that progress and feeds the same sidebar rows import/export use: one row per user action, combining its files, cancellable, each file keeping its own name/bytes/state behind a hover list. Transfers are serialized in main β electron-dl attaches a will-download listener per call, so concurrent ones report each other's bytes β which also keeps a batch off the gateway's shared budget while H2 is open.
Three corrections ride along:
- Saving an image fetched a thumbnail. /image/{id}?width=0 is not "no resizing": heart returns the first variant at least that wide, i.e. the smallest. Saving now always uses /file/{id} with ?attachment=1.
- Opening is extension-gated. Clicking a file block downloaded and opened whatever it was β a .tgz left an unpacked tree in Downloads. Only viewable types (documents, images, audio, video) are handed to the system handler; everything else is revealed in the file manager with the file selected.
- FileDownload is no longer used on desktop. It writes only inside heart's temp scope now, so Open file takes the gateway path too (heart's H5 can proceed; mobile keeps the RPC).
Downloading the same file twice reuses the copy already on disk instead of writing doc (1).pdf. Identity is the file object's own checksum: electron/ts/checksum.ts reproduces heart's recipe (sha256 + an encrypted/plain marker byte, base32 hex-alphabet, unpadded), pinned by tests against vectors generated from core/files.checksum. A ledger of where each object last landed makes the repeat case cost one stat; content decides everything else, and a file that does not match is never overwritten.
Local-link approval
Pairing requests and API keys are approved in a main-process queue before a code is minted, with space-scoped grants.
Testing
Unit tests throughout (secret delivery and ordering, download queue and reuse, checksum vectors, ledger, renderer store, the rendererβmain request seam). bun run typecheck and bun run lint clean.
E2E for the download flows is drafted in anytype-desktop-suite (specs/sidebar/file-download-progress.md) but not committed and never run β it needs the Electron fixture, which no test in that suite uses yet.
Still open, heart-side
H2 per-/file/ concurrency budget (the reason transfers are serialized), H4 gateway authentication (now the widest unauthenticated local door), and flipping the secret gate from permissive to fail-closed now that the client ships it.
PR was closed without comments.