fix(builder): sanitize CSS-value sinks to prevent style/<style> breakout XSS
Adds a single cssValue() sanitizer (src/utils/escape.ts) that strips
<>{};"'\ and neutralizes url(), safe for both style="..." attribute and
<style>...</style> element contexts. Applies it at every raw user-prop
CSS-value interpolation sink found via grep across src/components (colors,
sizes, gaps interpolated directly into style strings/<style> blocks),
including the highest-risk <style>-context sinks: ColumnLayout gap,
Menu/Navbar hover and background colors. Also Number()-coerces the
`columns` grid-template-columns sinks in Gallery/Testimonials/NumberCounter
as defense in depth. Regression tests assert </style><script> payloads are
neutralized and normal colors still render.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,6 +79,30 @@ export function safeUrl(s: string): string {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Neutralizes CSS-context breakout for a single design-token value (color,
|
||||
* length, gradient, etc.) so it is safe to interpolate RAW into either CSS
|
||||
* context found in exported HTML:
|
||||
* - ATTRIBUTE context: `style="color:${v}"` -- a `"` or `'` closes the
|
||||
* attribute early; a `;` injects an extra declaration.
|
||||
* - ELEMENT context: `<style>.x{color:${v}}</style>` -- `<`, `>`, `{`, `}`
|
||||
* let a value close the rule / the `<style>` element itself and open a
|
||||
* `<script>`, which is the worst case (arbitrary script execution).
|
||||
* Strips the characters that can terminate a value, declaration, rule, or
|
||||
* the `<style>` element itself, and neutralizes `url(` so a background
|
||||
* shorthand can't smuggle a `javascript:` URL. None of hex colors,
|
||||
* `rgb()`/`rgba()`, `var(...)`, gradients, `calc(...)`, or `px`/`%`/`em`/
|
||||
* `rem` sizes contain any stripped character, so legitimate values pass
|
||||
* through unchanged. If a sink legitimately needs a background image URL,
|
||||
* route that value through `safeUrl` instead of `cssValue`.
|
||||
*/
|
||||
export function cssValue(v: unknown): string {
|
||||
if (typeof v !== 'string') return '';
|
||||
return v
|
||||
.replace(/[<>{};"'\\]/g, '')
|
||||
.replace(/url\s*\(/gi, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a deterministic, HTML-id-safe slug from a field's `name`/`label`
|
||||
* for wiring `<label for>` to a matching `<input id>` in exported markup.
|
||||
|
||||
Reference in New Issue
Block a user