pithos/DESIGN-web-app.md
Local Dev ab3568a6fb Pithos browser app plan: pithos.silentmode.st, sign-in only, remember allowed
app.pithos.sia would still be served from the shared navigate.st origin
outside Theseus, so the app gets its own name on silentmode.st.
2026-10-05 02:11:32 +02:00

131 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Pithos in the browser (plan)
Status: plan, 2026-10-05. Nothing built yet. Decided: `https://pithos.silentmode.st`, sign-in only, "Remember on this device" allowed.
## Goal
Open your drives from any browser, on any device, with nothing installed:
browse, upload, download, rename, delete and share files on **Drives on
Silent Mode** (the hosted gateway at `https://s3.silentmode.st`).
Out of scope: a drive on your own computer (`http://127.0.0.1:8000`). s3d
sends no CORS headers, so a web page cannot talk to it; for that the website
hands over to the local Pithos ("Open my Pithos", port 47621).
## Where it lives: its own origin
**`https://pithos.silentmode.st`**: a vhost of its own that serves the app
files and forwards `/hosted/v1/*`, `/api/*` and the S3 paths to the hosted
gateway (127.0.0.1:8720), so the page and the API share one origin.
- Not `app.pithos.sia` either: outside Theseus BNS names are paths of
navigate.st (and BNS folds subdomains into the parent name's folder), so
the origin would still be navigate.st. Per-name origins on navigate.st
(`<name>.navigate.st`, wildcard certificate) would fix that for every BNS
site; a separate project.
- Not on `navigate.st/bns/pithos.sia/app`: every BNS site served through
navigate.st shares the origin `navigate.st`, so any other `.sia`/`.bch` site
could open the app's window (same origin) and read the keys out of it, or
read anything it stored.
- Same origin as the API, so **no CORS** has to be opened on the gateway at
all, and the browser keeps the keys away from every other site.
- `pithos.sia/app` keeps its meaning: in Theseus it is the full app (the
add-on answers it); in any other browser the website's app-address hint
gets a third button, "Open in the browser app", linking to
`https://pithos.silentmode.st`.
- Strict CSP (`default-src 'self'`, `connect-src 'self'`, no inline script,
`frame-ancestors 'none'`), no third-party scripts, files served by the
gateway from `Pithos/web/` (versioned with Pithos).
## Signing in
Two kinds of key, both loaded from a file (drag and drop or picker) or pasted:
1. **Your Pithos key** (owner, full access, encrypted drives readable).
- Without Theseus: the existing export, `pithos-hosted-key.json`
(`{v:1, secret}`), Accounts › On Silent Mode › Export key. From the secret:
identity seed = HKDF(secret, "", "pithos/hosted-identity/v1", 32),
encryption master = HKDF(secret, "", "pithos/hosted-encryption/v1", 32).
- With Theseus there is no file: the keys come from the vault. New export
**"Key for the browser app"** (vault unlocked + Theseus approval prompt):
`{v:2, identitySeed, encryptionKey}`, the two 32-byte values derived from
the vault paths `pithos/hosted-identity/v1` and
`pithos/hosted-encryption/v1`. NEVER change those paths. The file is as
powerful as the vault for these drives; the dialog says so.
- Flow: `GET /hosted/v1/challenge` → Ed25519 signature in WebCrypto
(PKCS8 wrapper `302e020100300506032b657004220420 || seed`) →
`POST /hosted/v1/session` → Bearer token → `GET /api/keys` → the account's
S3 key → S3 calls signed in the page.
2. **An S3 key file** (the Export JSON format `{endpoint, accessKey,
secretKey, bucket}`), including folder keys (grant keys `PG…`) from Share….
No encryption key: plain files and shared folders (stored readable) work,
encrypted names show as "encrypted file". The endpoint must be the
gateway.
Keys live **in memory only** by default; closing the tab signs out.
"Remember on this device" (allowed): the key file is wrapped with a
passphrase (PBKDF2-SHA256 600k + AES-GCM, as Save credentials… does) in
IndexedDB on the pithos.silentmode.st origin; the next visit asks for the
passphrase only. Sign out wipes it and calls
`DELETE` on the session.
## Crypto in the page
Port `core/crypt.js` and the signing part of `core/s3.js` to browser ESM
in `Pithos/web/lib/`, sharing test vectors with the Node versions.
- HKDF-SHA256, AES-256-GCM (bodies), AES-CTR, HMAC-SHA256 (SigV4): WebCrypto.
- AES-SIV (RFC 5297) for names is not in WebCrypto:
- CMAC/S2V from AES-CBC with a zero IV (WebCrypto appends a PKCS#7
block: take the block before it); one-block AES-ECB the same way.
- SIV-CTR with AES-CTR `counter = V'` (bits cleared as crypt.js does),
`length: 128`.
- No library: @noble/ciphers' `siv` is AES-GCM-SIV (RFC 8452), not RFC 5297.
- Tests: RFC 5297 vectors, plus Node-encrypts / browser-decrypts and back for
names (with the bucket + parent-path AD) and bodies (chunk sizes, ranges,
empty file), run under Node's WebCrypto in `npm test`.
## What the app does (v1)
- Drives list, folder browsing (merged encrypted + plain listing, as
`core/hosted.js` does), icons/list views.
- Upload: drag and drop; encrypted chunk by chunk (1 MiB) in the page;
multipart above ~64 MiB so big files never sit in memory whole.
- Download: decrypt as a stream (File System Access API where available,
else a Blob; very large files on browsers without it get a warning).
- Rename, move, delete, new folder (renames re-encrypt, as the desktop does).
- Share: public folder / link / folder key through `/hosted/v1/rules`.
- Viewer for pictures and text, audio/video player using range reads.
- Free space and account state (`/hosted/v1/me`, `/api/space`).
Later: the docked music player, offline cache, PWA install.
## Server changes
- DNS `pithos.silentmode.st` → the VPS; certificate; an Angie vhost that
serves `Pithos/web/` at `/` (strict CSP, versioned file names, long cache)
and proxies `/hosted/v1/`, `/api/` and S3 requests to 127.0.0.1:8720.
- The gateway must accept that Host for SigV4: the page signs requests for
host `pithos.silentmode.st`; owner keys go raw to s3d, which checks the
signature over the Host it receives, so the vhost passes `Host` through
unchanged and `publicUrl`-based checks (challenge message) need the second
name (the challenge text names the URL the client signed in at).
- No CORS: same origin. Path rules unchanged (owner keys raw-proxied to s3d;
grant keys checked by `rules.js`). Sign-in rate limits apply.
## Phases
1. Crypto + SigV4 port with cross-tests (largest risk, done first).
2. Gateway `/app` route + sign-in with a key file + read-only browsing and
downloads.
3. Uploads, folders, rename/delete.
4. Sharing, viewer and player.
5. "Key for the browser app" export in Theseus; website button; docs.
## Open questions
- Decided 2026-10-05: `pithos.silentmode.st`; sign-in only (accounts are
made in Pithos); "Remember on this device" allowed (passphrase-wrapped).
- Still open: does s3d accept SigV4 for a Host other than the one it was set
up with? (It checks the signature over whatever Host arrives, so it should;
verify in phase 2.)