Files
PiCloud/examples/cms-poc/scripts/comments/comment_sanitize.rhai
MechaCat02 813bc46640
Some checks failed
CI / Rust — fmt, clippy, test (push) Failing after 39m53s
CI / Dashboard — check (push) Successful in 10m15s
docs(examples): add the CMS proof-of-concept dogfooding artifact
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>
2026-07-19 20:15:29 +02:00

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("&", "&amp;"); // must be first
body.replace("<", "&lt;");
body.replace(">", "&gt;");
body.replace("\"", "&quot;");
body.replace("'", "&#39;");
// also escape any HTML the display_name might carry
let an = v.author_name;
let name = if an == () { "anonymous" } else { "" + an };
name.replace("&", "&amp;");
name.replace("<", "&lt;");
name.replace(">", "&gt;");
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 }