From e9158f06047b8e6081784964cdc081824a53994c Mon Sep 17 00:00:00 2001 From: SkyZeroZx <73321943+SkyZeroZx@users.noreply.github.com> Date: Fri, 5 Jun 2026 06:20:43 -0500 Subject: [PATCH] fix(core): validate lowercase SVG animation attribute names Normalize SVG animation attributeName lookup to also recognize lowercase attributename before allowing dynamic animation value bindings. Add runtime and platform-server SSR regression coverage for lowercase attributename retargeting. (cherry picked from commit d5e689af80eb3264909ccb317ff17aed1d67d57e) --- .../core/src/sanitization/sanitization.ts | 22 +++++++++++++++-- .../core/test/render3/integration_spec.ts | 24 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/core/src/sanitization/sanitization.ts b/packages/core/src/sanitization/sanitization.ts index 2194ac810f35..95757fa648d8 100644 --- a/packages/core/src/sanitization/sanitization.ts +++ b/packages/core/src/sanitization/sanitization.ts @@ -277,6 +277,7 @@ function getSanitizer(): Sanitizer | null { * Set of attributes that are sensitive and should be sanitized. */ const SECURITY_SENSITIVE_ATTRIBUTE_NAMES: ReadonlySet = new Set(['href', 'xlink:href']); +const SVG_ANIMATION_ATTRIBUTE_NAME_CANDIDATES = ['attributeName', 'attributename'] as const; /** * @remarks Keep this in sync with DOM Security Schema. @@ -356,9 +357,12 @@ export function ɵɵvalidateAttribute(value: T, tagName: string, attrib } const element = getNativeByTNode(tNode, lView) as SVGAnimateElement; - const attributeNameValue = element.getAttribute('attributeName'); + const attributeNameValue = getSecuritySensitiveSVGAnimationAttributeName( + element, + validationConfig, + ); - if (attributeNameValue && validationConfig.has(attributeNameValue.toLowerCase())) { + if (attributeNameValue) { const errorMessage = ngDevMode && `Angular has detected that the \`${attributeName}\` was applied ` + @@ -384,3 +388,17 @@ export function ɵɵvalidateAttribute(value: T, tagName: string, attrib `in a template or in host bindings section.`; throw new RuntimeError(RuntimeErrorCode.UNSAFE_ATTRIBUTE_BINDING, errorMessage); } + +function getSecuritySensitiveSVGAnimationAttributeName( + element: SVGAnimateElement, + validationConfig: ReadonlySet, +): string | null { + for (const attributeName of SVG_ANIMATION_ATTRIBUTE_NAME_CANDIDATES) { + const attributeNameValue = element.getAttribute(attributeName); + if (attributeNameValue !== null && validationConfig.has(attributeNameValue.toLowerCase())) { + return attributeNameValue; + } + } + + return null; +} diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 79554db19d37..e69f9e72295b 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -670,6 +670,30 @@ describe('sanitization', () => { ); }); + // The SVG `attributeName` is case-sensitive when accessed via the DOM API + // (i.e. `setAttribute('attributename', ...)` and `setAttribute('attributeName', ...)` + // create two distinct attributes). However, the browser tokenizer normalizes + // the lowercase form `attributename` to `attributeName` on initial parsing, + // which means the client-side sanitizer still ends up seeing `attributeName`. + // The SSR renderer (Domino) does not perform this normalization, so we + // explicitly look up the lowercase form as well to make sure the sanitizer + // is triggered consistently in both environments. + it('should throw when binding to set element with attributename="href"', () => { + @Component({ + selector: 'test-comp', + template: ``, + }) + class TestComp {} + + TestBed.configureTestingModule({ + providers: [provideZoneChangeDetection()], + }); + const fixture = TestBed.createComponent(TestComp); + expect(() => fixture.detectChanges()).toThrowError( + /Angular has detected that the `to` was applied/, + ); + }); + it('should not throw when binding to animate element when attributeName is not href', () => { @Component({ selector: 'test-comp',