-
released this
2026-10-12 08:52:13 +00:00 | 10 commits to main since this releaseFull audit pass (security, API edge cases, frontend, data integrity, load) with fixes.
Fixed — data safety
purgecould destroy a live topic.deleteTopic()now only ever deletes a topic
that is already in the trash, so a strayDELETEcan never wipe live data.- Editing an update discarded the edit when the AI was unreachable (the request
returned 500 and the new text was lost). The edit is now saved first and the analysis
is marked failed, exactly like creating an update. - An update inside a trashed topic could be deleted while the topic was still
recoverable; that path is now refused. /api/restoreaccepted anything and replaced the whole database with it (empty
titles, unknown statuses, non-timestamp dates, orphan updates, duplicate ids). Backup
files are now validated before a single row is touched, and failures return 400 with
invalid_backupinstead of a raw SQLite 500.- NUL characters silently truncated text on write (SQLite stops at
\0). They are
now rejected withinvalid_characters. ownerwas accepted when creating a topic and then silently dropped.
Fixed — security
- Stored XSS:
formatDate()returned non-timestamp input verbatim intoinnerHTML
in three places, andstatusBadge()injected a crafted status unescaped. Both are now
escaped/whitelisted, and restore validation rejects the payload that carried them. /api/modelswas not admin-gated and honoured a caller-supplied?base=, which
forwarded the stored AI key to any host (SSRF + key exfiltration). It is now
administrator-only, together with/api/backupand/api/restore.- Open redirect:
?next=//evil.compassed thestartsWith('/')check. Only
same-origin absolute paths are accepted now. - No session expiry server-side — a 400-day-old session still authenticated. Idle
(7 days) and absolute (30 days) limits are enforced on every request. - Malformed cookies (
%zz) and a JSONnullbody produced 500s; both are handled. - Security headers were missing entirely. Every response now carries a CSP
(frame-ancestors 'none', no inline scripts),nosniff,DENYandReferrer-Policy. - Session cookies get
Securewhen the app is served over HTTPS. - The data directory is
0700and the database0600(they hold session tokens and keys). /api/healthno longer discloses the AI endpoint or model to unauthenticated callers.- Saving an unreachable SimpleAuth URL is refused, so it can no longer lock every
administrator out with no way back through the UI. - Removed the dead
sauth_admin_keysetting.
Fixed — robustness
- No outbound timeouts: a hung AI or identity provider held a request open for
300 s and leaked the socket. All outbound calls now time out (AI 180 s, models 20 s,
SimpleAuth 15 s). - The advertised
413is now delivered instead of resetting the connection. - Field length caps (title 500, description 20 000, owner/author 200, update 50 000) so
one request can no longer store megabytes. ai_baseset to an empty string no longer silently reverts to the DeepSeek default,
and a JSONnullno longer becomes the literal string "null".- AI failures now return
502 ai_unreachableinstead of a generic 500. GET /api/topics/:id/updateswithout a cursor returns the most recent page instead of
an empty list.
Fixed — interface
- "Add update" was broken whenever sign-in was disabled: the client stopped sending
an author but the server still required one. The composer now shows a name field in
that mode only. .hiddenhad no CSS rule, so the admin-only Settings entry stayed visible to members.
Added
- 13 regression tests covering every fix above, plus migration tests for the two older
database schemas (v1 and 2.6-era) proving that an existing installation upgrades with
no data loss.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads