From bd7b95f9f3b56a85f124b30ecfb59ce37a12004a Mon Sep 17 00:00:00 2001 From: ArianAr Date: Wed, 15 Jul 2026 21:48:17 +0300 Subject: [PATCH] fix(security): use static theme FOUC boot script CodeQL js/bad-code-sanitization flagged constructing THEME_BOOT_SCRIPT via JSON.stringify interpolation. Embed the storage key as a literal instead; test keeps the key and boot script in sync. Addresses code scanning alert #12. --- CHANGELOG.md | 4 ++++ server/lib/theme.test.ts | 7 +++++-- server/lib/theme.ts | 9 +++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bae99b..c5c0331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Opt-in process metrics (`PAPERCUT_METRICS=1`) — `GET /api/metrics` counters only (`pastes_created`, `unlocks_ok`, `rate_limited`); disabled by default; no content/IP logging - Log canvas **wrap / no-wrap** toggle (dense horizontal scroll), sticky line-number gutter, preference in `localStorage` +### Fixed + +- Theme FOUC boot script is a static string (no `JSON.stringify` interpolation) to clear CodeQL `js/bad-code-sanitization` + ### Planned See [ROADMAP.md](./ROADMAP.md) (dependency updates, canvas tools, scale features, etc.). diff --git a/server/lib/theme.test.ts b/server/lib/theme.test.ts index 61f26be..7b253d4 100644 --- a/server/lib/theme.test.ts +++ b/server/lib/theme.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { isTheme, THEME_STORAGE_KEY } from "./theme"; +import { isTheme, THEME_BOOT_SCRIPT, THEME_STORAGE_KEY } from "./theme"; describe("theme helpers", () => { it("accepts only dark and light", () => { @@ -10,7 +10,10 @@ describe("theme helpers", () => { expect(isTheme(null)).toBe(false); }); - it("uses a stable storage key", () => { + it("uses a stable storage key shared by the FOUC boot script", () => { expect(THEME_STORAGE_KEY).toBe("papercut-theme"); + // Boot script embeds the key as a static literal (no code construction). + expect(THEME_BOOT_SCRIPT).toContain(`localStorage.getItem("${THEME_STORAGE_KEY}")`); + expect(THEME_BOOT_SCRIPT).toContain('setAttribute("data-theme"'); }); }); diff --git a/server/lib/theme.ts b/server/lib/theme.ts index 7607af9..5b7b649 100644 --- a/server/lib/theme.ts +++ b/server/lib/theme.ts @@ -33,5 +33,10 @@ export function resolveInitialTheme(): Theme { return readStoredTheme() ?? "dark"; } -/** Inline script: set data-theme before paint (no FOUC). */ -export const THEME_BOOT_SCRIPT = `(function(){try{var k=${JSON.stringify(THEME_STORAGE_KEY)};var t=localStorage.getItem(k);if(t!=="light"&&t!=="dark")t="dark";document.documentElement.setAttribute("data-theme",t);}catch(e){document.documentElement.setAttribute("data-theme","dark");}})();`; +/** + * Inline script: set data-theme before paint (no FOUC). + * Fully static string (no interpolation) so CodeQL js/bad-code-sanitization stays clean. + * The localStorage key literal must stay equal to THEME_STORAGE_KEY. + */ +export const THEME_BOOT_SCRIPT = + '(function(){try{var t=localStorage.getItem("papercut-theme");if(t!=="light"&&t!=="dark")t="dark";document.documentElement.setAttribute("data-theme",t);}catch(e){document.documentElement.setAttribute("data-theme","dark");}})();';