# Site Builder — security & data-loss hardening (2026-07-12) Fixes the **Critical** (stored-XSS / HTML-breakout) and **High** (state-corruption and save data-loss) findings from the 2026-07-12 Fable audit of the Craft.js site builder (`/workspace/site-builder/craft/`). Medium/Low findings are deferred to the audit backlog (in memory), not this spec. Sequenced **before** the asset-picker work per user decision. Every claim below was verified against the code before writing this spec. ## 1 — Escaping cluster (Critical, do first: closes the most with one change) **Verified root cause:** `escapeHtml` exists once (`utils/html-export.ts:201`) but is **not exported** and no component uses it; there are **27** local `esc`/`escapeHtml` definitions across `src/`, in three incompatible variants (some omit `&`, some omit quotes entirely, none escape `'`), and several land in attribute or JS-string contexts where HTML-entity escaping is wrong anyway. ### 1a — Shared escaping/URL util New `craft/src/utils/escape.ts` (exported, single source of truth): ```ts export function escapeHtml(s: string): string; // & < > " — text/content context export function escapeAttr(s: string): string; // escapeHtml + ' — attribute context export function safeUrl(s: string): string; // returns '' (or '#') for javascript:/data:text/html/vbscript: schemes ``` - Move the correct body from `html-export.ts` into `escape.ts`; `html-export.ts` imports it. Delete all 27 local copies and import from `escape.ts`. - `safeUrl` wraps every exported `href`/`src`/`action`/`url()` value: `ButtonLink`, `Icon`, `SocialLinks`, `Logo`, `Menu`, `Navbar`, `_cta-helpers`, `ContentSlider`, `FeaturesGrid`, `PricingTable`, `ImageBlock`, `VideoBlock`, `Gallery`, background images. Attribute values use `escapeAttr`; text nodes use `escapeHtml`. ### 1b — JS-string / raw-HTML contexts (entity escaping is NOT enough here) - **HtmlBlock** (`basic/HtmlBlock.tsx:144`): `toHtml` returns `props.code` raw while the render sanitizes via the file's own `purifyHtml`. Fix: `return { html: purifyHtml(props.code || '') }`. (DOMPurify already a dep.) - **Countdown** (`sections/Countdown.tsx:298`): `new Date("${targetDate}")` inside an inline `