Refresh checkpoint with CSP and navigation guards

This commit is contained in:
Avi 2026-08-21 13:54:52 -05:00
commit ac614072fe

View file

@ -1,4 +1,4 @@
# Checkpoint — Upload pick tokens (2026-08-21) # Checkpoint — Header-based CSP + navigation guards (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,12 +6,12 @@ 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` @ `6e627a3` ("Replace upload file paths with single-use pick tokens"). - Git repo: `master` @ `4d4dfde` ("Enforce header-based CSP and block window open/navigation").
Before it: `4bde395` ("Restrict renderer IPC to an explicit method allowlist"), then Before it: `6e627a3` (upload pick tokens), `4bde395` (IPC allowlist), then checkpoint
`dafed33` (checkpoint refresh), on top of contact-aware feed (`a1445d1`) etc. commits, on top of contact-aware feed (`a1445d1`) etc.
- Working tree is **clean** apart from this checkpoint update, which is committed right after. - Working tree is **clean** apart from this checkpoint update, which is committed right after.
## What was completed: security hardening items #1 and #2 ## What was completed: security hardening items #1, #2 and #3
Fixes from the 2026-08-21 security audit. Fixes from the 2026-08-21 security audit.
@ -39,8 +39,25 @@ page could read+publish arbitrary local files to nostr.build. Now:
- Fake backend mirrors the contract: `upload_image` without a non-empty `token` throws an - Fake backend mirrors the contract: `upload_image` without a non-empty `token` throws an
`unknown_token` error, so tests exercise the same protocol rule. `unknown_token` error, so tests exercise the same protocol rule.
**#3 Header-based CSP + navigation/window guards (`4d4dfde`):**
- The static CSP meta tag was **removed** from `frontend/index.html` and replaced with
response headers stamped by `main.ts` (`onHeadersReceived`, mainFrame only):
- `CSP_PROD` for `app://` pages: `script-src 'self'`**no `'unsafe-inline'`**, so an
injected `<script>` cannot execute. `connect-src 'self'` (all real network goes through
the backend/main process); img-src keeps `https:` for remote feed/preview images.
- `CSP_DEV` when `NOSTR_GUI_DEV_URL` matches: keeps `'unsafe-inline'` (Vite's React-refresh
preamble is an inline script) but pins `connect-src` to localhost instead of the old
wide-open `ws:` — HMR still works.
- The meta had to go because it would also have blocked the dev preamble; headers let us
ship strict-prod / workable-dev from one HTML file.
- `will-navigate`: any navigation away from app:// or the dev origin is blocked; http(s)
targets open in the system browser via `shell.openExternal`.
- `setWindowOpenHandler`: all `target="_blank"` popups are denied; external links go to the
system browser. Unknown schemes are denied without opening anything.
## Commits ## Commits
- `4d4dfde` "Enforce header-based CSP and block window open/navigation" — main.ts + index.html.
- `6e627a3` "Replace upload file paths with single-use pick tokens" — main.ts, api.ts, - `6e627a3` "Replace upload file paths with single-use pick tokens" — main.ts, api.ts,
types.ts, AppProvider.tsx, ComposeScreen.tsx, fakeBackend.ts (+62/27). types.ts, AppProvider.tsx, ComposeScreen.tsx, fakeBackend.ts (+62/27).
- `4bde395` "Restrict renderer IPC to an explicit method allowlist" — main.ts (+49 lines). - `4bde395` "Restrict renderer IPC to an explicit method allowlist" — main.ts (+49 lines).
@ -65,6 +82,8 @@ Manual protocol checks worth doing once:
- DevTools console: `window.backend.request('not_a_method')``unknown_method` envelope (#1). - DevTools console: `window.backend.request('not_a_method')``unknown_method` envelope (#1).
- DevTools console: `window.backend.request('upload_image', {path:'/etc/passwd'})` - DevTools console: `window.backend.request('upload_image', {path:'/etc/passwd'})`
`unknown_token` error; no file is read (#2). Real attachment flow works unchanged. `unknown_token` error; no file is read (#2). Real attachment flow works unchanged.
- DevTools console: `document.cookie` / injected `<script>` does not run; Application tab
shows the CSP header on the app:// document (#3). Link previews open in the system browser.
## How to resume ## How to resume
@ -78,13 +97,10 @@ Manual protocol checks worth doing once:
Remaining audit items in priority order: Remaining audit items in priority order:
1. **CSP** — drop `'unsafe-inline'` from `script-src` in `frontend/index.html`; add 1. **Legacy vault perms** — repo-root `profiles_vault.json` is group-readable (0664);
`setWindowOpenHandler(() => ({action:'deny'}))` + route external links through
`shell.openExternal` in `main.ts`.
2. **Legacy vault perms** — repo-root `profiles_vault.json` is group-readable (0664);
chmod 600 / delete after confirming migration; auto-tighten during migration in `src/vault.rs`. chmod 600 / delete after confirming migration; auto-tighten during migration in `src/vault.rs`.
3. **Write-race fixes** — create files with mode 0600 at creation time in `vault.rs` 2. **Write-race fixes** — create files with mode 0600 at creation time in `vault.rs`
(`write_restricted`, `backup_file`). (`write_restricted`, `backup_file`).
4. **Zeroize** — wipe decrypted key material via the `zeroize` crate. 3. **Zeroize** — wipe decrypted key material via the `zeroize` crate.
5. Smaller: link-preview SSRF guard (block loopback/private IPs), signer pending-cap, 4. Smaller: link-preview SSRF guard (block loopback/private IPs), signer pending-cap,
NIP-46 secret echo check, backend request timeout. NIP-46 secret echo check, backend request timeout.