Skip to content

Commit 49bc33f

Browse files
JohnMcLearclaude
andauthored
feat(gdpr): HttpOnly author-token cookie (PR3 of #6701) (#7548)
* docs: PR3 GDPR anonymous identity hardening design spec * docs: PR3 GDPR anon identity implementation plan * feat(gdpr): ensureAuthorTokenCookie helper — HttpOnly server-set author token * feat(gdpr): set HttpOnly author-token cookie from the pad routes * feat(gdpr): read author token from cookie first, keep message.token fallback * feat(gdpr): stop generating the author token client-side * test(gdpr): server sets + reuses the HttpOnly author-token cookie * fix+test(gdpr): parse token cookie from handshake Cookie header 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. * docs(gdpr): token cookie is now HttpOnly + server-set * fix(gdpr): close two HttpOnly token bypasses 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> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9014d3a commit 49bc33f

13 files changed

Lines changed: 1049 additions & 31 deletions

File tree

doc/cookies.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Cookies used by Etherpad.
77
| express_sid | s%3A7yCNjRmTW8ylGQ53I2IhOwYF9... | example.org | / | Session | true | true | Session ID of the [Express web framework](https://expressjs.com). When Etherpad is behind a reverse proxy, and an administrator wants to use session stickiness, he may use this cookie. If you are behind a reverse proxy, please remember to set `trustProxy: true` in `settings.json`. Set in [webaccess.js#L131](https://github.com/ether/etherpad-lite/blob/01497aa399690e44393e91c19917d11d025df71b/src/node/hooks/express/webaccess.js#L131). |
88
| language | en | example.org | / | Session | false | true | The language of the UI (e.g.: `en-GB`, `it`). Set by the pad client when the user changes **My View → Language** (currently in `src/static/js/pad.ts`, via `setMyViewLanguage()`). |
99
| prefs / prefsHttp | %7B%22epThemesExtTheme%22... | example.org | /p | year 3000 | false | true | Client-side preferences (e.g.: font family, chat always visible, show authorship colors, ...). Set in [pad_cookie.js#L49](https://github.com/ether/etherpad-lite/blob/01497aa399690e44393e91c19917d11d025df71b/src/static/js/pad_cookie.js#L49). `prefs` is used if Etherpad is accessed over HTTPS, `prefsHttp` if accessed over HTTP. For more info see https://github.com/ether/etherpad-lite/issues/3179. |
10-
| token | t.tFzkihhhBf4xKEpCK3PU | example.org | / | 60 days | false | true | A random token representing the author, of the form `t.randomstring_of_lenght_20`. The random string is generated by the client, at ([pad.js#L55-L66](https://github.com/ether/etherpad-lite/blob/01497aa399690e44393e91c19917d11d025df71b/src/static/js/pad.js#L55-L66)). This cookie is always set by the client (at [pad.js#L153-L158](https://github.com/ether/etherpad-lite/blob/01497aa399690e44393e91c19917d11d025df71b/src/static/js/pad.js#L153-L158)) without any solicitation from the server. It is used for all the pads accessed via the web UI (not used for the HTTP API). On the server side, its value is accessed at [SecurityManager.js#L33](https://github.com/ether/etherpad-lite/blob/01497aa399690e44393e91c19917d11d025df71b/src/node/db/SecurityManager.js#L33). |
10+
| token | t.tFzkihhhBf4xKEpCK3PU | example.org | / | 60 days | true | true | A random token representing the author, of the form `t.randomstring_of_length_20`. Set by the server as an `HttpOnly; SameSite=Lax` cookie on the first GET to `/p/:pad` (see `src/node/utils/ensureAuthorTokenCookie.ts`). The server reads the cookie from the socket.io handshake in `PadMessageHandler.handleClientReady` to resolve the author. Not readable from browser JavaScript. See [privacy.md](privacy.md). |
1111

1212
For more info, visit the related discussion at https://github.com/ether/etherpad-lite/issues/3563.
1313

0 commit comments

Comments
 (0)