An encrypted personal cloud built on the AT Protocol.

Main-thread authenticatedXrpc calls share session with worker #7

Open sans-self.org
Open opened by sans-self.org

Summary #

Several components make authenticated PDS calls via authenticatedXrpc on the main thread, loading session directly from IndexedDB. If a main-thread XRPC call overlaps with a worker PDS call, both read/write the same OAuthSession in IndexedDB — same DPoP nonce field, same last-writer-wins race as the worker-internal issue.

Affected call sites #

High risk (can race with cabinet operations) #

File Function What it does
lib/preview.ts decryptDocumentBlob Authenticated blob fetch for file previews
components/cabinet/DirectoryReadme.tsx fetchAndDecryptReadme Same pattern for README previews
components/cabinet/ShareDialog.tsx handleShare Fetches document record + creates grant
routes/cabinet/shared.tsx fetchGrants Lists outgoing grants via listOutgoingGrants
routes/cabinet/shared.tsx handleRevoke Revokes grant via revokeGrant

Low risk (user-initiated, not concurrent with cabinet) #

File Function What it does
routes/cabinet/settings.tsx save handler Reads/writes account config
lib/pairing.ts all functions Device pairing protocol
stores/auth.ts checkIdentity, publishIdentityKey Identity setup

Root cause #

authenticatedXrpc in lib/api.ts loads session from IndexedDB, creates a DPoP proof, makes the request, and handles token refresh — all on the main thread. The worker's session gate does the same thing independently. Both compete for the same IndexedDB session record.

Proposed fix #

Migrate high-risk call sites to worker PDS methods (most already have WASM equivalents):

  • revokeGrant → worker.grantRevoke() (exists)
  • listOutgoingGrants → worker.listGrantsRaw() + client-side filtering
  • ShareDialog grant creation → worker.documentFetchContentKey() + worker.grantCreate() (both exist)
  • Preview blob fetch → worker.documentFetchContentKey() through gate + unauthenticated getBlob (blobs are public in atproto), or add a WASM export that returns full metadata
  • createPendingShare → needs new WASM export (no equivalent yet)

Low-risk call sites can remain on main thread for now.

Note on blob fetch #

com.atproto.sync.getBlob may or may not require authentication depending on the PDS implementation. The Rust opake-core attaches auth headers to getBlob calls. The sharing.ts incoming grant flow fetches blobs without auth and it works for cross-PDS access. Testing needed to confirm whether the user's own PDS requires auth for getBlob.

Sign up or Sign in to add to the discussion.