feat(demo): refuse a data: URI in any write body (DM25) #229
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/demo-data-uri-lock"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What & why
Uploads are locked hard on the demo (DM4): every
multipart/*request is refused. An image pasted into rich text doesn't use multipart, though. It is saved as adata:image/...;base64,...string inside the JSON body, so a visitor could still store images. The org lead decided (DM25, 2026-10-27): refuse it on every write, for everydata:URI.refuseDataUri(server/src/demo/locks.js) is mounted next torefuseMultipartinapp.js, and only whenDEMO_MODE=true. It walks every string in aPOST/PUT/PATCH/DELETEJSON body (iteratively, so deep nesting can't overflow the stack) and answers the usual403 DEMO_LOCKEDon a match. Nothing is listed, so posts, wiki, events, forum posts, the hero draft, settings and module routes are all covered, including fields added later.What matches:
data:that isn't the end of a longer word, followed by,,;, ortype/subtypeand then a comma. Before testing, the string is read the way a browser reads an attribute: HTML character references are decoded (d,d,:,	,
) and tabs and newlines removed. Sodata:image/...,data:...,da<newline>ta:...,data: image/png;...and CSSurl(data:...)are all refused.metadata: none,Data: none, yetandhttps://x/data:ypass. Reads are never checked.No client change: the editor's save gets the demo sentence through the existing toast (DemoToast) and Android snackbar (DM19).
Note: off the demo,
sanitizeHtmlalready dropsdata:from<img>on news, wiki and forum bodies, but rich-text blocks and other fields only pass through DOMPurify at render, which keeps it. This rule is the one that covers them all on the demo.Docs: RunicGateway/docs#386 (DEMO_MODE.md §3.3 + DM25, BACKEND_DESIGN.md §5.1).
How it was tested
test/demoLocks.test.js: a data: URI refused on five routes across all four write methods, including deep nesting and a JSON-string setting; 12 shapes a browser would load (case, svg,text/html,data:,,data:;base64, a space after the colon, CSSurl(), decimal/hex/named references, a split newline, a tab); prose and URLs that must pass; non-string bodies; a read carrying one in the query.test/demoApp.test.js: through the real app, a pasted image onPOST /admin/postsis refused before its auth check (403, not 401).MODULES_DIRpointed away: 2317 tests, 0 failures.Checklist
AI-assisted contributions (required)
Claude Code (Claude Opus 5.5). I have reviewed and understandevery change, and take responsibility for it. AI-authored commits are
marked with a
Co-Authored-By/Assisted-Bytrailer.License
(GNU GPL v3.0 or later), and I have the right to contribute it.
🤖 Generated with Claude Code
https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY