Repository navigation
Keep a purged document id gone after a PUT or a reconnect #407
Copy link
Copy link
Closed
Labels
Offline-ModeSecuritySecurity, access control, and data exposureSecurity, access control, and data exposurebugSomething isn't workingSomething isn't working
Milestone
Description
Activity
- added a parent issue
on Oct 6, 2026 - added a commit that references this issue
on 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
- added a commit that references this issue
on Oct 6, 2026 - changed the title
[-][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[/+]on Oct 6, 2026 - addedSecuritySecurity, access control, and data exposureSecurity, access control, and data exposure
on Oct 6, 2026 Two facts the comment above does not record:
- Only the
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. - The WebSocket gate now reads the metadata row and the tombstone in parallel on every connection. Before, it read the tombstone only when no row existed.
Verify the
PUTcase:cd apps/hocuspocus.server && bun test tests/integration/documents.test.ts.
Generated by Claude Code
- Only the
- added 2 commits that reference this issue
on Oct 9, 2026
Metadata
Metadata
Assignees
Labels
Offline-ModeSecuritySecurity, access control, and data exposureSecurity, access control, and data exposurebugSomething isn't workingSomething isn't working
Summary
After a document is purged,
DocumentPurgeTombstoneshould stop it from coming back. ButPUT /api/documents/:docIdcreates 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.apps/hocuspocus.serverWhere
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).apps/hocuspocus.server/src/hocuspocus.server.ts:553-559.apps/hocuspocus.server/src/api/services/documentPurge.service.ts:86-90.Fix plan
updateDocument, when no row exists, look upDocumentPurgeTombstonefor the id. If found, return404and create nothing.onAuthenticate, check the tombstone even when a metadata row exists, and refuse the connection.documentIdSchemaparam validator to the documents routes that take:docIdor:documentId(update, delete, restore, duplicate). This also helps the local-storage path issue.Acceptance criteria
PUT /api/documents/<purgedId>returns404and creates no row.Verify
Add a test under
apps/hocuspocus.server/src/api/services/__tests__/that purges a document, then callsupdateDocumentwith the same id.Generated by Claude Code