Files
website/server/test/demoApp.test.js
wtclaude 61feca50a8
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 45s
PR Checks / client-build (pull_request) Successful in 1m2s
PR Checks / server-tests (pull_request) Successful in 6m4s
feat(demo): refuse a data: URI in any write body (DM25)
Uploads are locked hard on the demo, but an image pasted into rich text
does not use multipart: it is saved as a data: URI inside the JSON body.
refuseDataUri, mounted beside refuseMultipart, refuses any POST/PUT/PATCH/
DELETE whose body has a data: URI in any string, on any route, core's or
a module's, with the usual 403 DEMO_LOCKED.

Each string is first read the way a browser reads an attribute (HTML
character references decoded, tabs and newlines removed), so data: and
a data: split by a newline are refused too. "metadata:" and prose such as
"Data: none, yet" pass. Reads are never checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-27 13:13:51 -05:00

87 lines
3.3 KiB
JavaScript

// ── The demo lock, wired into the real app (DEMO_MODE.md §3.1) ─────────────
//
// demoLocks.test.js proves each rule against a stand-in app. This file proves
// the wiring: with DEMO_MODE=true, app.js mounts the lock ahead of every
// router, so a locked route is refused before its handler — before even its
// auth check — and an open one still reaches it. One process per flag value,
// since app.js decides at require time: this file is the demo, and every other
// test file that requires app.js is the normal deployment.
process.env.DB_HOST = '127.0.0.1'
process.env.DB_PORT = '59999'
process.env.DEMO_MODE = 'true'
process.env.MODULES_DIR = require('path').join(require('os').tmpdir(), 'rg-demo-app-no-modules')
const { test, after } = require('node:test')
const assert = require('node:assert/strict')
const app = require('../src/app')
const db = require('../src/utils/db')
after(() => db.close())
async function withServer(fn) {
const server = await new Promise((resolve) => {
const s = app.listen(0, '127.0.0.1', () => resolve(s))
})
try {
return await fn(`http://127.0.0.1:${server.address().port}`)
} finally {
server.closeAllConnections()
await new Promise((resolve) => server.close(resolve))
}
}
test('a locked route answers DEMO_LOCKED before its own auth check', async () => {
await withServer(async (base) => {
const res = await fetch(`${base}/api/v1/admin/site-mode`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mode: 'maintenance' }),
})
assert.equal(res.status, 403)
assert.equal((await res.json()).code, 'DEMO_LOCKED')
})
})
test('an open route still reaches its handler, which answers as usual', async () => {
await withServer(async (base) => {
const res = await fetch(`${base}/api/v1/admin/posts`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'x' }),
})
assert.equal(res.status, 401) // requireAuth, not the lock
})
})
test('an upload is refused before multer or auth sees it', async () => {
await withServer(async (base) => {
const form = new FormData()
form.append('image', new Blob(['x'], { type: 'image/png' }), 'x.png')
const res = await fetch(`${base}/api/v1/admin/uploads`, { method: 'POST', body: form })
assert.equal(res.status, 403)
assert.equal((await res.json()).code, 'DEMO_LOCKED')
})
})
test('a pasted data: image is refused before auth sees it, on an open route', async () => {
await withServer(async (base) => {
const res = await fetch(`${base}/api/v1/admin/posts`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'x', body: '<p><img src="data:image/png;base64,iVBORw0KGgo="></p>' }),
})
assert.equal(res.status, 403)
assert.equal((await res.json()).code, 'DEMO_LOCKED')
})
})
test('every response carries noindex, and robots.txt disallows everything', async () => {
await withServer(async (base) => {
const health = await fetch(`${base}/api/health`)
assert.equal(health.headers.get('x-robots-tag'), 'noindex, nofollow')
const robots = await fetch(`${base}/robots.txt`)
assert.equal(await robots.text(), 'User-agent: *\nDisallow: /\n')
})
})