From 9ce049a8fb1b221f46d6367886d8238e1085837b Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Fri, 5 Jun 2026 16:27:04 +0200 Subject: [PATCH] fix(compiler): sanitize `href`/`xlink:href` attributes of any element of the MathML namespace The ensures that future, present and past (and precated) elements of that namespace get sanitized. --- .../src/schema/dom_element_schema_registry.ts | 6 +++ .../src/schema/dom_security_schema.ts | 44 +++---------------- .../dom_element_schema_registry_spec.ts | 18 +++++++- 3 files changed, 28 insertions(+), 40 deletions(-) diff --git a/packages/compiler/src/schema/dom_element_schema_registry.ts b/packages/compiler/src/schema/dom_element_schema_registry.ts index ed2dca94ca0e..dd2691e819d3 100644 --- a/packages/compiler/src/schema/dom_element_schema_registry.ts +++ b/packages/compiler/src/schema/dom_element_schema_registry.ts @@ -455,10 +455,16 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry { const normalizedTag = normalizeTagName(tagName); propName = propName.toLowerCase(); + let nsWildcardTag: string | undefined; + if (normalizedTag.startsWith(':')) { + const lastColonIndex = normalizedTag.lastIndexOf(':'); + nsWildcardTag = `${normalizedTag.slice(0, lastColonIndex + 1)}*`; + } const securitySchema = SECURITY_SCHEMA(); const ctx = securitySchema[normalizedTag + '|' + propName] ?? + (nsWildcardTag !== undefined ? securitySchema[nsWildcardTag + '|' + propName] : undefined) ?? securitySchema['*|' + propName] ?? SecurityContext.NONE; diff --git a/packages/compiler/src/schema/dom_security_schema.ts b/packages/compiler/src/schema/dom_security_schema.ts index 1acc4ede7df7..351b142e97fa 100644 --- a/packages/compiler/src/schema/dom_security_schema.ts +++ b/packages/compiler/src/schema/dom_security_schema.ts @@ -68,43 +68,7 @@ export function SECURITY_SCHEMA(): {[k: string]: SecurityContext} { ['video', ['src']], ]); - registerContext(SecurityContext.URL, MATH_ML_NAMESPACE, [ - // MathML namespace - // https://crsrc.org/c/third_party/blink/renderer/core/sanitizer/sanitizer.cc;l=753-768;drc=b3eb16372dcd3317d65e9e0265015e322494edcd;bpv=1;bpt=1 - ['annotation', ['href', 'xlink:href']], - ['annotation-xml', ['href', 'xlink:href']], - ['maction', ['href', 'xlink:href']], - ['malignmark', ['href', 'xlink:href']], - ['math', ['href', 'xlink:href']], - ['mroot', ['href', 'xlink:href']], - ['msqrt', ['href', 'xlink:href']], - ['merror', ['href', 'xlink:href']], - ['mfrac', ['href', 'xlink:href']], - ['mglyph', ['href', 'xlink:href']], - ['msub', ['href', 'xlink:href']], - ['msup', ['href', 'xlink:href']], - ['msubsup', ['href', 'xlink:href']], - ['mmultiscripts', ['href', 'xlink:href']], - ['mprescripts', ['href', 'xlink:href']], - ['mi', ['href', 'xlink:href']], - ['mn', ['href', 'xlink:href']], - ['mo', ['href', 'xlink:href']], - ['mpadded', ['href', 'xlink:href']], - ['mphantom', ['href', 'xlink:href']], - ['mrow', ['href', 'xlink:href']], - ['ms', ['href', 'xlink:href']], - ['mspace', ['href', 'xlink:href']], - ['mstyle', ['href', 'xlink:href']], - ['mtable', ['href', 'xlink:href']], - ['mtd', ['href', 'xlink:href']], - ['mtr', ['href', 'xlink:href']], - ['mtext', ['href', 'xlink:href']], - ['mover', ['href', 'xlink:href']], - ['munder', ['href', 'xlink:href']], - ['munderover', ['href', 'xlink:href']], - ['semantics', ['href', 'xlink:href']], - ['none', ['href', 'xlink:href']], - ]); + registerContext(SecurityContext.URL, MATH_ML_NAMESPACE, [['*', ['href', 'xlink:href']]]); registerContext(SecurityContext.RESOURCE_URL, /** Namespace */ undefined, [ ['base', ['href']], @@ -161,8 +125,10 @@ function registerContext( specs: readonly [tagName: string, attributeNames: readonly string[]][], ): void { for (const [element, attributeNames] of specs) { - let tagName = - namespace && element !== '*' && element !== 'unknown' ? `:${namespace}:${element}` : element; + let tagName = element; + if (namespace && element !== 'unknown') { + tagName = `:${namespace}:${element}`; + } tagName = tagName.toLowerCase(); for (const attr of attributeNames) { diff --git a/packages/compiler/test/schema/dom_element_schema_registry_spec.ts b/packages/compiler/test/schema/dom_element_schema_registry_spec.ts index 839a206c5082..b0a4e9e79fbd 100644 --- a/packages/compiler/test/schema/dom_element_schema_registry_spec.ts +++ b/packages/compiler/test/schema/dom_element_schema_registry_spec.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.dev/license */ +import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SecurityContext} from '../../src/core'; import { _ATTR_TO_PROP, DomElementSchemaRegistry, SCHEMA, } from '../../src/schema/dom_element_schema_registry'; -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'; @@ -174,6 +174,22 @@ If 'onAnything' is a directive input, make sure the directive is imported by the expect(registry.securityContext(':svg:a', 'xlink:href', false)).toBe(SecurityContext.URL); expect(registry.securityContext(':svg:a', 'href', true)).toBe(SecurityContext.URL); expect(registry.securityContext(':svg:a', 'xlink:href', true)).toBe(SecurityContext.URL); + + // MathML link attributes + expect(registry.securityContext(':math:math', 'href', false)).toBe(SecurityContext.URL); + expect(registry.securityContext(':math:math', 'xlink:href', false)).toBe(SecurityContext.URL); + expect(registry.securityContext(':math:math', 'href', true)).toBe(SecurityContext.URL); + expect(registry.securityContext(':math:math', 'xlink:href', true)).toBe(SecurityContext.URL); + // Making sure we're have a wild card match for MathML elements with the correct security context + expect(registry.securityContext(':math:foobar', 'href', true)).toBe(SecurityContext.URL); + expect(registry.securityContext(':math:foobar', 'xlink:href', true)).toBe(SecurityContext.URL); + expect(registry.securityContext(':math:foobar', 'href', false)).toBe(SecurityContext.URL); + expect(registry.securityContext(':math:foobar', 'xlink:href', false)).toBe(SecurityContext.URL); + // Ensure MathML wildcard does not apply outside of the MathML namespace. + expect(registry.securityContext(':svg:foobar', 'href', false)).toBe(SecurityContext.NONE); + expect(registry.securityContext(':svg:foobar', 'xlink:href', false)).toBe(SecurityContext.NONE); + + expect(registry.securityContext('p', 'href', false)).toBe(SecurityContext.NONE); }); it('should detect properties on namespaced elements', () => {