A WordPress-inspired CMS whose entire backend is PiCloud Rhai scripts deployed with pic apply, plus a SvelteKit frontend. Built to dogfood the project tool, CLI, and SDK; exercises docs/kv/files/users, docs+queue+cron +pubsub triggers, the transactional-outbox notification chain, a docs before-interceptor, set_if CAS, SSE, per-app CORS, env overlays, and a durable Workflow (validate -> enrich || seo -> publish -> finalize) started with workflow::start and polled with workflow::run_status, visualized live with Svelte Flow. FINDINGS.md / SECURITY.md capture every gap, surprise, and security issue found (each tagged [PiCloud] vs [CMS]); the platform fixes for the actionable ones ship in the preceding commit. Also folds in the read-only `pic` allowlist entries added to .claude/settings.json during the session (fewer-permission-prompts). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
37 lines
1.3 KiB
Plaintext
37 lines
1.3 KiB
Plaintext
// comment_sanitize — a docs `before`/`create` INTERCEPTOR.
|
|
// Fires for EVERY docs create in the app (posts, pages, comments), so it must
|
|
// branch on the collection and no-op on everything but comments (F-020).
|
|
// Receives the op-context as its request body: { service, op, collection, key, value }.
|
|
// Returns #{ allowed: true, data: <rewritten value> }; anything else DENIES.
|
|
let p = ctx.request.body;
|
|
if p == () || p.collection != "comments" {
|
|
return #{ allowed: true }; // not a comment — pass through untouched
|
|
}
|
|
|
|
let v = p.value;
|
|
if v == () { return #{ allowed: true }; }
|
|
|
|
// --- sanitize the comment body: escape HTML so it can't inject markup ---
|
|
let raw = v.body;
|
|
let body = if raw == () { "" } else { "" + raw };
|
|
body.replace("&", "&"); // must be first
|
|
body.replace("<", "<");
|
|
body.replace(">", ">");
|
|
body.replace("\"", """);
|
|
body.replace("'", "'");
|
|
// also escape any HTML the display_name might carry
|
|
let an = v.author_name;
|
|
let name = if an == () { "anonymous" } else { "" + an };
|
|
name.replace("&", "&");
|
|
name.replace("<", "<");
|
|
name.replace(">", ">");
|
|
|
|
v.body = body;
|
|
v.author_name = name;
|
|
|
|
// --- moderation status from the site config var ---
|
|
let mode = vars::get("comment-moderation");
|
|
if mode == "auto" { v.status = "approved"; } else { v.status = "pending"; }
|
|
|
|
#{ allowed: true, data: v }
|