Skip to content

feat(core): support prefix-insensitive DOM schema lookups and compile-time i18n attribute validation#68591

Draft
alan-agius4 wants to merge 1 commit intoangular:mainfrom
alan-agius4:i18n-sec
Draft

feat(core): support prefix-insensitive DOM schema lookups and compile-time i18n attribute validation#68591
alan-agius4 wants to merge 1 commit intoangular:mainfrom
alan-agius4:i18n-sec

Conversation

@alan-agius4
Copy link
Copy Markdown
Contributor

Updates DomElementSchemaRegistry to strip :svg: and :math: namespace prefixes from tag names before querying SECURITY_SCHEMA at compile-time. This allows SVG and MathML attributes to correctly match their security contexts during compilation.

Also implements dynamic schema-based sanitization and validation for static i18n attributes by wrapping `` expressions directly in their core sanitizers and validators at compile-time via an optimized switch statement.

Additionally, updates the runtime i18n parser to dynamically resolve sanitizers based on the SECURITY_SCHEMA registry and cleans up type assertions inside ɵɵvalidateAttribute to safely support evaluation when no TNode is selected.

@angular-robot angular-robot Bot added detected: feature PR contains a feature commit area: core Issues related to the framework runtime labels May 6, 2026
@ngbot ngbot Bot added this to the Backlog milestone May 6, 2026
@alan-agius4 alan-agius4 force-pushed the i18n-sec branch 2 times, most recently from 72d2688 to c004f1b Compare May 6, 2026 12:01
…-time i18n attribute validation

Updates `DomElementSchemaRegistry` to strip `:svg:` and `:math:` namespace prefixes
from tag names before querying `SECURITY_SCHEMA` at compile-time. This allows SVG
and MathML attributes to correctly match their security contexts during compilation.

Also implements dynamic schema-based sanitization and validation for static i18n
attributes by wrapping `` expressions directly in their core sanitizers and
validators at compile-time via an optimized `switch` statement.

Additionally, updates the runtime i18n parser to dynamically resolve sanitizers based
on the `SECURITY_SCHEMA` registry and cleans up type assertions inside `ɵɵvalidateAttribute`
to safely support evaluation when no `TNode` is selected.

it('should throw error on static href attributes', () => {
loadTranslations({[computeMsgId('/safe')]: 'javascript:alert(1)'});
const template = `<a href="/safe" i18n-href>Link</a>`;
Copy link
Copy Markdown
Contributor Author

@alan-agius4 alan-agius4 May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be allowed, IE: static values to be overridden by i18n or disallow translations. (https://github.com/Hexix23/angular/blob/4f048e7de3f01e66d05dcc7f0a53926343d5fb5f/packages/compiler/src/render3/view/i18n/meta.ts#L211)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think no? Or else there's an attack vector through the translator right (lower risk, but still possible).

Is this specifically regarding href-like attributes or a generic concern for all static values?

@@ -140,7 +146,31 @@ export function collectI18nConsts(job: ComponentCompilationJob): void {
const attributesForMessage = extractedAttributesByI18nContext.get(op.i18nContext);
if (attributesForMessage !== undefined) {
for (const attr of attributesForMessage) {
Copy link
Copy Markdown
Contributor Author

@alan-agius4 alan-agius4 May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we run the santizer here or outright reject this in translations for such attributes?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Alan. To keep this aligned with the discussion from #68559: I agree the right fix should be centralized and schema-driven, not split into per-attribute patches.

My main concern is preserving the security invariant consistently:

translated static i18n attributes should not bypass the sanitizer/validator path Angular applies to the same security-sensitive tag/attribute pair elsewhere.

So I think the expected behavior should be:

  • non-security-sensitive translated attributes remain allowed;
  • URL / HTML / STYLE contexts are sanitized;
  • RESOURCE_URL fails closed, because translated strings should not become trusted executable resource URLs;
  • ATTRIBUTE_NO_BINDING goes through validateAttribute / rejection, because those attributes are not safely sanitizable.

The current #68591 direction appears to cover the concrete cases I reported across #68557, #68559, #68560, and #68580. My priority is to help make the broader hardening complete and robust rather than keep fragmented fixes open.

The only extra hardening point I would keep an eye on is namespace normalization anywhere SECURITY_SCHEMA is queried, especially SVG/MathML paths such as :svg:script, :svg:set, and MathML href / xlink:href, plus avoiding drift between the compiler-side and runtime/core schema
copies.

Hope this helps you and the team. Happy to test any extra cases if useful.

Comment on lines +448 to +452
if (tagName.startsWith(':svg:')) {
tagName = tagName.substring(5);
} else if (tagName.startsWith(':math:')) {
tagName = tagName.substring(6);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Is there a risk that we're changing the semantics of a tag name by stripping its namespace?

I imagine the different types are mostly compatible, but that feels largely coincidental rather than an intentional constraint of the standard. Is it possible that something like svg:foo is dangerous while regular foo is not and then accidentally skip sanitization? Even if no attributes currently meet that definition, is there a chance future additions to the spec might?

// property names do not have a security impact.
tagName = tagName.toLowerCase();
if (tagName.startsWith(':svg:')) {
tagName = tagName.substring(5);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider: Use .length to be more clear about the intent and meaning behind the magic number.

Suggested change
tagName = tagName.substring(5);
tagName = tagName.substring(':svg:'.length);

);
});

it('should allow no security senstive attributes', () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: I think you mean...

Suggested change
it('should allow no security senstive attributes', () => {
it('should allow non-security sensitive attributes', () => {


it('should allow no security senstive attributes', () => {
loadTranslations({[computeMsgId('50%')]: '100%'});
const template = `<iframe width="50%" i18n-width></iframe>`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: title might be the more representative example? Sure, internationalizing width is probably fine from a security perspective, but its also just a bad idea in general.


it('should throw error on static href attributes', () => {
loadTranslations({[computeMsgId('/safe')]: 'javascript:alert(1)'});
const template = `<a href="/safe" i18n-href>Link</a>`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think no? Or else there's an attack vector through the translator right (lower risk, but still possible).

Is this specifically regarding href-like attributes or a generic concern for all static values?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Where did this come from? Don't we have an existing manifest/schema of all the security sensitive attributes?

if (!ir.isStringLiteral(value)) {
throw Error('AssertionError: extracted attribute value should be string literal');
}
if (trustedValueFn !== null && ir.isStringLiteral(value)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change relevant/required?

Comment on lines +27 to +28
* @remarks Keep is a copy of DOM Security Schema.
* @see [SECURITY_SCHEMA](../../../compiler/src/schema/dom_security_schema.ts)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have an easy way to keep this file in sync with the packages/compiler/src/schema/dom_security_schema.ts? Maybe as an extra test in one of the packages?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: core Issues related to the framework runtime detected: feature PR contains a feature commit

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants