CodeQuorum is a real-time collaborative editor that also runs user-supplied code and renders user-supplied HTML/Markdown. That makes its trust boundaries the most important part of its design. This document is the threat model and the controls that address it.
| Boundary | Untrusted input | Risk |
|---|---|---|
| HTTP API | request bodies, cookies | injection, auth bypass, brute force |
| WebSocket | Yjs/awareness binary, room id | unauthorized edits, access to private rooms |
| Code runner | a JS file's contents | arbitrary code execution |
| Live preview | an HTML file's contents | XSS, navigation/network abuse |
| Notebook/Markdown render | markdown from any collaborator | stored XSS |
- Passwords hashed with scrypt (
node:crypto) using a per-user random salt; verified withcrypto.timingSafeEqualto avoid timing leaks. - Sessions are JWTs in an
httpOnly,SameSite=Laxcookie (Securein production). JS never touches the token. JWT_SECRETis required in production — the server refuses to boot with a default secret whenNODE_ENV=production.
- Room roles are resolved on the server (
repo.effectiveRole):owner/editor/viewer/ no-access. - Private rooms reject non-members at the socket layer (
access-denied); the client UI is never trusted to gate this. - Read-only is enforced on the server:
y-updateevents from aviewerare dropped, not merely disabled in the UI.
- All API bodies are validated with zod before use; failures return
400with no DB contact. - Every SQL statement is parameterized (prepared statements in
repo.js) — no string interpolation of user input, so the data layer is injection-safe by construction. - Auth endpoints are rate-limited (
express-rate-limit); the whole API has a global limit.
- helmet sets
Content-Security-Policy,Strict-Transport-Security,X-Content-Type-Options: nosniff,X-Frame-Options: SAMEORIGIN,Referrer-Policy, and removesX-Powered-By. - CSP uses
script-src 'self' 'wasm-unsafe-eval'(no inline app scripts — they are external files;'wasm-unsafe-eval'permits only WebAssembly compilation for the self-hosted Python runtime, not arbitrary JSeval),object-src 'none',base-uri 'self',frame-ancestors 'self'. - The Python runtime (Pyodide) is self-hosted (served from our own origin), so no third-party script/CDN origin is whitelisted in the CSP.
- JavaScript execution (the Run button and notebook cells) runs in a Web Worker — no DOM, separate thread — with a wall-clock timeout that terminates infinite loops. The user's code is embedded as the worker's own script rather than
eval'd, so the app keeps a strictscript-src 'self'CSP with no'unsafe-eval'. - HTML preview is served from a short-lived, same-origin URL (
/preview/:token) inside an<iframe sandbox="allow-scripts">withoutallow-same-origin— so it cannot read cookies, the parent DOM, or storage. That document carries its own locked-down CSP (default-src 'none'), which both lets the user's page run and blocks all network access from the preview (no exfiltration). Console output is relayed to the parent viapostMessage. - Markdown (notebook cells and
.mdpreview) is rendered withmarkedand then sanitized with DOMPurify before insertion, preventing stored XSS from a malicious collaborator.
- The code runner and HTML preview execute arbitrary code in the viewer's browser. They are isolated (Worker / sandboxed iframe) but, for a hardened multi-tenant deployment, previews should additionally be served from a separate origin so CSP and cookie isolation are absolute.
- SQLite suits a single-node deployment. For horizontal scale, move sessions/rooms to Postgres and add a Socket.IO Redis adapter (see
ARCHITECTUREnotes in the README). - No email verification or password reset flow yet.
Found an issue? Please open a private report rather than a public issue.