pithos/DESIGN-web-app.md
Local Dev b8ec745a43 Pithos in the browser: Drives on Silent Mode with a key file
A page served by the gateway that signs in with the Pithos key file
(Ed25519 in Web Crypto) or an Export JSON S3 key, then browses, views,
downloads, uploads, renames and deletes, encrypting and decrypting in the
page with the same formats as the desktop. Checked against a local
gateway and real s3d: files the desktop encrypted open byte-identical in
the browser, and browser uploads open in the desktop client; the server
only ever holds encrypted names. Remember on this device keeps the key
sealed with a passphrase in IndexedDB.
2026-10-05 02:25:02 +02:00

6.9 KiB
Raw Blame History

Pithos in the browser (plan)

Status: 2026-10-05. Phases 1-3 built and tested locally (crypto port, gateway / + /_app/ route, sign-in with a key file, browse, view, download, upload, new folder, rename, delete, remember). Not deployed: needs DNS for pithos.silentmode.st. 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.)