feat(gdpr): HttpOnly author-token cookie (PR3 of #6701)#7548
feat(gdpr): HttpOnly author-token cookie (PR3 of #6701)#7548JohnMcLear wants to merge 10 commits intodevelopfrom
Conversation
socket.io handshake doesn't run cookie-parser, so socket.request.cookies is undefined. Parse the Cookie header directly in handleClientReady so the HttpOnly token actually resolves. Playwright spec covers HttpOnly attribute, reload-stability, and context-isolation.
Review Summary by Qodofeat(gdpr): HttpOnly author-token cookie (PR3 of #6701)
WalkthroughsDescription• Server now mints and sets author-token cookie as HttpOnly; Secure; SameSite=Lax • Browser JavaScript no longer reads, writes, or generates the author token • Socket.io handshake reads cookie first; legacy message.token supported one release • Comprehensive unit, integration, and Playwright tests added for cookie lifecycle Diagramflowchart LR
Client["Browser Client"]
Server["Express Server"]
Socket["Socket.io Handshake"]
DB["Token→Author DB"]
Client -->|GET /p/:pad| Server
Server -->|Set-Cookie: HttpOnly token| Client
Client -->|Socket connect with cookie| Socket
Socket -->|Read cookie from headers| Server
Server -->|Lookup token| DB
DB -->|Return authorID| Server
File Changes1. src/node/utils/ensureAuthorTokenCookie.ts
|
Code Review by Qodo
1. No flag for HttpOnly token
|
|
|
||
|
|
||
| setRouteHandler("/p/:pad", (req: any, res: any, next: Function) => { | ||
| ensureAuthorTokenCookie(req, res, settings); |
There was a problem hiding this comment.
1. No flag for httponly token 📘 Rule violation ☼ Reliability
The new server-minted HttpOnly author-token cookie behavior is enabled unconditionally on pad routes, with no feature flag or default-off toggle. This violates the requirement that new features be gated to reduce rollout risk and allow safe disablement.
Agent Prompt
## Issue description
The new HttpOnly author-token cookie behavior is always enabled, but PR compliance requires new features to be behind a feature flag and disabled by default.
## Issue Context
`ensureAuthorTokenCookie()` is now called on all pad and timeslider routes with no conditional gating, changing behavior immediately for all deployments.
## Fix Focus Areas
- src/node/hooks/express/specialpages.ts[191-191]
- src/node/hooks/express/specialpages.ts[222-222]
- src/node/hooks/express/specialpages.ts[354-354]
- src/node/hooks/express/specialpages.ts[375-375]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Qodo review:
- Timeslider still ran the pre-PR3 JS-cookie path: it read
Cookies.get('${cp}token') (which HttpOnly hides), then generated a
fresh plaintext token and overwrote the server's HttpOnly cookie with
it, and sent token in every socket message. Strip the token read/
write entirely from timeslider.ts and from the outgoing message
shape; the server reads the cookie off the socket.io handshake just
like on /p/:pad.
- tokenTransfer re-issued the author cookie without HttpOnly, undoing
the hardening the first time a user transferred a session. Re-set
it as HttpOnly + Secure (on HTTPS) + SameSite=Lax. Also stop
trusting the body-supplied token on POST: read it off req.cookies
server-side so the client never needs JS access to the token.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
HttpOnly; Secure (on HTTPS); SameSite=Lax(orNonewhen cross-site embedded).tokenfield is dropped from the CLIENT_READY message.handleClientReadyparses the token from the socket.io handshake Cookie header (socket.io doesn't run cookie-parser). Legacymessage.tokenis honoured for one release with a one-time WARN per session.Part of the GDPR work tracked in #6701. PR1 #7546 landed deletion controls; PR2 #7547 landed the IP/privacy audit. Remaining PR4 (cookie banner) and PR5 (author erasure) stay in follow-ups.
Design spec:
docs/superpowers/specs/2026-04-19-gdpr-pr3-anon-identity-design.mdImplementation plan:
docs/superpowers/plans/2026-04-19-gdpr-pr3-anon-identity.mdTest plan
pnpm --filter ep_etherpad-lite run ts-check