Problem
EmDash renders Portable Text content using Astro's `set:html` directive without sanitization. If an attacker gains access to the CMS (e.g., via a leaked API token), they can inject malicious HTML/JavaScript into content blocks that will be rendered to all visitors.
Components affected (example from a real site):
- `About.astro` - `set:html` with CMS data
- `CtaBanner.astro` - `set:html` with CMS data
- `ValueProps.astro` - `set:html` with CMS data
- Any component using `` or `set:html` with user-managed content
Security impact
This was identified during a security audit. While Portable Text is structured (not raw HTML), the rendering pipeline converts blocks to HTML and injects via `set:html`. If CMS content is compromised:
- Stored XSS affecting all site visitors
- Session hijacking
- Phishing overlays
- Crypto mining scripts
Proposed solution
Add built-in HTML sanitization at the SSR rendering stage:
- PortableText component: sanitize output before `set:html` injection (e.g., using DOMPurify or a lightweight allowlist)
- Configurable allowlist: let site owners define which HTML tags/attributes are permitted
- CSP nonce support: optionally inject nonces for inline scripts that EmDash itself generates
Current workaround
Developers must manually wrap CMS content with a sanitizer:
import DOMPurify from "isomorphic-dompurify";
const clean = DOMPurify.sanitize(htmlFromCMS);
But this requires knowing which components use `set:html` and adds a dependency.
Environment
- EmDash 0.5.0
- Astro 6 with SSR
- Cloudflare Workers deployment
Problem
EmDash renders Portable Text content using Astro's `set:html` directive without sanitization. If an attacker gains access to the CMS (e.g., via a leaked API token), they can inject malicious HTML/JavaScript into content blocks that will be rendered to all visitors.
Components affected (example from a real site):
Security impact
This was identified during a security audit. While Portable Text is structured (not raw HTML), the rendering pipeline converts blocks to HTML and injects via `set:html`. If CMS content is compromised:
Proposed solution
Add built-in HTML sanitization at the SSR rendering stage:
Current workaround
Developers must manually wrap CMS content with a sanitizer:
But this requires knowing which components use `set:html` and adds a dependency.
Environment