Docs + a11y: document Cashu wallet seed storage/backup as tracked accepted-risk in SECURITY.md (F1 follow-up); add skip-to-content link and main landmark id (UX U4)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
00482fc332
commit
8e02c77ba1
2 changed files with 49 additions and 3 deletions
42
SECURITY.md
42
SECURITY.md
|
|
@ -247,13 +247,51 @@ oversights:
|
|||
no real protection (the decryption key would live in the same JS context). The
|
||||
meaningful mitigation is preventing script injection (CSP shipped in the bundle
|
||||
so it also covers mobile + no-`innerHTML` discipline) and offering
|
||||
extension/bunker login for users who want keys off the page.
|
||||
extension/bunker login for users who want keys off the page. (The Cashu wallet
|
||||
*seed* is a partial exception — see "Known limitations — Cashu wallet" below.)
|
||||
- **Broad `connect-src`/`img-src`.** Necessary for user-chosen relays, mints,
|
||||
and media hosts. This permits data exfiltration *if* script execution is ever
|
||||
achieved — which is why preventing XSS is the top priority.
|
||||
- **`style-src 'unsafe-inline'`.** Required by the CSS framework; CSS-injection
|
||||
defense is handled by input sanitization rather than CSP.
|
||||
|
||||
### Known limitations — Cashu wallet
|
||||
|
||||
The built-in Cashu ecash wallet is **native-only** (browsers block most mints via
|
||||
CORS). Its BIP-39 mnemonic is the master key to all ecash funds, so it gets
|
||||
stronger handling than other secrets:
|
||||
|
||||
- **Seed storage location (per platform).**
|
||||
- **Native (Android / iOS):** the mnemonic is stored in the OS-backed secure
|
||||
store — Android **KeyStore** / iOS **Keychain** — via
|
||||
`capacitor-secure-storage-plugin` (`src/lib/secureStorage.ts`), **not** in
|
||||
plaintext `localStorage`. Existing users on an older build are migrated
|
||||
transparently on first read (the legacy plaintext `cashu-mnemonic` copy is
|
||||
moved into secure storage and the plaintext removed).
|
||||
- **Web:** falls back to `localStorage` (same accepted-risk posture as `nsec`
|
||||
above). The wallet is effectively native-only in practice, so web plaintext
|
||||
exposure is a minor surface.
|
||||
- Ecash **proofs** (the spendable tokens) still live in IndexedDB
|
||||
(`cashu-wallet`) in plaintext on device — a stolen unlocked device can spend
|
||||
them, exactly like cash. Loss of the seed without a backup = permanent loss.
|
||||
|
||||
- **Backup behavior (Android / native).** A freshly auto-generated seed sets a
|
||||
`needsBackup` flag that forces a **mandatory, non-dismissable backup step**
|
||||
(`RecoveryPhraseDialog` in `requireConfirm` mode): the user must reveal the 12
|
||||
words and explicitly confirm ("I've written it down") before the wallet is
|
||||
usable. This prevents a silently generated seed from being left un-backed-up —
|
||||
clearing app storage without a backup means the funds are unrecoverable. The
|
||||
phrase can be re-viewed any time from the wallet ("View Recovery Phrase").
|
||||
|
||||
- **Planned / status.** Secure-storage migration is **shipped** (native seed now
|
||||
in Keychain/KeyStore). Remaining accepted risk: proofs-at-rest in IndexedDB and
|
||||
the web-only `localStorage` fallback. Encrypting proofs on-device is tracked as
|
||||
a future hardening item but offers limited value while the key must live in the
|
||||
same JS context (same reasoning as `nsec` above).
|
||||
|
||||
Relevant files: `src/hooks/wallet/useCashu.ts`, `src/lib/secureStorage.ts`,
|
||||
`src/components/wallet/RecoveryPhraseDialog.tsx`.
|
||||
|
||||
---
|
||||
|
||||
## For auditors: where to look first
|
||||
|
|
@ -266,7 +304,7 @@ oversights:
|
|||
| Rich text rendering | `src/components/NoteContent.tsx` |
|
||||
| Login / key handling | `src/components/auth/`, Nostrify login (`nostr:login`) |
|
||||
| Lightning / NWC | `src/hooks/useNWC.ts`, `src/components/WalletModal.tsx` |
|
||||
| Cashu ecash (native only): proofs in IndexedDB `cashu-wallet`, seed in `cashu-mnemonic` | `src/hooks/wallet/useCashu.ts`, `src/lib/platform.ts` |
|
||||
| Cashu ecash (native only): proofs in IndexedDB `cashu-wallet`; seed in OS Keychain/KeyStore (secure storage), localStorage fallback on web | `src/hooks/wallet/useCashu.ts`, `src/lib/secureStorage.ts`, `src/lib/platform.ts` |
|
||||
| Admin authority / moderation / takedowns | `src/lib/admins.ts`, `src/hooks/useAdminModeration.ts`, `src/hooks/useDeleteEntity.ts`, `src/components/DeleteEntityDialog.tsx`, `src/components/GearSwapItemCard.tsx` |
|
||||
| CSP & security headers | `index.html` (`<meta>` — covers mobile), `public/_headers`, `netlify.toml`, `public/boot-guard.js` |
|
||||
| Native wrapper (same bundle) | `capacitor.config.ts` (`webDir: 'dist'`) |
|
||||
|
|
|
|||
|
|
@ -484,6 +484,14 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
|||
|
||||
return (
|
||||
<div className="min-h-screen bg-black text-white">
|
||||
{/* Skip-to-content — visually hidden until focused, so keyboard and
|
||||
screen-reader users can jump past the nav on every page. */}
|
||||
<a
|
||||
href="#main"
|
||||
className="sr-only focus:not-sr-only focus:fixed focus:top-3 focus:left-3 focus:z-[100] focus:rounded-lg focus:bg-[#ccff00] focus:px-4 focus:py-2 focus:text-sm focus:font-black focus:uppercase focus:tracking-wide focus:text-black focus:shadow-lg"
|
||||
>
|
||||
Skip to content
|
||||
</a>
|
||||
{/* Desktop Header */}
|
||||
<header className="hidden md:flex sticky top-0 z-50 border-b border-red-500/30 bg-black/95 backdrop-blur">
|
||||
<div className="container flex items-center justify-between h-16 px-4">
|
||||
|
|
@ -967,7 +975,7 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
|||
|
||||
{/* Main Content — extra bottom padding on mobile so the fixed bottom nav never overlaps */}
|
||||
{/* noPadding: full-bleed pages (map) manage their own height */}
|
||||
<main className={noPadding ? undefined : "pb-[calc(5rem+env(safe-area-inset-bottom,0px))] md:pb-6"}>
|
||||
<main id="main" className={noPadding ? undefined : "pb-[calc(5rem+env(safe-area-inset-bottom,0px))] md:pb-6"}>
|
||||
{children}
|
||||
</main>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue