From e53c45ba94620341e40dffa340b4ac4af8de5906 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 28 Apr 2026 20:24:19 -0700 Subject: [PATCH] ci: shard control ui codeql quality Adds a narrow CodeQL Critical Quality shard for the Control UI/control-plane surface and fixes the custom-theme font-family ReDoS finding discovered by the new shard. --- ...deql-ui-control-plane-critical-quality.yml | 36 +++++++++++++++++++ .github/workflows/codeql-critical-quality.yml | 21 +++++++++++ docs/ci.md | 5 ++- ui/src/ui/custom-theme.test.ts | 21 +++++++++++ ui/src/ui/custom-theme.ts | 15 ++++++-- 5 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 .github/codeql/codeql-ui-control-plane-critical-quality.yml diff --git a/.github/codeql/codeql-ui-control-plane-critical-quality.yml b/.github/codeql/codeql-ui-control-plane-critical-quality.yml new file mode 100644 index 00000000000..3ccc55fd00a --- /dev/null +++ b/.github/codeql/codeql-ui-control-plane-critical-quality.yml @@ -0,0 +1,36 @@ +name: openclaw-codeql-ui-control-plane-critical-quality + +disable-default-queries: true + +queries: + - uses: security-and-quality + +query-filters: + - include: + problem.severity: + - error + - exclude: + tags: + - security + +paths: + - ui/src/main.ts + - ui/src/local-storage.ts + - ui/src/ui + - src/tasks/task-registry-control*.ts + +paths-ignore: + - "**/node_modules" + - "**/coverage" + - "**/*.generated.ts" + - "**/*.bundle.js" + - "**/*-runtime.js" + - "**/*.test.ts" + - "**/*.test.tsx" + - "**/*.e2e.test.ts" + - "**/*.e2e.test.tsx" + - "**/*test-support*" + - "**/*test-helper*" + - "**/*mock*" + - "**/*fixture*" + - "**/*bench*" diff --git a/.github/workflows/codeql-critical-quality.yml b/.github/workflows/codeql-critical-quality.yml index f362cffd7a1..8048dcb56b6 100644 --- a/.github/workflows/codeql-critical-quality.yml +++ b/.github/workflows/codeql-critical-quality.yml @@ -123,6 +123,27 @@ jobs: with: category: "/codeql-critical-quality/agent-runtime-boundary" + ui-control-plane: + name: Critical Quality (ui-control-plane) + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 25 + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + submodules: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4 + with: + languages: javascript-typescript + config-file: ./.github/codeql/codeql-ui-control-plane-critical-quality.yml + + - name: Analyze + uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4 + with: + category: "/codeql-critical-quality/ui-control-plane" + plugin-boundary: name: Critical Quality (plugin-boundary) runs-on: blacksmith-4vcpu-ubuntu-2404 diff --git a/docs/ci.md b/docs/ci.md index fc0c88a883f..084774f39a7 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -273,11 +273,14 @@ the separate `/codeql-critical-quality/channel-runtime-boundary` category. The agent-runtime-boundary job scans command execution, model/provider dispatch, auto-reply dispatch and queues, and ACP control-plane runtime contracts under the separate `/codeql-critical-quality/agent-runtime-boundary` category. The +ui-control-plane job scans Control UI bootstrap, local persistence, gateway +control flows, and task control-plane runtime contracts under the separate +`/codeql-critical-quality/ui-control-plane` category. The plugin-boundary job scans loader, registry, public-surface, and Plugin SDK entrypoint contracts under a separate `/codeql-critical-quality/plugin-boundary` category. Keep the workflow separate from security so quality findings can be scheduled, measured, disabled, or expanded without obscuring security signal. -Swift, Python, UI, and bundled-plugin CodeQL expansion should be added back as +Swift, Python, and bundled-plugin CodeQL expansion should be added back as scoped or sharded follow-up work only after the narrow profiles have stable runtime and signal. diff --git a/ui/src/ui/custom-theme.test.ts b/ui/src/ui/custom-theme.test.ts index 147955b6ebc..34ce8f111d5 100644 --- a/ui/src/ui/custom-theme.test.ts +++ b/ui/src/ui/custom-theme.test.ts @@ -254,6 +254,27 @@ describe("custom theme import helpers", () => { ).toThrow("Unsupported tweakcn token"); }); + it("validates imported font families without regex backtracking", () => { + const payload = createTweakcnPayload(); + payload.cssVars.theme["font-sans"] = + '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'; + + expect( + normalizeImportedCustomTheme(payload, { + sourceUrl: "https://tweakcn.com/themes/cmlhfpjhw000004l4f4ax3m7z", + themeId: "cmlhfpjhw000004l4f4ax3m7z", + }).light["font-body"], + ).toBe('"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'); + + payload.cssVars.theme["font-sans"] = `${"Inter, ".repeat(20)}@bad`; + expect(() => + normalizeImportedCustomTheme(payload, { + sourceUrl: "https://tweakcn.com/themes/cmlhfpjhw000004l4f4ax3m7z", + themeId: "cmlhfpjhw000004l4f4ax3m7z", + }), + ).toThrow("Unsupported tweakcn token"); + }); + it("builds stable CSS blocks for custom dark and light themes", () => { const css = buildCustomThemeStyles(createImportedTheme()); diff --git a/ui/src/ui/custom-theme.ts b/ui/src/ui/custom-theme.ts index f94e7f8fd92..e2b1553edc1 100644 --- a/ui/src/ui/custom-theme.ts +++ b/ui/src/ui/custom-theme.ts @@ -27,7 +27,7 @@ const SAFE_COLOR_KEYWORDS = new Set(["black", "white", "transparent", "currentco const SAFE_COLOR_FUNCTION_PATTERN = /^(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch)\([a-z0-9+\-.,/%\s]+\)$/i; const SAFE_HEX_COLOR_PATTERN = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i; -const SAFE_FONT_FAMILY_PATTERN = /^[a-z0-9\s,'"._-]+(?:,\s*[a-z0-9\s'"._-]+)*$/i; +const SAFE_FONT_FAMILY_PUNCTUATION = new Set([",", "'", '"', ".", "_", "-"]); const MODE_TOKEN_ORDER = [ "bg", @@ -261,12 +261,23 @@ function requireSafeExternalColorValue(value: unknown, label: string) { throw new Error(`Unsupported tweakcn token: ${label}`); } +function isSafeFontFamilyCharacter(char: string) { + const code = char.charCodeAt(0); + return ( + (code >= 0x30 && code <= 0x39) || + (code >= 0x41 && code <= 0x5a) || + (code >= 0x61 && code <= 0x7a) || + char === " " || + SAFE_FONT_FAMILY_PUNCTUATION.has(char) + ); +} + function requireSafeFontFamilyValue(value: unknown, label: string) { const normalized = requireSafeCssValue(value, label); if ( normalized.includes("(") || normalized.includes(")") || - !SAFE_FONT_FAMILY_PATTERN.test(normalized) + !Array.from(normalized).every(isSafeFontFamilyCharacter) ) { throw new Error(`Unsupported tweakcn token: ${label}`); }