Refresh checkpoint with IPC allowlist hardening
This commit is contained in:
parent
4bde3957b9
commit
dafed3330b
1 changed files with 45 additions and 53 deletions
|
|
@ -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
|
||||
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
|
||||
|
||||
- Project: `/home/avi/Projects/0_Nostr`
|
||||
- Git repo: `master` @ `a1445d1` ("Add contact-aware feed scope").
|
||||
- Working tree is **clean** — this session's contact-aware feed changes and its checkpoint
|
||||
are committed.
|
||||
- On top of that sits the NIP-46 signer work and the original feed aggregation, all committed.
|
||||
- Git repo: `master` @ `4bde395` ("Restrict renderer IPC to an explicit method allowlist").
|
||||
- Working tree is **clean** apart from this checkpoint update, which is committed right after.
|
||||
- Underneath sit the contact-aware feed (`a1445d1`), feed aggregation (`37a9d94`) and the
|
||||
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
|
||||
stream, it fetches the active profile's kind 3 contact list from the enabled relays and returns
|
||||
only recent notes authored by those contacts. This builds on the existing aggregation engine.
|
||||
First fix from the 2026-08-21 security audit. The Electron main process now enforces an
|
||||
explicit allowlist of renderer-callable methods, so a compromised web page inside the app
|
||||
can no longer invent arbitrary backend calls (e.g. unknown/future methods or typos that
|
||||
might alias onto sensitive ones).
|
||||
|
||||
- **Backend** (`src/feed.rs`):
|
||||
- `contact_feed(settings, limit, owner_hex)` fetches the owner's contact pubkeys
|
||||
(`contact_pubkeys`) then aggregates only notes from those authors.
|
||||
- `contact_pubkeys` subscribes to kind 3 (contact list) for the owner, collecting the hex
|
||||
pubkeys of everyone they follow from `TagStandard::PublicKey` tags, de-duplicated.
|
||||
- `aggregate_feed` still serves the global feed and now delegates to a private
|
||||
`aggregate_for(settings, limit, authors)` used by both scopes. When `authors` is set the
|
||||
NIP-01 filter restricts to those authors **and** `FeedBuilder` drops any note from someone
|
||||
else (defence in depth for relays that ignore the `authors` filter).
|
||||
- `FeedBuilder::new(limit, authors)` takes an optional author whitelist.
|
||||
- `owner_pubkey(npub)` decodes a stored `npub1...` (or hex) to a `PublicKey`; shared by the
|
||||
IPC server and the CLI.
|
||||
- **IPC** (`src/ipc.rs`): `FeedGet` now accepts `contacts_only: Option<bool>`. With
|
||||
`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.
|
||||
- **Electron** (`frontend/electron/main.ts`):
|
||||
- New `RENDERER_METHODS` set: the three native methods (`pick_image`, `link_preview`,
|
||||
`upload_image`) plus every Rust-backend method actually used by `frontend/src/lib/api.ts`
|
||||
(init/get_state, profiles, publish, feed, relays, settings, vault password/unlock,
|
||||
reveal_secret_key, signer control/approve).
|
||||
- `isAllowedMethod()` gate at the top of the `backend:request` handler; anything not in
|
||||
the list returns `{status:'error', code:'unknown_method'}` and is logged to stderr —
|
||||
it never reaches the backend process.
|
||||
- No behaviour change for the real UI: `window.backend.request` is only ever called from
|
||||
`api.ts`, whose full surface is covered by the allowlist (confirmed by grep + tests).
|
||||
- Remaining audit items (not yet done): token-based upload path validation (#2), CSP
|
||||
`'unsafe-inline'` removal + window-open deny (#3), legacy-vault permissions (#4),
|
||||
permission race windows (#5), key zeroization (#6).
|
||||
|
||||
## Commits
|
||||
|
||||
- `a1445d1` "Add contact-aware feed scope" — the full feature: `src/feed.rs`
|
||||
(`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".)
|
||||
- `4bde395` "Restrict renderer IPC to an explicit method allowlist" — `main.ts` only (+49 lines).
|
||||
|
||||
## 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 clippy --all-targets # 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 lint # clean (pre-existing module warning only)
|
||||
npm run lint # clean (pre-existing module-type warning only)
|
||||
npm run format:check # clean
|
||||
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/)
|
||||
```
|
||||
|
||||
## How to resume
|
||||
|
||||
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`
|
||||
at the top.
|
||||
3. To try the contact feed:
|
||||
- GUI: open the **Feed** screen and switch **Everyone → My contacts**. You need an active
|
||||
profile that follows people; the segmented control filters the feed to their notes.
|
||||
- CLI: `./target/release/nostr-manager-backend feed --contacts 10` prints up to 10 recent
|
||||
notes from the active profile's contacts.
|
||||
4. Re-run verification with the commands above.
|
||||
2. State is committed: `git status` should be clean; `git log --oneline -3` shows `4bde395`
|
||||
on top.
|
||||
3. Launch as usual: `cd frontend && npm start` (backend already built in `target/release/`).
|
||||
4. To see the gate working: from the DevTools console run
|
||||
`window.backend.request('not_a_method')` → resolves to a `unknown_method` error envelope;
|
||||
normal screens keep working unchanged.
|
||||
5. Re-run verification with the commands above.
|
||||
|
||||
## Outstanding / next steps (if you continue)
|
||||
|
||||
- The contact feed depends on the kind 3 contact list being present on the enabled relays; a
|
||||
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.
|
||||
- A live end-to-end check of the feed (global and contacts) against public relays has not been
|
||||
run in this session; the deterministic parts are covered by unit + frontend tests.
|
||||
- The checkpoint/session discipline in AGENTS.md says to commit the feature first, then the
|
||||
checkpoint update. This was done: `a1445d1` (feature) then this checkpoint commit.
|
||||
Security-audit follow-ups in priority order:
|
||||
|
||||
1. **Upload path tokens** — `upload_image` currently reads any renderer-supplied file path;
|
||||
issue random tokens per picked file and accept only those (`main.ts`).
|
||||
2. **CSP** — drop `'unsafe-inline'` from `script-src` in `frontend/index.html`; add
|
||||
`setWindowOpenHandler` deny + `shell.openExternal` for external links in `main.ts`.
|
||||
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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue