Version: emdash 0.29.0 (with @emdash-cms/admin 0.29.0), Cloudflare Workers runtime, D1 with session: "auto", KV object cache.
Summary: the admin page editor can silently overwrite a newer draft revision. Two behaviours combine:
- The editor's content PUTs never send
_rev, so the server-side optimistic concurrency check (validateRev) never engages ("No _rev = blind write" per the comment in the update handler).
- Shortly after a save there is a stale read window in which loading the editor returns the entry WITHOUT its newest draft revision.
ContentRepository.findById / findBySlug / mapRow build item.data from the base content table columns only; the draft revision is hydrated separately, and in our reproductions the editor sometimes rendered base-only content while draft_revision_id pointed at a newer revision in the revisions table.
When 1 and 2 combine, a user who reloads the editor inside the stale window sees old content, edits it, and saves; the save repoints draft_revision_id at a new revision built from the stale state. The newer draft is orphaned with no warning. We reproduced this twice on a fresh 0.29.0 tenant; the revisions table showed the newer draft (585 bytes, custom blocks intact) and then a post-reload save created a smaller revision from base content and repointed the draft.
Steps to reproduce (timing dependent):
- Edit a draft page, add content, save (draft revision written, correct).
- Reload the editor shortly after; if the load falls in the stale window the editor shows content without the fresh draft.
- Edit and save again: the newer draft is silently replaced.
Suggested fixes: have the admin editor echo _rev from the GET into its PUTs so validateRev refuses the stale save; and/or hydrate the draft revision atomically in the same read that loads the entry for the editor.
Workaround we ship: template middleware injects a small script into the admin shell that remembers _rev from content API responses and adds it to PUT bodies that lack one, letting the existing validateRev do its job.
Version: emdash 0.29.0 (with @emdash-cms/admin 0.29.0), Cloudflare Workers runtime, D1 with
session: "auto", KV object cache.Summary: the admin page editor can silently overwrite a newer draft revision. Two behaviours combine:
_rev, so the server-side optimistic concurrency check (validateRev) never engages ("No_rev= blind write" per the comment in the update handler).ContentRepository.findById/findBySlug/mapRowbuilditem.datafrom the base content table columns only; the draft revision is hydrated separately, and in our reproductions the editor sometimes rendered base-only content whiledraft_revision_idpointed at a newer revision in therevisionstable.When 1 and 2 combine, a user who reloads the editor inside the stale window sees old content, edits it, and saves; the save repoints
draft_revision_idat a new revision built from the stale state. The newer draft is orphaned with no warning. We reproduced this twice on a fresh 0.29.0 tenant; the revisions table showed the newer draft (585 bytes, custom blocks intact) and then a post-reload save created a smaller revision from base content and repointed the draft.Steps to reproduce (timing dependent):
Suggested fixes: have the admin editor echo
_revfrom the GET into its PUTs sovalidateRevrefuses the stale save; and/or hydrate the draft revision atomically in the same read that loads the entry for the editor.Workaround we ship: template middleware injects a small script into the admin shell that remembers
_revfrom content API responses and adds it to PUT bodies that lack one, letting the existingvalidateRevdo its job.