fix(compiler): register SVG animation attributes in URL security context (#67797)

This change is a security hardening measure to prevent potentially unsafe attribute value manipulation through SVG animations. By mapping `animate|to`, `animate|from`, `animate|values`, and `set|to` to the `SecurityContext.URL`,  Angular will now automatically sanitize these attributes.

PR Close #67797
This commit is contained in:
Alan Agius 2026-03-23 09:37:59 +00:00 committed by Pawel Kozlowski
parent f257f54967
commit 08d36599d7
3 changed files with 30 additions and 4 deletions

View file

@ -105,6 +105,13 @@ export function SECURITY_SCHEMA(): {[k: string]: SecurityContext} {
'none|href',
'none|xlink:href',
// SVG animation value attributes — may animate URL-bearing attrs (e.g. attributeName="href")
// https://www.w3.org/TR/SVG11/animate.html#ToAttribute
'animate|to',
'animate|from',
'animate|values',
'set|to',
// The below two items are safe and should be removed but they require a G3 clean-up as a small number of tests fail.
'img|src',
'video|src',

View file

@ -11,14 +11,11 @@ import {
DomElementSchemaRegistry,
SCHEMA,
} from '../../src/schema/dom_element_schema_registry';
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SecurityContext} from '@angular/core';
import {isNode} from '@angular/private/testing';
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SecurityContext} from '../../src/core';
import {Element} from '../../src/ml_parser/ast';
import {HtmlParser} from '../../src/ml_parser/html_parser';
import {extractSchema} from './schema_extractor';
describe('DOMElementSchema', () => {
let registry: DomElementSchemaRegistry;
beforeEach(() => {
@ -157,6 +154,12 @@ If 'onAnything' is a directive input, make sure the directive is imported by the
expect(registry.securityContext('a', 'href', false)).toBe(SecurityContext.URL);
expect(registry.securityContext('a', 'style', false)).toBe(SecurityContext.STYLE);
expect(registry.securityContext('base', 'href', false)).toBe(SecurityContext.RESOURCE_URL);
// SVG animate and set attributes
expect(registry.securityContext('animate', 'to', false)).toBe(SecurityContext.URL);
expect(registry.securityContext('animate', 'from', false)).toBe(SecurityContext.URL);
expect(registry.securityContext('animate', 'values', false)).toBe(SecurityContext.URL);
expect(registry.securityContext('set', 'to', false)).toBe(SecurityContext.URL);
});
it('should detect properties on namespaced elements', () => {

View file

@ -166,6 +166,22 @@ describe('sanitization', () => {
expect(
ɵɵsanitizeUrlOrResourceUrl(bypassSanitizationTrustUrl('javascript:true'), 'a', 'href'),
).toEqual('javascript:true');
// SVG animate and set attributes
expect(ɵɵsanitizeUrlOrResourceUrl('javascript:alert(1)', 'animate', 'to')).toEqual(
'unsafe:javascript:alert(1)',
);
expect(ɵɵsanitizeUrlOrResourceUrl('0.2', 'animate', 'to')).toEqual('0.2');
expect(ɵɵsanitizeUrlOrResourceUrl('javascript:alert(1)', 'animate', 'from')).toEqual(
'unsafe:javascript:alert(1)',
);
expect(ɵɵsanitizeUrlOrResourceUrl('javascript:alert(1)', 'animate', 'values')).toEqual(
'unsafe:javascript:alert(1)',
);
expect(ɵɵsanitizeUrlOrResourceUrl('javascript:alert(1)', 'set', 'to')).toEqual(
'unsafe:javascript:alert(1)',
);
expect(ɵɵsanitizeUrlOrResourceUrl('0.2', 'set', 'to')).toEqual('0.2');
});
it('should only trust constant strings from template literal tags without interpolation', () => {