Repository navigation
deploy/cloudflare: Worker + Durable Object template for per-person keyrings (#49, #50, #52) - #57
Conversation
…yrings (#49, #50, #52) One Durable Object per person supervises one container running the unchanged keyring as 'keyring instance --container'. The DO keeps the sealed state (version only moves forward, #49), saves daily usage every minute and before stopping the container and hands it back on unlock, keeps the container up while consent links are open (#50), and the Worker checks X-Keyring-Caller in constant time and removes it before anything else sees the request (#52). Data keys come from a MASTER_KEY secret via HKDF with a per-keyring salt stored in the DO; deleting the DO's storage destroys the keyring. Instance status also reports oauth2 refresh failures per service, for the router's services list. smoke/smoke.ts checks the Worker and DO code against the real binary in Bun. Deploying needs the owner's approval.
Decision: Findings
Tests Run
Raw runtime output was retained in local Gitmoot state and is not posted here. |
…field; one start at a time Review of b6a41be (P1, P2, P3): - a call that read the keyring before a delete could start the deleted keyring's container again (reproduced); wipe now sets a flag before its first await that every start and write checks, and destroys the container even while it is still starting - usage, open links, sealed state and secrets hash each have their own storage key, so an alarm cannot erase a link's keep-alive - running() is single-flight on the start promise alone; a stale monitor no longer clears a newer start; no idle stop while calls are in flight Smoke: race checks for proxy, page and admin calls against a delete in both orders (red on b6a41be), concurrent calls start once (red on b6a41be), alarm vs link, link page on a stopped container; the fake container now turns running asynchronously.
|
Review round 1 (job
Smoke: 33/33 checks pass, 5 runs in a row. Mutants of the new code: 10 of 12 fail the smoke. The two that pass are masked by a second layer of the same defence: (a) only the first of the two |
Decision: Findings
Tests Run
Raw runtime output was retained in local Gitmoot state and is not posted here. |
|
Merge gate (keyring seat): level3_required at 742cbf5. Independent review by g7-review (devin/swe-2), job |
Closes #49, #52; completes #50 (the keyring side merged in #56). Part of #46 (owner decision B1: Cloudflare Containers). Deploying needs the owner's approval; this PR only adds the template.
What
deploy/cloudflare/: one Worker, one Durable Object class (KeyringInstance, one per person), and a container image holding the unchanged keyring binary, run askeyring instance --container. The interface is the one agreed with phobos-dev (gitmoot messages 313693 and 313709), and it is documented indeploy/cloudflare/README.md.X-Keyring-Calleragainst theCALLER_SECRETsecret in constant time (SHA-256 +timingSafeEqual) before any DO is touched, so strangers wake nothing. It then removes the header. The DO removes it again, and the keyring in container mode never forwards it (feat: keyring instance --container (#51) #54). The admin routes also needBearer ADMIN_TOKEN. Public routes: only/connect/<id>/c/<link>and/connect/callback.GET /stateafter every change, and the version only moves forward. The DO never decrypts it. The data key is HKDF(MASTER_KEY, per-keyring random salt, id).DELETEwipes the container andstorage.deleteAll(), so the salt is gone and no copy can be opened again. RepeatingDELETEreturns 200.GET /usageevery minute and before stopping an idle container, and it is passed back on unlock. The container stays up while a consent link or its sign-in may still be in use (openUntil). Link pages are refused while the container is stopped, so a stranger cannot wake it. Audit goes to stdout (Workers logs).public_urlis<PUBLIC_BASE>/connect; the DO rewrites link URLs to/connect/<id>/c/<link>./connect/callbackis a single URL registered with Google, and it finds its keyring through a 10-minute__Host-keyring-instancecookie set when the person presses Connect. The keyring still checks state, PKCE and its own browser cookie.GET /statusaddsfailing: {service: reason}for OAuth refresh failures (feeds the router's services list). Test:TestStatusShowsOAuthRefreshFailure.Verification
go test ./... -race(Go 1.24.13): all ok.npx tsc --noEmit: clean.wrangler deploy --dry-run: builds the image and bundles.deploy/cloudflare/smoke/smoke.ts(Bun): runs the real Worker + DO code against the real keyring binary, with fakes forcloudflare:workersand the container runtime. 24 checks pass: caller and admin auth; create; services list; 503 before connect; link → page → value saved → link reused 410; daily limit 3 → 429; the upstream got the key and never the caller proof; the DO stores only sealed state; the alarm saves usage; after the container and object restart, the limit still holds (429) and the container started twice; role token rotation; Gmail Connect → 303 to the provider with the right redirect_uri and both cookies; callback without the cookie 400; key delete; keyring delete (storage empty, container stopped); delete again 200; call after delete 404.start()pushed client secrets before/create, so creation failed with 409. Fixed.wrangler devwith local containers fails on this host: workerd's egress sidecar times out (kj/timer: overloaded) before it creates the keyring container. Cold start,monitor,signal(15)on idle, and the 1-3 s wake are only verified at a first deploy.Review focus
Auth order in the Worker (nothing reaches a DO without the caller proof), header stripping, key derivation and deletion, the state version moving only forward, the instance cookie (it only routes; the keyring does the auth), the public link routes not being able to wake containers, and races in
running()/ready.