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

6.7 KiB
Raw Blame History

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.)