Skip to content

Commit 8d4c3c3

Browse files
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>
1 parent 65d7b2f commit 8d4c3c3

2 files changed

Lines changed: 69 additions & 5 deletions

File tree

src/modal/modal.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -632,16 +632,22 @@ export class CaptureEyeModal extends LitElement {
632632
);
633633
}
634634

635+
private isValidColor(color: string): boolean {
636+
return CSS.supports('color', color.trim());
637+
}
638+
635639
private updateModalColor() {
636-
// Set primary color
637-
this.style.setProperty('--primary-color', this._color);
640+
// Clear colors first; set only if color is valid
641+
this.style.setProperty('--primary-color', '');
642+
this.style.setProperty('--hover-color', '');
638643

639-
// Set hover color
640-
this.style.setProperty('--hover-color', ''); // Clear the color
641-
if (!this._color) {
644+
if (!this._color || !this.isValidColor(this._color)) {
642645
return;
643646
}
644647

648+
// Set primary color
649+
this.style.setProperty('--primary-color', this._color);
650+
645651
const ctx = document.createElement('canvas').getContext('2d');
646652
if (!ctx) {
647653
return;

src/test/modal_test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,4 +526,62 @@ suite('capture-eye-modal', () => {
526526
buttonCr = el.shadowRoot!.querySelector('.button-content-credentials');
527527
assert.notExists(buttonCr);
528528
});
529+
530+
suite('updateModalColor color validation', () => {
531+
test('sets --primary-color for valid hex color', async () => {
532+
const el = await fixture<CaptureEyeModal>(html`
533+
<capture-eye-modal></capture-eye-modal>
534+
`);
535+
el.updateModalOptions({ nid: '1', color: '#027fe5' });
536+
await el.updateComplete;
537+
assert.equal(el.style.getPropertyValue('--primary-color'), '#027fe5');
538+
});
539+
540+
test('sets --primary-color for valid short hex color', async () => {
541+
const el = await fixture<CaptureEyeModal>(html`
542+
<capture-eye-modal></capture-eye-modal>
543+
`);
544+
el.updateModalOptions({ nid: '1', color: '#fff' });
545+
await el.updateComplete;
546+
assert.equal(el.style.getPropertyValue('--primary-color'), '#fff');
547+
});
548+
549+
test('sets --primary-color for valid named color', async () => {
550+
const el = await fixture<CaptureEyeModal>(html`
551+
<capture-eye-modal></capture-eye-modal>
552+
`);
553+
el.updateModalOptions({ nid: '1', color: 'red' });
554+
await el.updateComplete;
555+
assert.equal(el.style.getPropertyValue('--primary-color'), 'red');
556+
});
557+
558+
test('does not set --primary-color for invalid color with semicolon injection', async () => {
559+
const el = await fixture<CaptureEyeModal>(html`
560+
<capture-eye-modal></capture-eye-modal>
561+
`);
562+
el.updateModalOptions({ nid: '1', color: 'red; --injected: value' });
563+
await el.updateComplete;
564+
assert.equal(el.style.getPropertyValue('--primary-color'), '');
565+
});
566+
567+
test('does not set --primary-color for arbitrary string', async () => {
568+
const el = await fixture<CaptureEyeModal>(html`
569+
<capture-eye-modal></capture-eye-modal>
570+
`);
571+
el.updateModalOptions({ nid: '1', color: 'not-a-color' });
572+
await el.updateComplete;
573+
assert.equal(el.style.getPropertyValue('--primary-color'), '');
574+
});
575+
576+
test('clears --primary-color when color is empty', async () => {
577+
const el = await fixture<CaptureEyeModal>(html`
578+
<capture-eye-modal></capture-eye-modal>
579+
`);
580+
el.updateModalOptions({ nid: '1', color: '#027fe5' });
581+
await el.updateComplete;
582+
el.updateModalOptions({ nid: '1', color: '' });
583+
await el.updateComplete;
584+
assert.equal(el.style.getPropertyValue('--primary-color'), '');
585+
});
586+
});
529587
});

0 commit comments

Comments
 (0)