fix(configurator): null-safe JSON import, finite-number guards, style-injection hardening - #462
Conversation
…-injection hardening
Bugs surfaced by automated review of SLASHED-Plugins#122 (which vendors this
tree verbatim) that predate that PR but were never caught here since this
repo has no CodeQL/CodeRabbit coverage of its own:
- App.svelte: handleImport() passed any parsed JSON value straight to
setOverrides(), including null/arrays — typeof null === "object", so a
malformed import file crashed the configurator in shallowEq's
Object.keys() call. Restored the data!==null/!Array.isArray/string-value
filtering that AppOverlay.svelte's import flow already had.
- lib/persistence.ts: getNum()'s isNaN() guard let Infinity/-Infinity
through (parseFloat can return both), which fmt() then serialized as the
literal strings "Infinity"/"NaN" into generated CSS. Switched to
Number.isFinite() and added a matching guard in fmt() itself.
- ColorsPanel.svelte: getLightSurface/getDarkSurface/getLightText/
getDarkText read overrides[...] directly instead of going through
sourceValue()'s override→loaded-token→default precedence, so the palette
swatch strips could disagree with the rest of the panel when a source
token was synced/loaded but not overridden. Added sourceByName() and
routed all four through sourceValue().
- EffectsPanel.svelte: the text-shadow input checked v.trim() but passed
the untrimmed v to onSet, so whitespace-padded values were persisted.
- MotionPanel.svelte: staggerBase was $derived to a function instead of a
value ($derived(() => ...) instead of $derived.by(() => ...)), so it
never recomputed when overrides/scale changed.
- SpacingPanel.svelte: the space-scale preview's exponent offset was
off-by-one (i - 4 instead of i - 3) — SPACE_STEPS[3] is "m", the
framework's exponent-0 anchor (core/tokens.css), so every preview bar
rendered one step below its real fluid-scale value.
- ThemesPanel.svelte + the swatch/preview sites across ColorsPanel,
EffectsPanel, TokenRow, ColorInput, and OklchColorDesk: color values
(sourced from overrides, which can come from a saved theme, imported
JSON, or a shared URL hash) were interpolated into raw `style={...}`
template strings. A value like `red; background-image:url(...)` would
inject extra CSS declarations. Switched the single-value cases to
Svelte's `style:background={value}` property directive, which only ever
sets that one property — no declaration-separator injection possible.
svelte-check (0 errors) and vite build both pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQXr34nocCi1jrGcp5TPVm
|
Warning Review limit reached
Next review available in: 35 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughUpdates configurator app behavior: validates and sanitizes JSON theme imports, migrates several swatch background styles to Svelte's style:background directive, reworks color source resolution in ColorsPanel, fixes finiteness checks in persistence parsing/formatting, filters CSS custom-property keys in codec, and corrects minor logic in MotionPanel, SpacingPanel, and EffectsPanel. ChangesConfigurator data handling and UI binding updates
Estimated code review effort🎯 2 (Simple) | ⏱️ ~15 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Re-syncs the configurator now that codeslash-dev/SLASHED#462 fixes the bugs CodeRabbit and Qodo found in this PR's vendored files: - App.svelte: null-safe JSON import (typeof null === "object" crash) - lib/persistence.ts: Number.isFinite guards against Infinity/-Infinity leaking into generated CSS - ColorsPanel.svelte: surface/text helpers now route through sourceValue()'s override precedence instead of reading overrides[...] directly - EffectsPanel.svelte: trim value before onSet - MotionPanel.svelte: staggerBase uses $derived.by so it actually recomputes - SpacingPanel.svelte: off-by-one exponent in the space-scale preview - ThemesPanel.svelte + swatch sites in ColorsPanel/EffectsPanel/ TokenRow/ColorInput/OklchColorDesk: style:background={value} instead of raw style={`background: ${value}`} string interpolation, closing the CSS-declaration-injection path the failing CodeQL check flagged These were pre-existing upstream bugs (not introduced by this PR's sync), fixed in SLASHED#462 since this repo has no CodeQL/CodeRabbit coverage of its own. verify-sync and the full test suite pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQXr34nocCi1jrGcp5TPVm
PR Summary by QodoFix configurator import/persistence edge cases and harden color style bindings
AI Description
Diagram
High-Level Assessment
Files changed (10)
|
Code Review by Qodo
Context used✅ Compliance rules (platform):
6 rules 1.
|
fa() interpolated override keys verbatim into generated CSS
(`${key}: ${da(e[key])};`) while only sanitizing values through da().
Keys reach this function from several untrusted-ish entry points —
imported JSON, the shareable URL hash, localStorage, WP hydration —
none of which validate key shape, so a crafted key could break out of
its CSS declaration in the live-preview <style> tag or exported
stylesheet (e.g. a key like "--sf-x } body{display:none} /*").
Every real token name (source or derived) matches --sf-[\w-]+, so
filter to that pattern at the same choke point da() already sanitizes
values at, rather than chasing every override entry point individually.
svelte-check, vite build, and the full vitest suite (59/59) pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQXr34nocCi1jrGcp5TPVm
Re-syncs after codeslash-dev/SLASHED#462 added a follow-up fix Qodo found on that PR: codec.ts's fa() interpolated override keys verbatim into generated CSS while only sanitizing values, letting a crafted override key (from imported JSON, the URL hash, localStorage, or WP hydration) break out of its CSS declaration in the live-preview <style> tag or exported stylesheet. Keys are now filtered to the --sf-[\w-]+ pattern every real token name matches. verify-sync and the full test suite (67/67) pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQXr34nocCi1jrGcp5TPVm
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@configurator/src/App.svelte`:
- Around line 170-175: The JSON import path in App.svelte is replacing overrides
unconditionally via setOverrides(safe), which can wipe existing settings when
the parsed object is empty or all values are filtered out. Update the import
flow in the same branch that builds safe from Object.entries(data) to only call
setOverrides when safe has at least one key, matching the guard used by the CSS
import path. Keep the logic localized around the JSON parsing/import handling so
empty or fully filtered imports are ignored instead of clearing current
overrides.
In `@configurator/src/components/panels/ColorsPanel.svelte`:
- Around line 353-357: The getDarkSurface source-resolution path is bypassing
the documented precedence chain by reading
overrides["--sf-color-base-source-dark"] directly and skipping loaded token
values. Update getDarkSurface in ColorsPanel.svelte to resolve the dark base
source through the same sourceValue(sourceByName(...)) flow used by
getLightSurface, then only fall back to deriveDarkFromLight when no explicit
override or loaded token exists. Keep the fix aligned with the existing
sourceTokenMap-backed resolution helpers so the auto-dark preview matches the
swatches and inputs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c49b539d-88ba-47b7-85ba-66ae209a4cea
📒 Files selected for processing (11)
configurator/src/App.svelteconfigurator/src/components/inputs/ColorInput.svelteconfigurator/src/components/inputs/OklchColorDesk.svelteconfigurator/src/components/inputs/TokenRow.svelteconfigurator/src/components/panels/ColorsPanel.svelteconfigurator/src/components/panels/EffectsPanel.svelteconfigurator/src/components/panels/MotionPanel.svelteconfigurator/src/components/panels/SpacingPanel.svelteconfigurator/src/components/panels/ThemesPanel.svelteconfigurator/src/lib/codec.tsconfigurator/src/lib/persistence.ts
- App.svelte: handleImport()'s JSON branch called setOverrides(safe) unconditionally, wiping all current overrides if the file had no string-valued keys (e.g. empty object, or all non-string values filtered out). Guard with the same Object.keys(...).length > 0 check the CSS-import branch already uses. - ColorsPanel.svelte: getDarkSurface() read overrides[...] directly instead of going through the documented override -> loaded-token -> default precedence (sourceValue/sourceByName), unlike getLightSurface right above it. A loaded dark token with no explicit override would be skipped in favor of deriving from the light source, so the swatch/preview could disagree with the actual rendered theme. svelte-check, vite build, and the full vitest suite (59/59) pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQXr34nocCi1jrGcp5TPVm
CodeQL flagged handleImport()'s JSON path as js/remote-property-injection: the imported file's keys flowed straight into Object.fromEntries() with only the values filtered by type, so an untrusted JSON file's property names ended up as object keys downstream with no shape validation. Filter keys to the same --sf-[\w-]+ pattern fa() already enforces on emission and AppOverlay.svelte's own import flow used historically, closing the gap at construction time instead of only at output time. svelte-check, vite build, and the full vitest suite (59/59) pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQXr34nocCi1jrGcp5TPVm
Summary
Six bugs surfaced by automated review (CodeRabbit + Qodo) on SLASHED-Plugins#122, which vendors
configurator/src/verbatim. These predate that PR but were never caught here since this repo has no CodeQL/CodeRabbit coverage of its own — they only surfaced once the plugin repo's automated reviewers scanned the freshly-synced files.Changes
App.svelte—handleImport()passed any parsed JSON value straight tosetOverrides(), includingnull/arrays (typeof null === "object"), crashing the configurator inshallowEq'sObject.keys()call on malformed import. Restored thedata !== null && !Array.isArray(data)+ string-value filtering thatAppOverlay.svelte's import flow already had.lib/persistence.ts—getNum()'sisNaN()guard letInfinity/-Infinitythrough (parseFloatcan return both), whichfmt()then serialized as literal"Infinity"/"NaN"strings into generated CSS. Switched toNumber.isFinite()and added a matching guard infmt().ColorsPanel.svelte—getLightSurface/getDarkSurface/getLightText/getDarkTextreadoverrides[...]directly instead of going throughsourceValue()'s override→loaded-token→default precedence, so the palette swatch strips could disagree with the rest of the panel when a source token was synced/loaded but not overridden. AddedsourceByName()and routed all four throughsourceValue().EffectsPanel.svelte— the text-shadow input checkedv.trim()but passed the untrimmedvtoonSet, persisting whitespace-padded values.MotionPanel.svelte—staggerBasewas$derivedto a function instead of a value ($derived(() => ...)instead of$derived.by(() => ...)), so it never recomputed when overrides/scale changed.SpacingPanel.svelte— the space-scale preview's exponent offset was off-by-one (i - 4instead ofi - 3) —SPACE_STEPS[3]is"m", the framework's exponent-0 anchor percore/tokens.css's own comment (--sf-space-m (N=0): 2xs=-3 … 4xl=5), so every preview bar rendered one step below its real fluid-scale value.ThemesPanel.svelte+ swatch sites inColorsPanel,EffectsPanel,TokenRow,ColorInput,OklchColorDesk— color values (sourced fromoverrides, which can come from a saved theme, imported JSON, or a shared URL hash) were interpolated into rawstyle={...}template strings. A value likered; background-image:url(...)would inject extra CSS declarations. Switched the single-value cases to Svelte'sstyle:background={value}property directive, which only ever sets that one property — no declaration-separator injection possible.Test plan
npm run check(svelte-check) — 0 errorsnpm run build(root CSS bundles + docs) — passescd configurator && npm run build(vite build) — passesGenerated by Claude Code
Summary by CodeRabbit