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.