From fd34914bed881cfc51f54d8b10f1c03aa3ca0c0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B3=E5=BB=B7=E5=AE=89?= <73953029+nrps9909@users.noreply.github.com> Date: Thu, 1 Oct 2026 10:10:53 +0800 Subject: [PATCH] fix: avoid TrustedHTML sink for style injection --- src/Dom/dynamicCSS.ts | 6 +++--- tests/dynamicCSS.test.tsx | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/Dom/dynamicCSS.ts b/src/Dom/dynamicCSS.ts index 5a566d3d..df9fd2a6 100644 --- a/src/Dom/dynamicCSS.ts +++ b/src/Dom/dynamicCSS.ts @@ -80,7 +80,7 @@ export function injectCSS(css: string, option: Options = {}) { if (csp?.nonce) { styleNode.nonce = csp?.nonce; } - styleNode.innerHTML = css; + styleNode.textContent = css; const container = getContainer(option); if (!container) { @@ -193,8 +193,8 @@ export function updateCSS( existNode.nonce = option.csp?.nonce; } - if (existNode.innerHTML !== css) { - existNode.innerHTML = css; + if (existNode.textContent !== css) { + existNode.textContent = css; } return existNode; diff --git a/tests/dynamicCSS.test.tsx b/tests/dynamicCSS.test.tsx index 1cc97d1d..ffb6d2b8 100644 --- a/tests/dynamicCSS.test.tsx +++ b/tests/dynamicCSS.test.tsx @@ -46,6 +46,41 @@ describe('dynamicCSS', () => { } }); + it('does not assign CSS through the innerHTML sink', () => { + const innerHTMLDescriptor = Object.getOwnPropertyDescriptor( + HTMLStyleElement.prototype, + 'innerHTML', + ); + + Object.defineProperty(HTMLStyleElement.prototype, 'innerHTML', { + configurable: true, + set() { + throw new TypeError( + "This document requires 'TrustedHTML' assignment.", + ); + }, + }); + + try { + const style = injectCSS(TEST_STYLE); + const managedStyle = updateCSS(TEST_STYLE, 'trusted-types'); + + expect(style.textContent).toEqual(TEST_STYLE); + expect(() => updateCSS('.light {}', 'trusted-types')).not.toThrow(); + expect(managedStyle.textContent).toEqual('.light {}'); + } finally { + if (innerHTMLDescriptor) { + Object.defineProperty( + HTMLStyleElement.prototype, + 'innerHTML', + innerHTMLDescriptor, + ); + } else { + delete HTMLStyleElement.prototype.innerHTML; + } + } + }); + describe('prepend', () => { function testPrepend() { const head = document.querySelector('head');