Records why it must live on its own origin (every navigate.st/bns site shares one origin and could read its keys), the two sign-in keys, the AES-SIV port WebCrypto lacks, and the phases.
5.7 KiB
Pithos in the browser (plan)
Status: plan, 2026-10-05. Nothing built yet.
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://s3.silentmode.st/app, served by the hosted gateway.
- Not on
navigate.st/bns/pithos.sia/app: every BNS site served through navigate.st shares the originnavigate.st, so any other.sia/.bchsite 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/appkeeps 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 tohttps://s3.silentmode.st/app.- Strict CSP (
default-src 'self',connect-src 'self', no inline script,frame-ancestors 'none'), no third-party scripts, files served by the gateway fromPithos/web/(versioned with Pithos).
Signing in
Two kinds of key, both loaded from a file (drag and drop or picker) or pasted:
- 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 pathspithos/hosted-identity/v1andpithos/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 wrapper302e020100300506032b657004220420 || seed) →POST /hosted/v1/session→ Bearer token →GET /api/keys→ the account's S3 key → S3 calls signed in the page.
- Without Theseus: the existing export,
- An S3 key file (the Export JSON format
{endpoint, accessKey, secretKey, bucket}), including folder keys (grant keysPG…) 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.
Optional "Remember on this device": the key file is wrapped with a
passphrase (PBKDF2-SHA256 600k + AES-GCM, as Save credentials… does) in
IndexedDB on the gateway origin. 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'
sivis 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.jsdoes), 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.
Gateway changes (Pithos/hosted)
- Serve
GET /app,/app/*(static files, strict CSP, long cache with versioned names), before the S3/auth routing. - Nothing else: same origin, so no CORS. The S3 path rules stay as they are
(owner keys raw-proxied to s3d; grant keys checked by
rules.js). - Rate limits already in place for sign-in apply.
Phases
- Crypto + SigV4 port with cross-tests (largest risk, done first).
- Gateway
/approute + sign-in with a key file + read-only browsing and downloads. - Uploads, folders, rename/delete.
- Sharing, viewer and player.
- "Key for the browser app" export in Theseus; website button; docs.
Open questions
- Is
s3.silentmode.st/appthe address, or a dedicatedpithos.silentmode.st(same design, another certificate and vhost)? - Should the browser app also sign up new accounts (BNS signature / invite), or only sign in to accounts made in Pithos?
- "Remember on this device": allow it, or always ask for the key file?