diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 4135fdf..15eb4a2 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -210,7 +210,9 @@ export function sanitizeElement(element: HTMLElement): void { // 2. Remove javascript: URIs in specific attributes if (['href', 'src', 'action', 'formaction'].includes(attrName)) { - if (attrValue.startsWith('javascript:')) { + // Strip all whitespace and control characters to prevent bypasses like "java\tscript:" + const normalizedValue = attrValue.replace(/[\s\x00-\x1F\x7F]/g, ''); + if (normalizedValue.startsWith('javascript:')) { element.removeAttribute(attributes[i].name); } } diff --git a/tests/unit/Security.test.ts b/tests/unit/Security.test.ts index 9242deb..c2b5ef5 100644 --- a/tests/unit/Security.test.ts +++ b/tests/unit/Security.test.ts @@ -60,6 +60,25 @@ describe('Security', () => { expect(form.hasAttribute('formaction')).toBe(false); }); + it('should remove obscured javascript: URIs (whitespace bypass)', () => { + const window = new Window(); + const document = window.document; + const link = document.createElement('a'); + + // Bypass attempts: tabs, newlines, spaces + link.setAttribute('href', 'java\tscript:alert(1)'); + sanitizeElement(link as unknown as HTMLElement); + expect(link.hasAttribute('href')).toBe(false); + + link.setAttribute('href', 'java\nscript:alert(1)'); + sanitizeElement(link as unknown as HTMLElement); + expect(link.hasAttribute('href')).toBe(false); + + link.setAttribute('href', ' javascript:alert(1)'); + sanitizeElement(link as unknown as HTMLElement); + expect(link.hasAttribute('href')).toBe(false); + }); + it('should preserve safe URLs', () => { const window = new Window(); const document = window.document;