Fix C1/C2: XSS via unsanitized style keys and animation attrs
C1: cssPropsToString emitted the camelToKebab'd style-object KEY unsanitized while only sanitizing the VALUE. A malicious style key containing a quote (reachable via AI update_props or deserialized saved state) could close the style="..." attribute and inject a live element. Now validates each key against a CSS property/custom-prop allowlist and drops anything that doesn't match. C2: buildDataAttrs (html-export.ts) interpolated props.animation and props.animationDelay directly into data-animation="..."/ data-animation-delay="..." with no escaping, for every exported node. Now routes both through escapeAttr. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,11 +43,24 @@ function sanitizeCssValue(raw: string): string {
|
||||
return out;
|
||||
}
|
||||
|
||||
// A real CSS property name (`color`, `background-color`), vendor-prefixed
|
||||
// property (`-webkit-box-shadow`), or custom property (`--custom-prop`) --
|
||||
// nothing else. This is a KEY allowlist, not a value sanitizer: it exists
|
||||
// solely to stop a malicious style object KEY (e.g.
|
||||
// `'"><img src=x onerror=alert(1)>'`, reachable via AI `update_props` or
|
||||
// deserialized saved state, which spread arbitrary keys into `p.style`)
|
||||
// from being emitted unescaped into `style="${camelToKebab(k)}:${...}"` and
|
||||
// closing the attribute early. Legitimate keys never contain `"`, `>`, `<`,
|
||||
// `;`, whitespace, `{`, `}`, or digits-only, so this never rejects real CSS.
|
||||
const VALID_CSS_KEY_RE = /^-{0,2}[a-z][a-z-]*$/;
|
||||
|
||||
export function cssPropsToString(style: CSSProperties | undefined): string {
|
||||
if (!style) return '';
|
||||
return Object.entries(style)
|
||||
.filter(([, v]) => v !== undefined && v !== null && v !== '')
|
||||
.map(([k, v]) => `${camelToKebab(k)}:${typeof v === 'string' ? sanitizeCssValue(v) : v}`)
|
||||
.map(([k, v]) => [camelToKebab(k), v] as const)
|
||||
.filter(([k]) => VALID_CSS_KEY_RE.test(k))
|
||||
.map(([k, v]) => `${k}:${typeof v === 'string' ? sanitizeCssValue(v) : v}`)
|
||||
.join(';');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user