Repository navigation
Keep the access token out of document page props #429
Copy link
Copy link
Closed
Labels
AuthOffline-ModeSecuritySecurity, access control, and data exposureSecurity, access control, and data exposurebugSomething isn't workingSomething isn't working
Milestone
Description
Summary
The document page puts the signed-in user's access token in its page props as accessToken. Next.js writes page props into the HTML (__NEXT_DATA__) and into the /_next/data/...json?slugs= response. The service worker caches both in Cache Storage for 24 hours, and sign-out clears no cache. On a shared device, the next person can read the previous user's JWT until it expires, up to 1 hour later.
- Severity: Low (shared devices only)
- Area: webapp auth, service worker
- Source: security review of 2026-10-06, finding L9
Where
- Props:
apps/webapp/src/utils/documentServerSideProps.ts:77,108(accessToken: session?.access_token ?? null). The comment at:68-70covers only the refresh token. - Consumers of the prop:
apps/webapp/src/pages/[...slugs].tsx:47,87,93. Line 87 uses it only asisAuthed={Boolean(accessToken)}forSlugPageLoader.apps/webapp/src/components/pages/document/DocumentPage.tsx:21,28,44.apps/webapp/src/hooks/useYdocAndProvider.ts:48,55,124. Line 124 falls back to the prop whengetSession()returns no session and no error.
- Cache: next-pwa 5.6.0's
othersrule (NetworkFirst,maxAgeSeconds: 86400) takes every same-origin GET outside/api/. Itsnext-datarule needs the URL to end in.json, so the?slugs=query skips it. - Guard:
apps/webapp/config/pwa/workbox-runtime-caching.js:89-96excludes only requests with atokenorauthorizationheader. Page and data requests carry neither. - Sign-out:
apps/webapp/src/components/settings/hooks/useSignOut.tsclears no cache.
Root cause
The token is in the response body. The cache guard reads only request headers, so it cannot see it.
Fix plan
documentServerSideProps.ts: in the documentprops, replaceaccessTokenwithisAuthed: Boolean(session). In the gateprops, deleteaccessToken; the gate does not read it. Update the comment at:68-70.pages/[...slugs].tsx: passisAuthedtoSlugPageLoader. Stop passing a token toDocumentPage.DocumentPage.tsxanduseYdocAndProvider.ts: remove theaccessTokenprop and argument. In thetokencallback, setaccessToken: data.session?.access_token ?? ''. The browser client reads the same auth cookies (createBrowserClientinapps/webapp/src/utils/supabase/component.ts), sogetSession()already holds the session.workbox-runtime-caching.js:89-91: correct the comment. Say the rule checks request headers only, and that page props must never carry a token.
Layer: the change stays in the page props and their consumers. Do not change the Workbox rules, next.config.js or useSignOut.
Out of scope
- A
NetworkOnlyrule for page and/_next/data/requests. It would stop the offline reload of a visited document. Private metadata in Cache Storage is the same class as the IndexedDB document copy.apps/webapp/CLAUDE.md§Offline mirror records that copy as accepted. - Clearing Cache Storage on sign-out. With no token in the props, it guards nothing that the IndexedDB copy does not already expose.
- Chat drafts and old localStorage keys. Drafts are keyed by workspace and room only (
apps/webapp/src/db/messageComposerDB.ts:58). Four localStorage keys that nothing reads hold the last document id, slug and Pad title (apps/webapp/src/hooks/useDocumentMetadata.ts:15-19). These are separate outcomes; file them on their own if wanted.
Acceptance criteria
- Signed in, a document page's
__NEXT_DATA__holds noaccessTokenand no JWT. - The
/_next/data/...json?slugs=response for a document holds noaccessTokenand no JWT. - In a production build, after visiting documents signed in, no Cache Storage entry contains
accessToken. - A signed-in owner opens their Private document, and the editor connects and syncs as them.
- A signed-in user sees the signed-in slug-page skeleton; a signed-out visitor sees the signed-out one.
- Reloading a visited document while offline still works as before.
Verify
bun run --filter @docs.plus/webapp typecheckandbun run lint.grep -rn "accessToken" apps/webapp/src/pages apps/webapp/src/utils/documentServerSideProps.ts apps/webapp/src/components/pages/documentreturns nothing.- The service worker runs only in a production build (
disable: !isProduction || isE2Einapps/webapp/next.config.js:23). Runbun run --filter @docs.plus/webapp build, thenstart. - Sign in and open two documents, one through a client-side link. In DevTools, open Application, then Cache Storage, then
others. No entry containsaccessToken.
Related
- Bring back push on Chrome and Android devices the server switched off #417 (sign-out already removes the push subscription)
Generated by Claude Code
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
AuthOffline-ModeSecuritySecurity, access control, and data exposureSecurity, access control, and data exposurebugSomething isn't workingSomething isn't working
Activity
[-][Security] The service worker caches the access token and Private metadata after sign-out[/-][+]Keep the access token out of document page props[/+]fix(webapp): keep the token out of page props and clear drafts on sig…
HMarzban commentedon Oct 9, 2026
Fixed and live.
bdce7bd5bbbdaae66c(production run 37976785981, green).isAuthed, never the access token.