From 65d7b2fbd3c1a757c9d39a7dd19ddbb1f3ca73d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 14:13:20 +0000 Subject: [PATCH 1/2] Initial plan From 8d4c3c378a07c04f4891639652f1ec04e88c816e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 14:18:15 +0000 Subject: [PATCH 2/2] Fix CSS injection via unvalidated color property in modal component Agent-Logs-Url: https://github.com/numbersprotocol/capture-eye/sessions/4e9e0041-158a-4d32-88c8-a3fab26600f5 Co-authored-by: numbers-official <181934381+numbers-official@users.noreply.github.com> --- src/modal/modal.ts | 16 ++++++++---- src/test/modal_test.ts | 58 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/modal/modal.ts b/src/modal/modal.ts index 81f4783..dc349a2 100644 --- a/src/modal/modal.ts +++ b/src/modal/modal.ts @@ -632,16 +632,22 @@ export class CaptureEyeModal extends LitElement { ); } + private isValidColor(color: string): boolean { + return CSS.supports('color', color.trim()); + } + private updateModalColor() { - // Set primary color - this.style.setProperty('--primary-color', this._color); + // Clear colors first; set only if color is valid + this.style.setProperty('--primary-color', ''); + this.style.setProperty('--hover-color', ''); - // Set hover color - this.style.setProperty('--hover-color', ''); // Clear the color - if (!this._color) { + if (!this._color || !this.isValidColor(this._color)) { return; } + // Set primary color + this.style.setProperty('--primary-color', this._color); + const ctx = document.createElement('canvas').getContext('2d'); if (!ctx) { return; diff --git a/src/test/modal_test.ts b/src/test/modal_test.ts index 29a064d..c6e1875 100644 --- a/src/test/modal_test.ts +++ b/src/test/modal_test.ts @@ -526,4 +526,62 @@ suite('capture-eye-modal', () => { buttonCr = el.shadowRoot!.querySelector('.button-content-credentials'); assert.notExists(buttonCr); }); + + suite('updateModalColor color validation', () => { + test('sets --primary-color for valid hex color', async () => { + const el = await fixture(html` + + `); + el.updateModalOptions({ nid: '1', color: '#027fe5' }); + await el.updateComplete; + assert.equal(el.style.getPropertyValue('--primary-color'), '#027fe5'); + }); + + test('sets --primary-color for valid short hex color', async () => { + const el = await fixture(html` + + `); + el.updateModalOptions({ nid: '1', color: '#fff' }); + await el.updateComplete; + assert.equal(el.style.getPropertyValue('--primary-color'), '#fff'); + }); + + test('sets --primary-color for valid named color', async () => { + const el = await fixture(html` + + `); + el.updateModalOptions({ nid: '1', color: 'red' }); + await el.updateComplete; + assert.equal(el.style.getPropertyValue('--primary-color'), 'red'); + }); + + test('does not set --primary-color for invalid color with semicolon injection', async () => { + const el = await fixture(html` + + `); + el.updateModalOptions({ nid: '1', color: 'red; --injected: value' }); + await el.updateComplete; + assert.equal(el.style.getPropertyValue('--primary-color'), ''); + }); + + test('does not set --primary-color for arbitrary string', async () => { + const el = await fixture(html` + + `); + el.updateModalOptions({ nid: '1', color: 'not-a-color' }); + await el.updateComplete; + assert.equal(el.style.getPropertyValue('--primary-color'), ''); + }); + + test('clears --primary-color when color is empty', async () => { + const el = await fixture(html` + + `); + el.updateModalOptions({ nid: '1', color: '#027fe5' }); + await el.updateComplete; + el.updateModalOptions({ nid: '1', color: '' }); + await el.updateComplete; + assert.equal(el.style.getPropertyValue('--primary-color'), ''); + }); + }); });