From b2592fc517a1d7da4f6d7769238a814ed9b19aa0 Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Mon, 10 Mar 2025 00:15:17 +0100 Subject: [PATCH 1/3] fix: fix handling of background url (@fehmer) --- frontend/src/ts/controllers/theme-controller.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/ts/controllers/theme-controller.ts b/frontend/src/ts/controllers/theme-controller.ts index 508e2b7cde55..585d5bde61f2 100644 --- a/frontend/src/ts/controllers/theme-controller.ts +++ b/frontend/src/ts/controllers/theme-controller.ts @@ -354,7 +354,9 @@ function applyCustomBackground(): void { $("#words").addClass("noErrorBorder"); $("#resultWordsHistory").addClass("noErrorBorder"); $(".customBackground").html( - `` + `` ); BackgroundFilter.apply(); applyCustomBackgroundSize(); From be5e570485a52c574a9bc6953e0e39797300a8b5 Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Mon, 10 Mar 2025 12:09:08 +0100 Subject: [PATCH 2/3] improve background url validation --- .../src/ts/controllers/theme-controller.ts | 14 +++- .../contracts/__test__/schema/config.spec.ts | 82 +++++++++++++++++++ packages/contracts/src/schemas/configs.ts | 6 +- 3 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 packages/contracts/__test__/schema/config.spec.ts diff --git a/frontend/src/ts/controllers/theme-controller.ts b/frontend/src/ts/controllers/theme-controller.ts index 585d5bde61f2..da722c91be65 100644 --- a/frontend/src/ts/controllers/theme-controller.ts +++ b/frontend/src/ts/controllers/theme-controller.ts @@ -353,11 +353,17 @@ function applyCustomBackground(): void { } else { $("#words").addClass("noErrorBorder"); $("#resultWordsHistory").addClass("noErrorBorder"); - $(".customBackground").html( - `` + + //use setAttribute for possible unsafe customBackground value + const container = document.querySelector(".customBackground"); + const img = document.createElement("img"); + img.setAttribute("src", Config.customBackground); + img.setAttribute( + "onError", + "javascript:window.dispatchEvent(new Event('customBackgroundFailed'))" ); + container?.replaceChildren(img); + BackgroundFilter.apply(); applyCustomBackgroundSize(); } diff --git a/packages/contracts/__test__/schema/config.spec.ts b/packages/contracts/__test__/schema/config.spec.ts new file mode 100644 index 000000000000..2ae1fd4825d0 --- /dev/null +++ b/packages/contracts/__test__/schema/config.spec.ts @@ -0,0 +1,82 @@ +import { CustomBackgroundSchema } from "../../src/schemas/configs"; + +describe("config schema", () => { + describe("CustomBackgroundSchema", () => { + it.for([ + { + name: "http", + input: `http://example.com/path/image.png`, + }, + { + name: "https", + input: `https://example.com/path/image.png`, + }, + { + name: "png", + input: `https://example.com/path/image.png`, + }, + { + name: "gif", + input: `https://example.com/path/image.gif?width=5`, + }, + { + name: "jpeg", + input: `https://example.com/path/image.jpeg`, + }, + { + name: "jpg", + input: `https://example.com/path/image.jpg`, + }, + { + name: "tiff", + input: `https://example.com/path/image.tiff`, + expectedError: "Unsupported image format.", + }, + { + name: "non-url", + input: `test`, + expectedError: "Needs to be an URI.", + }, + { + name: "single quotes", + input: `https://example.com/404.jpg?q=alert('1')`, + expectedError: "May not contain quotes.", + }, + { + name: "double quotes", + input: `https://example.com/404.jpg?q=alert("1")`, + expectedError: "May not contain quotes.", + }, + { + name: "back tick", + input: `https://example.com/404.jpg?q=alert(\`1\`)`, + expectedError: "May not contain quotes.", + }, + { + name: "javascript url", + input: `javascript://alert('asdf')//https://example.com/img.jpg`, + expectedError: "Unsupported protocol.", + }, + { + name: "data url", + input: `data:image/gif;base64,data`, + expectedError: "Unsupported protocol.", + }, + { + name: "long url", + input: `https://example.com/path/image.jpeg?q=${new Array(2048) + .fill("x") + .join()}`, + expectedError: "URL is too long.", + }, + ])(`$name`, ({ input, expectedError }) => { + const parsed = CustomBackgroundSchema.safeParse(input); + if (expectedError !== undefined) { + expect(parsed.success).toEqual(false); + expect(parsed.error?.issues[0]?.message).toEqual(expectedError); + } else { + expect(parsed.success).toEqual(true); + } + }); + }); +}); diff --git a/packages/contracts/src/schemas/configs.ts b/packages/contracts/src/schemas/configs.ts index 0b971daf0fa4..d36e4995b414 100644 --- a/packages/contracts/src/schemas/configs.ts +++ b/packages/contracts/src/schemas/configs.ts @@ -285,7 +285,11 @@ export type MaxLineWidth = z.infer; export const CustomBackgroundSchema = z .string() - .regex(/(https|http):\/\/(www\.|).+\..+\/.+(\.png|\.gif|\.jpeg|\.jpg)/gi) + .url("Needs to be an URI.") + .regex(/^(https|http):\/\/.*/, "Unsupported protocol.") + .regex(/^[^`'"]*$/, "May not contain quotes.") + .regex(/.+(\.png|\.gif|\.jpeg|\.jpg)/gi, "Unsupported image format.") + .max(2048, "URL is too long.") .or(z.literal("")); export type CustomBackground = z.infer; From eb9ad46f78ae0e561e064465b0368e653667836e Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Mon, 10 Mar 2025 19:48:23 +0100 Subject: [PATCH 3/3] test --- packages/contracts/__test__/schema/config.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/__test__/schema/config.spec.ts b/packages/contracts/__test__/schema/config.spec.ts index 2ae1fd4825d0..6e2161513fff 100644 --- a/packages/contracts/__test__/schema/config.spec.ts +++ b/packages/contracts/__test__/schema/config.spec.ts @@ -54,7 +54,7 @@ describe("config schema", () => { }, { name: "javascript url", - input: `javascript://alert('asdf')//https://example.com/img.jpg`, + input: `javascript:alert('asdf');//https://example.com/img.jpg`, expectedError: "Unsupported protocol.", }, {