Repository navigation
Closed
Labels
Offline-ModeSecuritySecurity, access control, and data exposureSecurity, access control, and data exposurebugSomething isn't workingSomething isn't working
Milestone
Description
Summary
After a document is purged, DocumentPurgeTombstone should stop it from coming back. But PUT /api/documents/:docId creates a metadata row for any id and never reads the tombstone. Once a row exists, the WebSocket gate also skips the tombstone. A stale browser tab that was offline during the purge can then reconnect and upload its local copy, so the erased content is stored again. No attacker is needed; a stale tab's title edit is enough.
- Severity: Low
- Area:
apps/hocuspocus.server - Source: security review of 2026-10-06
Where
- Route:
apps/hocuspocus.server/src/api/routers/documents.router.ts:57-62(optionalUser, so anonymous is allowed). No:docIdparam schema. updateDocumentchecks onlydeletedAt(apps/hocuspocus.server/src/api/services/documents.service.ts:545-549), then upserts any id (:603-615).- WebSocket gate reads the tombstone only when no row exists:
apps/hocuspocus.server/src/hocuspocus.server.ts:553-559. - What the tombstone is for:
apps/hocuspocus.server/src/api/services/documentPurge.service.ts:86-90.
Fix plan
- In
updateDocument, when no row exists, look upDocumentPurgeTombstonefor the id. If found, return404and create nothing. - In the WebSocket
onAuthenticate, check the tombstone even when a metadata row exists, and refuse the connection. - Add a
documentIdSchemaparam validator to the documents routes that take:docIdor:documentId(update, delete, restore, duplicate). This also helps the local-storage path issue. - Before you enforce the id format, count existing rows that do not match it, so you do not lock out real documents.
Acceptance criteria
- After a purge,
PUT /api/documents/<purgedId>returns404and creates no row. - After a purge, a WebSocket connection to that room is refused, even if a row was created earlier.
- Creating and updating normal documents still works, for both signed-in and signed-out users.
Verify
Add a test under apps/hocuspocus.server/src/api/services/__tests__/ that purges a document, then calls updateDocument with the same id.
Generated by Claude Code
Metadata
Metadata
Assignees
Labels
Offline-ModeSecuritySecurity, access control, and data exposureSecurity, access control, and data exposurebugSomething isn't workingSomething isn't working
Activity
fix(hocuspocus): keep purged document ids gone
HMarzban commentedon Oct 6, 2026
Fixed in
02f7530:updateDocumentreturns 404 for a purged id with no row, and the WebSocket gate reads the tombstone even when a row exists. A regression case was added totests/integration/documents.test.ts.Not done on purpose: the
documentIdSchemaparam validator. Production hocuspocus rows were not checked, so older ids that do not match the format cannot be ruled out. Enforcing it could lock out real documents. The tombstone check closes this issue on its own.Generated by Claude Code
docs: record the security and push fixes in changelogs and agent docs
[-][Security] PUT /api/documents/:docId can bring back a purged document id and bypass the purge tombstone[/-][+]Keep a purged document id gone after a PUT or a reconnect[/+]HMarzban commentedon Oct 6, 2026
Two facts the comment above does not record:
PUTpath has a test. The WebSocket gate change (acceptance criterion 2) has no automated test. Check it by hand after deploy: purge a test document, then reconnect a tab that was offline during the purge. The server must refuse the connection.Verify the
PUTcase:cd apps/hocuspocus.server && bun test tests/integration/documents.test.ts.Generated by Claude Code
chore(merge): land the security and push fixes branch
refactor(hocuspocus): tidy the push, SSRF, error and admin-log follow…