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
|
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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue