Follow-up to the map-XSS work. The Capacitor app ships the same dist/ bundle
(webDir: 'dist') and renders the same untrusted relay content, so it shared
the same injection surface — but the CSP only existed as HTTP headers
(public/_headers, netlify.toml), which never reach a WebView loading from a
local scheme. The prior "OS sandbox is enough" comment was wrong: the sandbox
isolates the app from OTHER apps; it does not stop a same-origin injected
script from reading this app's own localStorage (nsec, NWC string, Cashu seed)
or IndexedDB (Cashu bearer ecash proofs, native-only wallet).
Changes:
- index.html: add a <meta http-equiv="Content-Security-Policy"> so the policy
ships inside the bundle and applies to BOTH web and native. script-src stays
'self' 'wasm-unsafe-eval' (no unsafe-inline/eval). Capacitor local origins
(https://localhost, capacitor://localhost, ionic://localhost) are allowlisted
on script/style/img/connect so asset loading & live-reload still work.
Replaces the old "CSP intentionally omitted" comment. Verified the app boots
and the map renders with no CSP violations.
- SECURITY.md: correct the CSP section (now three synced locations incl. the
meta tag; explain the local-scheme reason and the OS-sandbox misconception),
add Cashu ecash/seed to the threat model and residual-risks, note the shared
bundle, and extend the auditor file map (useCashu.ts, platform.ts,
capacitor.config.ts).
Note: the actual injection fix (createElement + textContent in map popups)
already shipped on mobile since it lives in the shared bundle; this commit
adds the defense-in-depth layer that was previously web-only.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Add root SECURITY.md documenting the threat model (plaintext nsec / NWC in
localStorage inherent to client-side Nostr), reporting channels (Nostr DM /
NIP-05), in/out of scope, the CSP and its trade-offs, untrusted-data
rendering rules, URL/CSS sanitization, author-scoped queries, NWC budget
guidance, accepted residual risks, and an auditor file map.
- netlify.toml now serves the same Content-Security-Policy as public/_headers
and uses X-Frame-Options: DENY + Permissions-Policy, so the hardening holds
regardless of which static host serves the web build.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>