From f55d3e10e1f93fc4074b031115dc2fb011988aba Mon Sep 17 00:00:00 2001 From: liufeichun Date: Tue, 30 Sep 2025 00:24:19 +0800 Subject: [PATCH 1/3] fix(runtime-dom): ensure iframe sandbox is handled as an attribute to prevent unintended behavior --- .../runtime-dom/__tests__/patchAttrs.spec.ts | 25 +++++++++++++++++++ packages/runtime-dom/src/patchProp.ts | 7 ++++++ 2 files changed, 32 insertions(+) diff --git a/packages/runtime-dom/__tests__/patchAttrs.spec.ts b/packages/runtime-dom/__tests__/patchAttrs.spec.ts index 393b685b0e9..fc403d78f29 100644 --- a/packages/runtime-dom/__tests__/patchAttrs.spec.ts +++ b/packages/runtime-dom/__tests__/patchAttrs.spec.ts @@ -88,4 +88,29 @@ describe('runtime-dom: attrs patching', () => { expect(el2.dataset.test).toBe(undefined) expect(testvalue).toBe(obj) }) + + // #13946 + test('sandbox attribute should always be handled as attribute', () => { + const iframe = document.createElement('iframe') + + // Verify sandbox is treated as attribute, not property + patchProp(iframe, 'sandbox', null, 'allow-scripts') + expect(iframe.getAttribute('sandbox')).toBe('allow-scripts') + + // Setting to null should remove the attribute + patchProp(iframe, 'sandbox', 'allow-scripts', null) + expect(iframe.hasAttribute('sandbox')).toBe(false) + expect(iframe.getAttribute('sandbox')).toBe(null) + + // Setting to undefined should also remove the attribute + patchProp(iframe, 'sandbox', null, 'allow-forms') + expect(iframe.getAttribute('sandbox')).toBe('allow-forms') + patchProp(iframe, 'sandbox', 'allow-forms', undefined) + expect(iframe.hasAttribute('sandbox')).toBe(false) + + // Empty string should set empty attribute (most restrictive sandbox) + patchProp(iframe, 'sandbox', null, '') + expect(iframe.getAttribute('sandbox')).toBe('') + expect(iframe.hasAttribute('sandbox')).toBe(true) + }) }) diff --git a/packages/runtime-dom/src/patchProp.ts b/packages/runtime-dom/src/patchProp.ts index 27174ddf624..5040810cf41 100644 --- a/packages/runtime-dom/src/patchProp.ts +++ b/packages/runtime-dom/src/patchProp.ts @@ -111,6 +111,13 @@ function shouldSetAsProp( return false } + // #13946 iframe.sandbox should always be set as attribute since setting + // the property to null results in 'null' string, and setting to empty string + // enables the most restrictive sandbox mode instead of no sandboxing. + if (key === 'sandbox') { + return false + } + // #1787, #2840 form property on form elements is readonly and must be set as // attribute. if (key === 'form') { From cc9cf7f673db19b6164ddad7f31c5b25880c0afb Mon Sep 17 00:00:00 2001 From: liufeichun Date: Tue, 30 Sep 2025 01:46:50 +0800 Subject: [PATCH 2/3] fix(runtime-dom): improve sandbox attribute handling in iframe tests --- .../runtime-dom/__tests__/patchAttrs.spec.ts | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/runtime-dom/__tests__/patchAttrs.spec.ts b/packages/runtime-dom/__tests__/patchAttrs.spec.ts index fc403d78f29..d181a1b038b 100644 --- a/packages/runtime-dom/__tests__/patchAttrs.spec.ts +++ b/packages/runtime-dom/__tests__/patchAttrs.spec.ts @@ -90,27 +90,36 @@ describe('runtime-dom: attrs patching', () => { }) // #13946 - test('sandbox attribute should always be handled as attribute', () => { - const iframe = document.createElement('iframe') - - // Verify sandbox is treated as attribute, not property + test('sandbox should be handled as attribute even if property exists', () => { + const iframe = document.createElement('iframe') as any + let propSetCount = 0 + // simulate sandbox property in jsdom environment + Object.defineProperty(iframe, 'sandbox', { + configurable: true, + enumerable: true, + get() { + return this._sandbox + }, + set(v) { + propSetCount++ + this._sandbox = v + }, + }) + patchProp(iframe, 'sandbox', null, 'allow-scripts') expect(iframe.getAttribute('sandbox')).toBe('allow-scripts') - - // Setting to null should remove the attribute + expect(propSetCount).toBe(0) + patchProp(iframe, 'sandbox', 'allow-scripts', null) expect(iframe.hasAttribute('sandbox')).toBe(false) expect(iframe.getAttribute('sandbox')).toBe(null) - - // Setting to undefined should also remove the attribute - patchProp(iframe, 'sandbox', null, 'allow-forms') - expect(iframe.getAttribute('sandbox')).toBe('allow-forms') - patchProp(iframe, 'sandbox', 'allow-forms', undefined) - expect(iframe.hasAttribute('sandbox')).toBe(false) - - // Empty string should set empty attribute (most restrictive sandbox) + expect(propSetCount).toBe(0) + patchProp(iframe, 'sandbox', null, '') expect(iframe.getAttribute('sandbox')).toBe('') expect(iframe.hasAttribute('sandbox')).toBe(true) + expect(propSetCount).toBe(0) + + delete iframe.sandbox }) }) From e36aa162e868550e131780e01fc5246ab982c436 Mon Sep 17 00:00:00 2001 From: liufeichun Date: Wed, 1 Oct 2025 02:21:13 +0800 Subject: [PATCH 3/3] fix(runtime-dom): restrict to iframe tag --- packages/runtime-dom/src/patchProp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime-dom/src/patchProp.ts b/packages/runtime-dom/src/patchProp.ts index 5040810cf41..74b5774ec9e 100644 --- a/packages/runtime-dom/src/patchProp.ts +++ b/packages/runtime-dom/src/patchProp.ts @@ -114,7 +114,7 @@ function shouldSetAsProp( // #13946 iframe.sandbox should always be set as attribute since setting // the property to null results in 'null' string, and setting to empty string // enables the most restrictive sandbox mode instead of no sandboxing. - if (key === 'sandbox') { + if (key === 'sandbox' && el.tagName === 'IFRAME') { return false }