Skip to content

Stop any viewer from pushing a fake Pad title to live viewers #406

Description

@HMarzban

Summary

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".
  • Broadcast: apps/hocuspocus.server/src/hocuspocus.server.ts:348-365.
  • 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)

  1. Treat docTitle as a signal only. On receipt, the client refetches the title from REST (GET document metadata) and ignores the payload text.
  2. Keep the relay allowlist as is. No server change is needed.
  3. 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.

Generated by Claude Code

Activity

  1. added theissue type on Oct 6, 2026
  2. added 2 commits that reference this issue on Oct 6, 2026
    6e5a537
    7560222
  3. 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
  4. added
    EditorTiptap & Prosemirror
    SecuritySecurity, access control, and data exposure
    on Oct 6, 2026
  5. HMarzban commented on Oct 6, 2026

    @HMarzban
    CollaboratorAuthor

    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.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    EditorTiptap & ProsemirrorSecuritySecurity, access control, and data exposurebugSomething isn't working

    Type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions