Refresh checkpoint with IPC allowlist hardening

This commit is contained in:
Avi 2026-08-21 13:31:34 -05:00
commit dafed3330b

View file

@ -1,4 +1,4 @@
# Checkpoint — Contact-aware feed (2026-08-06) # Checkpoint — Renderer IPC method allowlist (2026-08-21)
A stopping point you can return to if this session is closed. Everything below was A stopping point you can return to if this session is closed. Everything below was
verified green at the moment this file was written. verified green at the moment this file was written.
@ -6,46 +6,35 @@ verified green at the moment this file was written.
## Where things are ## Where things are
- Project: `/home/avi/Projects/0_Nostr` - Project: `/home/avi/Projects/0_Nostr`
- Git repo: `master` @ `a1445d1` ("Add contact-aware feed scope"). - Git repo: `master` @ `4bde395` ("Restrict renderer IPC to an explicit method allowlist").
- Working tree is **clean** — this session's contact-aware feed changes and its checkpoint - Working tree is **clean** apart from this checkpoint update, which is committed right after.
are committed. - Underneath sit the contact-aware feed (`a1445d1`), feed aggregation (`37a9d94`) and the
- On top of that sits the NIP-46 signer work and the original feed aggregation, all committed. NIP-46 signer work, all committed.
## What was completed: contact-aware feed ## What was completed: security hardening item #1 (IPC allowlist)
The Feed screen (and CLI) can now be scoped to **My contacts**: instead of showing the global First fix from the 2026-08-21 security audit. The Electron main process now enforces an
stream, it fetches the active profile's kind 3 contact list from the enabled relays and returns explicit allowlist of renderer-callable methods, so a compromised web page inside the app
only recent notes authored by those contacts. This builds on the existing aggregation engine. can no longer invent arbitrary backend calls (e.g. unknown/future methods or typos that
might alias onto sensitive ones).
- **Backend** (`src/feed.rs`): - **Electron** (`frontend/electron/main.ts`):
- `contact_feed(settings, limit, owner_hex)` fetches the owner's contact pubkeys - New `RENDERER_METHODS` set: the three native methods (`pick_image`, `link_preview`,
(`contact_pubkeys`) then aggregates only notes from those authors. `upload_image`) plus every Rust-backend method actually used by `frontend/src/lib/api.ts`
- `contact_pubkeys` subscribes to kind 3 (contact list) for the owner, collecting the hex (init/get_state, profiles, publish, feed, relays, settings, vault password/unlock,
pubkeys of everyone they follow from `TagStandard::PublicKey` tags, de-duplicated. reveal_secret_key, signer control/approve).
- `aggregate_feed` still serves the global feed and now delegates to a private - `isAllowedMethod()` gate at the top of the `backend:request` handler; anything not in
`aggregate_for(settings, limit, authors)` used by both scopes. When `authors` is set the the list returns `{status:'error', code:'unknown_method'}` and is logged to stderr —
NIP-01 filter restricts to those authors **and** `FeedBuilder` drops any note from someone it never reaches the backend process.
else (defence in depth for relays that ignore the `authors` filter). - No behaviour change for the real UI: `window.backend.request` is only ever called from
- `FeedBuilder::new(limit, authors)` takes an optional author whitelist. `api.ts`, whose full surface is covered by the allowlist (confirmed by grep + tests).
- `owner_pubkey(npub)` decodes a stored `npub1...` (or hex) to a `PublicKey`; shared by the - Remaining audit items (not yet done): token-based upload path validation (#2), CSP
IPC server and the CLI. `'unsafe-inline'` removal + window-open deny (#3), legacy-vault permissions (#4),
- **IPC** (`src/ipc.rs`): `FeedGet` now accepts `contacts_only: Option<bool>`. With permission race windows (#5), key zeroization (#6).
`contacts_only: true`, it resolves the active profile's npub, decodes it, and calls
`contact_feed`. Without an active profile it returns a `no_active_profile` error.
- **CLI** (`src/main.rs`): `feed [--contacts] [limit]``--contacts` filters to the active
profile's contacts; the no-results message is adjusted accordingly.
- **Electron/React frontend**: the Feed screen (`FeedScreen.tsx`) gains a segmented **Everyone /
My contacts** scope toggle. "My contacts" is disabled when no profile is active (and silently
forced back to Everyone). Scope-aware empty states ("No notes from your contacts"), and
`feedGet(limit, contactsOnly)` threads the flag from `api.ts` through `AppProvider`.
- **Styles** (`styles.css`): a `.segmented` pill control for the scope toggle.
## Commits ## Commits
- `a1445d1` "Add contact-aware feed scope" — the full feature: `src/feed.rs` - `4bde395` "Restrict renderer IPC to an explicit method allowlist" — `main.ts` only (+49 lines).
(`contact_feed`/`contact_pubkeys`/`aggregate_for` + author whitelist), IPC `FeedGet`
`contacts_only`, CLI `feed --contacts`, the frontend scope toggle + styles + tests, README.
(Parent: `1fd057d` "Refresh checkpoint with feed aggregation".)
## How it was verified (all green) ## How it was verified (all green)
@ -53,33 +42,36 @@ only recent notes authored by those contacts. This builds on the existing aggreg
cargo test # 84 passed cargo test # 84 passed
cargo clippy --all-targets # clean cargo clippy --all-targets # clean
cargo fmt --check # clean cargo fmt --check # clean
cargo build --release # builds (rebuilt so the GUI runs the new backend) cargo build --release # ok
npm run typecheck # clean (frontend/) npm run typecheck # clean (frontend/)
npm run lint # clean (pre-existing module warning only) npm run lint # clean (pre-existing module-type warning only)
npm run format:check # clean npm run format:check # clean
npm test # 78 passed (14 files) npm test # 78 passed (14 files)
npm run electron:build # compiles the Electron main process npm run electron:build # compiles the Electron main process with the new gate
npm run build # rebuilds the React bundle (dist/) npm run build # rebuilds the React bundle (dist/)
``` ```
## How to resume ## How to resume
1. Open the repo: `cd /home/avi/Projects/0_Nostr` 1. Open the repo: `cd /home/avi/Projects/0_Nostr`
2. State is committed: `git status` should be clean; `git log --oneline -5` shows `a1445d1` 2. State is committed: `git status` should be clean; `git log --oneline -3` shows `4bde395`
at the top. on top.
3. To try the contact feed: 3. Launch as usual: `cd frontend && npm start` (backend already built in `target/release/`).
- GUI: open the **Feed** screen and switch **Everyone → My contacts**. You need an active 4. To see the gate working: from the DevTools console run
profile that follows people; the segmented control filters the feed to their notes. `window.backend.request('not_a_method')` → resolves to a `unknown_method` error envelope;
- CLI: `./target/release/nostr-manager-backend feed --contacts 10` prints up to 10 recent normal screens keep working unchanged.
notes from the active profile's contacts. 5. Re-run verification with the commands above.
4. Re-run verification with the commands above.
## Outstanding / next steps (if you continue) ## Outstanding / next steps (if you continue)
- The contact feed depends on the kind 3 contact list being present on the enabled relays; a Security-audit follow-ups in priority order:
profile with no contacts (or a newly created one that has never published a contact list)
produces an empty feed, which is expected but could be surfaced more clearly in the GUI. 1. **Upload path tokens**`upload_image` currently reads any renderer-supplied file path;
- A live end-to-end check of the feed (global and contacts) against public relays has not been issue random tokens per picked file and accept only those (`main.ts`).
run in this session; the deterministic parts are covered by unit + frontend tests. 2. **CSP** — drop `'unsafe-inline'` from `script-src` in `frontend/index.html`; add
- The checkpoint/session discipline in AGENTS.md says to commit the feature first, then the `setWindowOpenHandler` deny + `shell.openExternal` for external links in `main.ts`.
checkpoint update. This was done: `a1445d1` (feature) then this checkpoint commit. 3. **Legacy vault perms** — repo-root `profiles_vault.json` is group-readable (0664);
chmod 600 / delete after confirming migration; also auto-tighten during migration in
`src/vault.rs`.
4. **Write-race fixes** — create files with mode 0600 at creation time in `vault.rs`.
5. **Zeroize** — wipe decrypted key material via the `zeroize` crate.