You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Stop any viewer from pushing a fake Pad title to live viewers #406
The WebSocket stateless relay forwards a docTitle message from any connection to every client on the document, on every replica. It checks only the message type and size. So a signed-out or Read-only viewer can show a fake Pad title to everyone who is viewing an owned document. An example is "Session expired — sign in at ". The fake title is not saved. It lasts until reload.
Severity: Low
Area:apps/hocuspocus.server, apps/webapp
Source: security review of 2026-10-06 (found by three reviewers)
Where
Relay allowlist: apps/hocuspocus.server/src/extensions/statelessRelay.ts:13; decider at :25-39. Its comment says "The relay has no authz".
Client checks only the shape: apps/webapp/src/utils/titleWrite.ts:3-11.
Client applies it: apps/webapp/src/components/TipTap/DocTitle.tsx:113-117 and MobilePadTitle.tsx:268-273, into settings.metadata.title.
The REST rename refuses a non-owner (apps/hocuspocus.server/src/api/services/documents.service.ts:555-557), so the relay is the weaker path.
Constraint
The Open document rule (CONTEXT.md §Document access): a document with no ownerId may be retitled by anyone. A server-side sender check must keep that rule. The connection context holds no ownerId today.
Fix plan (recommended)
Treat docTitle as a signal only. On receipt, the client refetches the title from REST (GET document metadata) and ignores the payload text.
Keep the relay allowlist as is. No server change is needed.
Keep the Title write ruling in root CLAUDE.md §Settled: the open-pad relay still runs once in useUpdateDocMetadata's onSuccess.
Alternative: relay only when the sender may edit metadata (owner, or any user when ownerId is null). This needs ownerId in the connection context.
Acceptance criteria
A stateless {"type":"docTitle","state":{"title":"fake"}} from a Read-only connection does not change the title any other client shows.
A real rename by the owner still updates all open clients at once.
A rename on an ownerless document by any user still updates all clients.
changed the title [-][Security] Any viewer can push a fake Pad title to every live viewer through the docTitle relay[/-][+]Stop any viewer from pushing a fake Pad title to live viewers[/+]on Oct 6, 2026
Status: fixed in 6e5a537 on claude/youthful-lovelace-3nvsc6, with the recommended plan. Not merged or deployed yet.
What shipped:
apps/webapp/src/utils/titleWrite.ts: onDocTitleStateless replaces parseDocTitlePayload. On a docTitle message it calls fetchDocument and applies the REST title. It never applies the payload text. It drops the result when the user has moved to another pad.
One fetch runs at a time. Messages that arrive during a fetch cause one more fetch, 2 s later (REFETCH_GAP_MS). So a stream of forged messages costs each viewer at most one GET every 2 s.
DocTitle.tsx and MobilePadTitle.tsx now call onDocTitleStateless. The relay, the server and the sender in useUpdateDocMetadata are unchanged.
Jest spec: apps/webapp/src/utils/titleWrite.test.ts. Root CLAUDE.md §Settled (Title write) and apps/hocuspocus.server/CLAUDE.md record the rule.
Behaviour change: a rename reaches other viewers after one REST round trip, not at once.
Verify: cd apps/webapp && bun run test -- src/utils/titleWrite.test.ts.
Check after deploy: acceptance criteria 2 and 3 need two browsers. Rename an owned document as its owner, then rename an ownerless document as another user.
Activity
fix(webapp): treat the docTitle relay as a refetch signal
docs: record the security and push fixes in changelogs and agent docs
[-][Security] Any viewer can push a fake Pad title to every live viewer through the docTitle relay[/-][+]Stop any viewer from pushing a fake Pad title to live viewers[/+]HMarzban commentedon Oct 6, 2026
Status: fixed in
6e5a537onclaude/youthful-lovelace-3nvsc6, with the recommended plan. Not merged or deployed yet.What shipped:
apps/webapp/src/utils/titleWrite.ts:onDocTitleStatelessreplacesparseDocTitlePayload. On adocTitlemessage it callsfetchDocumentand applies the REST title. It never applies the payload text. It drops the result when the user has moved to another pad.REFETCH_GAP_MS). So a stream of forged messages costs each viewer at most one GET every 2 s.DocTitle.tsxandMobilePadTitle.tsxnow callonDocTitleStateless. The relay, the server and the sender inuseUpdateDocMetadataare unchanged.apps/webapp/src/utils/titleWrite.test.ts. RootCLAUDE.md§Settled (Title write) andapps/hocuspocus.server/CLAUDE.mdrecord the rule.Behaviour change: a rename reaches other viewers after one REST round trip, not at once.
Verify:
cd apps/webapp && bun run test -- src/utils/titleWrite.test.ts.Check after deploy: acceptance criteria 2 and 3 need two browsers. Rename an owned document as its owner, then rename an ownerless document as another user.
Generated by Claude Code
chore(merge): land the security and push fixes branch
fix: close follow-ups from the security and push review
refactor(webapp): route docTitle through the provider and trim push code