From 0f9398fce7f5a2d0727940c799cc7a6715e11191 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:46:52 +0000 Subject: [PATCH 1/6] Initial plan From bcfa2ad2c1eecb63ba8ad848ca9a734301460165 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:56:16 +0000 Subject: [PATCH 2/6] Filter out event handlers to prevent CSP violations in @unpic/svelte Co-authored-by: ascorbic <213306+ascorbic@users.noreply.github.com> --- packages/svelte/src/lib/base/image.svelte | 9 ++++++++- packages/svelte/src/lib/base/source.svelte | 9 ++++++++- packages/svelte/src/lib/image.svelte | 9 ++++++++- packages/svelte/src/lib/source.svelte | 9 ++++++++- packages/svelte/test/svelte.test.ts | 18 ++++++++++++++++++ 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/lib/base/image.svelte b/packages/svelte/src/lib/base/image.svelte index 55aa5087..56fa4cdf 100644 --- a/packages/svelte/src/lib/base/image.svelte +++ b/packages/svelte/src/lib/base/image.svelte @@ -24,10 +24,17 @@ .filter(Boolean) .join(";"), ); + + // Filter out event handlers to prevent CSP violations with inline event handlers + const filteredProps = $derived( + Object.fromEntries( + Object.entries(props).filter(([key]) => !key.startsWith("on")) + ) + ); !key.startsWith("on")) + ) + ); - + diff --git a/packages/svelte/src/lib/image.svelte b/packages/svelte/src/lib/image.svelte index 91447fa5..e71bc71d 100644 --- a/packages/svelte/src/lib/image.svelte +++ b/packages/svelte/src/lib/image.svelte @@ -43,10 +43,17 @@ aspectRatio: _aspectRatio, ...rest } = props; + + // Filter out event handlers to prevent CSP violations with inline event handlers + const filteredRest = $derived( + Object.fromEntries( + Object.entries(rest).filter(([key]) => !key.startsWith("on")) + ) + ); !key.startsWith("on")) + ) + ); - + diff --git a/packages/svelte/test/svelte.test.ts b/packages/svelte/test/svelte.test.ts index 03101979..939ae120 100644 --- a/packages/svelte/test/svelte.test.ts +++ b/packages/svelte/test/svelte.test.ts @@ -27,4 +27,22 @@ describe("the Svelte component", () => { expectSourcePropsToMatchTransformed(source, props); }); } + + test("filters out event handlers to prevent CSP violations", () => { + const props = { + src: "https://cdn.shopify.com/static/sample-images/bath_grande_crop_center.jpeg", + layout: "constrained" as const, + width: 800, + height: 600, + alt: "Test image", + onload: "console.log('loaded')", + onerror: "console.log('error')", + }; + render(Image, { props }); + const img = screen.getByAltText("Test image"); + expect(img).toBeTruthy(); + // Verify event handlers are not present as attributes + expect(img.getAttribute("onload")).toBeNull(); + expect(img.getAttribute("onerror")).toBeNull(); + }); }); From 3984b375e4d7bd0593ed53e0c3b19da961917459 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:58:51 +0000 Subject: [PATCH 3/6] Extract event handler filtering to reusable utility function Co-authored-by: ascorbic <213306+ascorbic@users.noreply.github.com> --- packages/svelte/src/lib/base/image.svelte | 7 ++----- packages/svelte/src/lib/base/source.svelte | 7 ++----- packages/svelte/src/lib/filter-event-handlers.ts | 15 +++++++++++++++ packages/svelte/src/lib/image.svelte | 7 ++----- packages/svelte/src/lib/source.svelte | 7 ++----- 5 files changed, 23 insertions(+), 20 deletions(-) create mode 100644 packages/svelte/src/lib/filter-event-handlers.ts diff --git a/packages/svelte/src/lib/base/image.svelte b/packages/svelte/src/lib/base/image.svelte index 56fa4cdf..eadec4c2 100644 --- a/packages/svelte/src/lib/base/image.svelte +++ b/packages/svelte/src/lib/base/image.svelte @@ -2,6 +2,7 @@ import { transformBaseImageProps } from "@unpic/core/base"; import styleToCss from "style-object-to-css-string"; import type { BaseImageProps } from "./types"; + import { filterEventHandlers } from "../filter-event-handlers"; let { style: parentStyle, ...props }: BaseImageProps = $props(); @@ -26,11 +27,7 @@ ); // Filter out event handlers to prevent CSP violations with inline event handlers - const filteredProps = $derived( - Object.fromEntries( - Object.entries(props).filter(([key]) => !key.startsWith("on")) - ) - ); + const filteredProps = $derived(filterEventHandlers(props)); = $props(); @@ -13,11 +14,7 @@ ); // Filter out event handlers to prevent CSP violations with inline event handlers - const filteredProps = $derived( - Object.fromEntries( - Object.entries(transformedProps).filter(([key]) => !key.startsWith("on")) - ) - ); + const filteredProps = $derived(filterEventHandlers(transformedProps)); diff --git a/packages/svelte/src/lib/filter-event-handlers.ts b/packages/svelte/src/lib/filter-event-handlers.ts new file mode 100644 index 00000000..e6bd0c01 --- /dev/null +++ b/packages/svelte/src/lib/filter-event-handlers.ts @@ -0,0 +1,15 @@ +/** + * Filters out event handler properties (those starting with "on") from an object. + * This prevents CSP violations when spreading props onto HTML elements in Svelte 5, + * as Svelte would otherwise convert them to inline event handlers. + * + * @param props - The props object to filter + * @returns A new object with event handlers removed + */ +export function filterEventHandlers>( + props: T, +): Partial { + return Object.fromEntries( + Object.entries(props).filter(([key]) => !key.startsWith("on")), + ) as Partial; +} diff --git a/packages/svelte/src/lib/image.svelte b/packages/svelte/src/lib/image.svelte index e71bc71d..5fb82862 100644 --- a/packages/svelte/src/lib/image.svelte +++ b/packages/svelte/src/lib/image.svelte @@ -3,6 +3,7 @@ import { transformProps } from "@unpic/core"; import styleToCss from "style-object-to-css-string"; import type { ImageProps } from "./types"; + import { filterEventHandlers } from "./filter-event-handlers"; let { style: parentStyle, @@ -45,11 +46,7 @@ } = props; // Filter out event handlers to prevent CSP violations with inline event handlers - const filteredRest = $derived( - Object.fromEntries( - Object.entries(rest).filter(([key]) => !key.startsWith("on")) - ) - ); + const filteredRest = $derived(filterEventHandlers(rest)); !key.startsWith("on")) - ) - ); + const filteredProps = $derived(filterEventHandlers(transformedProps)); From c108a20cf10867f2eb0475291478a0ddbf761f48 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:00:15 +0000 Subject: [PATCH 4/6] Add documentation for Partial return type decision Co-authored-by: ascorbic <213306+ascorbic@users.noreply.github.com> --- packages/svelte/src/lib/filter-event-handlers.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/svelte/src/lib/filter-event-handlers.ts b/packages/svelte/src/lib/filter-event-handlers.ts index e6bd0c01..f207f89a 100644 --- a/packages/svelte/src/lib/filter-event-handlers.ts +++ b/packages/svelte/src/lib/filter-event-handlers.ts @@ -5,6 +5,10 @@ * * @param props - The props object to filter * @returns A new object with event handlers removed + * + * Note: The return type is Partial because we cannot statically determine at compile + * time which keys start with "on". This is a pragmatic choice that provides type safety + * when spreading the result onto HTML elements. */ export function filterEventHandlers>( props: T, From 5c1831157d61415e280cf8c0dd580bbb61cb6ca8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:16:31 +0000 Subject: [PATCH 5/6] Filter only undefined event handlers to preserve user-defined handlers Co-authored-by: ascorbic <213306+ascorbic@users.noreply.github.com> --- packages/svelte/src/lib/base/image.svelte | 16 ++++++++-- packages/svelte/src/lib/base/source.svelte | 16 ++++++++-- .../svelte/src/lib/filter-event-handlers.ts | 19 ------------ packages/svelte/src/lib/image.svelte | 16 ++++++++-- packages/svelte/src/lib/source.svelte | 16 ++++++++-- packages/svelte/test/svelte.test.ts | 31 ++++++++++++++++--- 6 files changed, 79 insertions(+), 35 deletions(-) delete mode 100644 packages/svelte/src/lib/filter-event-handlers.ts diff --git a/packages/svelte/src/lib/base/image.svelte b/packages/svelte/src/lib/base/image.svelte index eadec4c2..fff6fe36 100644 --- a/packages/svelte/src/lib/base/image.svelte +++ b/packages/svelte/src/lib/base/image.svelte @@ -2,7 +2,6 @@ import { transformBaseImageProps } from "@unpic/core/base"; import styleToCss from "style-object-to-css-string"; import type { BaseImageProps } from "./types"; - import { filterEventHandlers } from "../filter-event-handlers"; let { style: parentStyle, ...props }: BaseImageProps = $props(); @@ -26,8 +25,19 @@ .join(";"), ); - // Filter out event handlers to prevent CSP violations with inline event handlers - const filteredProps = $derived(filterEventHandlers(props)); + // Filter out undefined event handlers to prevent CSP violations in Svelte 5 + // When event handler properties are present but undefined in the spread object, + // Svelte 5 adds internal event bindings that violate CSP policies + const filteredProps = $derived( + Object.fromEntries( + Object.entries(props).filter(([key, value]) => { + // Keep non-event-handler properties + if (!key.startsWith("on")) return true; + // Keep event handlers that are explicitly defined by the user + return value !== undefined; + }) + ) + ); = $props(); @@ -13,8 +12,19 @@ }), ); - // Filter out event handlers to prevent CSP violations with inline event handlers - const filteredProps = $derived(filterEventHandlers(transformedProps)); + // Filter out undefined event handlers to prevent CSP violations in Svelte 5 + // When event handler properties are present but undefined in the spread object, + // Svelte 5 adds internal event bindings that violate CSP policies + const filteredProps = $derived( + Object.fromEntries( + Object.entries(transformedProps).filter(([key, value]) => { + // Keep non-event-handler properties + if (!key.startsWith("on")) return true; + // Keep event handlers that are explicitly defined by the user + return value !== undefined; + }) + ) + ); diff --git a/packages/svelte/src/lib/filter-event-handlers.ts b/packages/svelte/src/lib/filter-event-handlers.ts deleted file mode 100644 index f207f89a..00000000 --- a/packages/svelte/src/lib/filter-event-handlers.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Filters out event handler properties (those starting with "on") from an object. - * This prevents CSP violations when spreading props onto HTML elements in Svelte 5, - * as Svelte would otherwise convert them to inline event handlers. - * - * @param props - The props object to filter - * @returns A new object with event handlers removed - * - * Note: The return type is Partial because we cannot statically determine at compile - * time which keys start with "on". This is a pragmatic choice that provides type safety - * when spreading the result onto HTML elements. - */ -export function filterEventHandlers>( - props: T, -): Partial { - return Object.fromEntries( - Object.entries(props).filter(([key]) => !key.startsWith("on")), - ) as Partial; -} diff --git a/packages/svelte/src/lib/image.svelte b/packages/svelte/src/lib/image.svelte index 5fb82862..a41312ea 100644 --- a/packages/svelte/src/lib/image.svelte +++ b/packages/svelte/src/lib/image.svelte @@ -3,7 +3,6 @@ import { transformProps } from "@unpic/core"; import styleToCss from "style-object-to-css-string"; import type { ImageProps } from "./types"; - import { filterEventHandlers } from "./filter-event-handlers"; let { style: parentStyle, @@ -45,8 +44,19 @@ ...rest } = props; - // Filter out event handlers to prevent CSP violations with inline event handlers - const filteredRest = $derived(filterEventHandlers(rest)); + // Filter out undefined event handlers to prevent CSP violations in Svelte 5 + // When event handler properties are present but undefined in the spread object, + // Svelte 5 adds internal event bindings that violate CSP policies + const filteredRest = $derived( + Object.fromEntries( + Object.entries(rest).filter(([key, value]) => { + // Keep non-event-handler properties + if (!key.startsWith("on")) return true; + // Keep event handlers that are explicitly defined by the user + return value !== undefined; + }) + ) + ); { + // Keep non-event-handler properties + if (!key.startsWith("on")) return true; + // Keep event handlers that are explicitly defined by the user + return value !== undefined; + }) + ) + ); diff --git a/packages/svelte/test/svelte.test.ts b/packages/svelte/test/svelte.test.ts index 939ae120..24eed4c0 100644 --- a/packages/svelte/test/svelte.test.ts +++ b/packages/svelte/test/svelte.test.ts @@ -28,21 +28,44 @@ describe("the Svelte component", () => { }); } - test("filters out event handlers to prevent CSP violations", () => { + test("filters out undefined event handlers to prevent CSP violations", () => { const props = { src: "https://cdn.shopify.com/static/sample-images/bath_grande_crop_center.jpeg", layout: "constrained" as const, width: 800, height: 600, alt: "Test image", - onload: "console.log('loaded')", - onerror: "console.log('error')", + // Simulating the case where TypeScript types include these but they're undefined + onload: undefined, + onerror: undefined, }; render(Image, { props }); const img = screen.getByAltText("Test image"); expect(img).toBeTruthy(); - // Verify event handlers are not present as attributes + // Verify undefined event handlers are not present as attributes expect(img.getAttribute("onload")).toBeNull(); expect(img.getAttribute("onerror")).toBeNull(); }); + + test("preserves explicitly defined event handlers", () => { + const onloadHandler = () => console.log("loaded"); + const onerrorHandler = () => console.log("error"); + const props = { + src: "https://cdn.shopify.com/static/sample-images/bath_grande_crop_center.jpeg", + layout: "constrained" as const, + width: 800, + height: 600, + alt: "Test image with handlers", + onload: onloadHandler, + onerror: onerrorHandler, + }; + render(Image, { props }); + const img = screen.getByAltText( + "Test image with handlers", + ); + expect(img).toBeTruthy(); + // Verify explicit event handlers are present + // In Svelte 5, these will be bound using Svelte's event system + // We can't directly check the handler functions, but we verify the component renders without error + }); }); From ee5d152a51b4b733772788424c17e6029dd1c8d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:24:55 +0000 Subject: [PATCH 6/6] Add changeset for CSP fix Co-authored-by: ascorbic <213306+ascorbic@users.noreply.github.com> --- .changeset/fix-svelte-csp-violations.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-svelte-csp-violations.md diff --git a/.changeset/fix-svelte-csp-violations.md b/.changeset/fix-svelte-csp-violations.md new file mode 100644 index 00000000..3670e1d1 --- /dev/null +++ b/.changeset/fix-svelte-csp-violations.md @@ -0,0 +1,5 @@ +--- +"@unpic/svelte": patch +--- + +Fix CSP violations by filtering undefined event handlers in Svelte 5. When event handler properties like `onload` and `onerror` are present but undefined in spread props, Svelte 5 adds internal event bindings that trigger CSP violations. The fix filters out only undefined event handlers while preserving explicitly passed handlers.