VYPR
Critical severity9.0NVD Advisory· Published Mar 16, 2026· Updated Apr 30, 2026

CVE-2026-32635

CVE-2026-32635

Description

Angular is a development platform for building mobile and desktop web applications using TypeScript/JavaScript and other languages. Prior to 22.0.0-next.3, 21.2.4, 20.3.18, and 19.2.20, a Cross-Site Scripting (XSS) vulnerability has been identified in the Angular runtime and compiler. It occurs when the application uses a security-sensitive attribute (for example href on an anchor tag) together with Angular's ability to internationalize attributes. Enabling internationalization for the sensitive attribute by adding i18n-<attribute> name bypasses Angular's built-in sanitization mechanism, which when combined with a data binding to untrusted user-generated data can allow an attacker to inject a malicious script. This vulnerability is fixed in 22.0.0-next.3, 21.2.4, 20.3.18, and 19.2.20.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@angular/corenpm
>= 22.0.0-next.0, < 22.0.0-next.322.0.0-next.3
@angular/corenpm
>= 21.0.0-next.0, < 21.2.421.2.4
@angular/corenpm
>= 20.0.0-next.0.0.0, < 20.3.1820.3.18
@angular/corenpm
>= 19.0.0-next.0, < 19.2.2019.2.20
@angular/corenpm
>= 17.0.0-next.0, <= 18.2.14
@angular/compilernpm
>= 22.0.0-next.0, < 22.0.0-next.322.0.0-next.3
@angular/compilernpm
>= 21.0.0-next.0, < 21.2.421.2.4
@angular/compilernpm
>= 20.0.0-next.0.0.0, < 20.3.1820.3.18
@angular/compilernpm
>= 19.0.0-next.0, < 19.2.2019.2.20
@angular/compilernpm
>= 17.0.0-next.0, <= 18.2.14

Affected products

3
  • cpe:2.3:a:angular:angular_cli:*:*:*:*:*:*:*:*+ 2 more
    • cpe:2.3:a:angular:angular_cli:*:*:*:*:*:*:*:*range: >=17.0.0,<19.2.0
    • cpe:2.3:a:angular:angular_cli:22.0.0:next0:*:*:*:*:*:*
    • cpe:2.3:a:angular:angular_cli:22.0.0:next1:*:*:*:*:*:*

Patches

4
78dea55351fb

fix(compiler): disallow translations of iframe src

https://github.com/angular/angularKristiyan KostadinovMar 12, 2026via ghsa
2 files changed · +3 1
  • packages/compiler/src/schema/trusted_types_sinks.ts+2 1 modified
    @@ -11,7 +11,7 @@
      * tags use '*'.
      *
      * Extracted from, and should be kept in sync with
    - * https://w3c.github.io/webappsec-trusted-types/dist/spec/#integrations
    + * https://www.w3.org/TR/trusted-types/#integrations
      */
     const TRUSTED_TYPES_SINKS = new Set<string>([
       // NOTE: All strings in this set *must* be lowercase!
    @@ -25,6 +25,7 @@ const TRUSTED_TYPES_SINKS = new Set<string>([
     
       // TrustedScriptURL
       'embed|src',
    +  'iframe|src',
       'object|codebase',
       'object|data',
     ]);
    
  • packages/compiler/test/schema/trusted_types_sinks_spec.ts+1 0 modified
    @@ -13,6 +13,7 @@ describe('isTrustedTypesSink', () => {
         expect(isTrustedTypesSink('iframe', 'srcdoc')).toBeTrue();
         expect(isTrustedTypesSink('p', 'innerHTML')).toBeTrue();
         expect(isTrustedTypesSink('embed', 'src')).toBeTrue();
    +    expect(isTrustedTypesSink('iframe', 'src')).toBeTrue();
         expect(isTrustedTypesSink('a', 'href')).toBeFalse();
         expect(isTrustedTypesSink('base', 'href')).toBeFalse();
         expect(isTrustedTypesSink('div', 'style')).toBeFalse();
    
ed2d324f9cc1

fix(compiler): disallow translations of iframe src

https://github.com/angular/angularKristiyan KostadinovMar 12, 2026via ghsa
2 files changed · +3 1
  • packages/compiler/src/schema/trusted_types_sinks.ts+2 1 modified
    @@ -11,7 +11,7 @@
      * tags use '*'.
      *
      * Extracted from, and should be kept in sync with
    - * https://w3c.github.io/webappsec-trusted-types/dist/spec/#integrations
    + * https://www.w3.org/TR/trusted-types/#integrations
      */
     const TRUSTED_TYPES_SINKS = new Set<string>([
       // NOTE: All strings in this set *must* be lowercase!
    @@ -25,6 +25,7 @@ const TRUSTED_TYPES_SINKS = new Set<string>([
     
       // TrustedScriptURL
       'embed|src',
    +  'iframe|src',
       'object|codebase',
       'object|data',
     ]);
    
  • packages/compiler/test/schema/trusted_types_sinks_spec.ts+1 0 modified
    @@ -13,6 +13,7 @@ describe('isTrustedTypesSink', () => {
         expect(isTrustedTypesSink('iframe', 'srcdoc')).toBeTrue();
         expect(isTrustedTypesSink('p', 'innerHTML')).toBeTrue();
         expect(isTrustedTypesSink('embed', 'src')).toBeTrue();
    +    expect(isTrustedTypesSink('iframe', 'src')).toBeTrue();
         expect(isTrustedTypesSink('a', 'href')).toBeFalse();
         expect(isTrustedTypesSink('base', 'href')).toBeFalse();
         expect(isTrustedTypesSink('div', 'style')).toBeFalse();
    
8630319f74c9

fix(core): sanitize translated attribute bindings with interpolations

https://github.com/angular/angularKristiyan KostadinovMar 10, 2026via ghsa
2 files changed · +74 13
  • packages/core/src/render3/i18n/i18n_parse.ts+9 13 modified
    @@ -388,7 +388,7 @@ export function i18nAttributesFirstPass(tView: TView, index: number, values: str
               previousElementIndex,
               attrName,
               countBindings(updateOpCodes),
    -          null,
    +          URI_ATTRS[attrName.toLowerCase()] ? _sanitizeUrl : null,
             );
           }
         }
    @@ -810,18 +810,14 @@ function walkIcuTree(
                 const hasBinding = !!attr.value.match(BINDING_REGEXP);
                 if (hasBinding) {
                   if (VALID_ATTRS.hasOwnProperty(lowerAttrName)) {
    -                if (URI_ATTRS[lowerAttrName]) {
    -                  generateBindingUpdateOpCodes(
    -                    update,
    -                    attr.value,
    -                    newIndex,
    -                    attr.name,
    -                    0,
    -                    _sanitizeUrl,
    -                  );
    -                } else {
    -                  generateBindingUpdateOpCodes(update, attr.value, newIndex, attr.name, 0, null);
    -                }
    +                generateBindingUpdateOpCodes(
    +                  update,
    +                  attr.value,
    +                  newIndex,
    +                  attr.name,
    +                  0,
    +                  URI_ATTRS[lowerAttrName] ? _sanitizeUrl : null,
    +                );
                   } else {
                     ngDevMode &&
                       console.warn(
    
  • packages/core/test/acceptance/i18n_spec.ts+65 0 modified
    @@ -3534,6 +3534,71 @@ describe('runtime i18n', () => {
           'translatedText value',
         );
       });
    +
    +  describe('attribute sanitization', () => {
    +    @Component({template: ''})
    +    class SanitizeAppComp {
    +      url = 'javascript:alert("oh no")';
    +      count = 0;
    +    }
    +
    +    it('should sanitize translated attribute binding', () => {
    +      const fixture = initWithTemplate(SanitizeAppComp, '<a [attr.href]="url" i18n-href></a>');
    +      const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
    +      expect(link.getAttribute('href')).toMatch(/^unsafe:/);
    +    });
    +
    +    it('should sanitize translated property binding', () => {
    +      const fixture = initWithTemplate(SanitizeAppComp, '<a [href]="url" i18n-href></a>');
    +      const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
    +      expect(link.getAttribute('href')).toMatch(/^unsafe:/);
    +    });
    +
    +    it('should sanitize translated interpolation', () => {
    +      const fixture = initWithTemplate(SanitizeAppComp, '<a href="{{url}}" i18n-href></a>');
    +      const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
    +      expect(link.getAttribute('href')).toMatch(/^unsafe:/);
    +    });
    +
    +    it('should sanitize interpolation inside translated element', () => {
    +      const fixture = initWithTemplate(SanitizeAppComp, `<div i18n><a href="{{url}}"></a></div>`);
    +      const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
    +      expect(link.getAttribute('href')).toMatch(/^unsafe:/);
    +    });
    +
    +    it('should sanitize attribute binding inside translated element', () => {
    +      const fixture = initWithTemplate(
    +        SanitizeAppComp,
    +        `<div i18n><a [attr.href]="url"></a></div>`,
    +      );
    +      const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
    +      expect(link.getAttribute('href')).toMatch(/^unsafe:/);
    +    });
    +
    +    it('should sanitize property binding inside translated element', () => {
    +      const fixture = initWithTemplate(SanitizeAppComp, `<div i18n><a [href]="url"></a></div>`);
    +      const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
    +      expect(link.getAttribute('href')).toMatch(/^unsafe:/);
    +    });
    +
    +    it('should sanitize property binding inside an ICU', () => {
    +      const fixture = initWithTemplate(
    +        SanitizeAppComp,
    +        `<div i18n>{count, plural,
    +            =0 {no <strong>link</strong> yet}
    +            other {{{count}} Here is the <a href="{{url}}">link</a>!}
    +        }</div>`,
    +      );
    +
    +      expect(fixture.nativeElement.querySelector('a')).toBeFalsy();
    +
    +      fixture.componentInstance.count = 1;
    +      fixture.detectChanges();
    +      const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
    +      expect(link).toBeTruthy();
    +      expect(link.getAttribute('href')).toMatch(/^unsafe:/);
    +    });
    +  });
     });
     
     function initWithTemplate(compType: Type<any>, template: string) {
    
224e60ecb1b9

fix(core): sanitize translated attribute bindings with interpolations

https://github.com/angular/angularKristiyan KostadinovMar 10, 2026via ghsa
2 files changed · +74 13
  • packages/core/src/render3/i18n/i18n_parse.ts+9 13 modified
    @@ -388,7 +388,7 @@ export function i18nAttributesFirstPass(tView: TView, index: number, values: str
               previousElementIndex,
               attrName,
               countBindings(updateOpCodes),
    -          null,
    +          URI_ATTRS[attrName.toLowerCase()] ? _sanitizeUrl : null,
             );
           }
         }
    @@ -810,18 +810,14 @@ function walkIcuTree(
                 const hasBinding = !!attr.value.match(BINDING_REGEXP);
                 if (hasBinding) {
                   if (VALID_ATTRS.hasOwnProperty(lowerAttrName)) {
    -                if (URI_ATTRS[lowerAttrName]) {
    -                  generateBindingUpdateOpCodes(
    -                    update,
    -                    attr.value,
    -                    newIndex,
    -                    attr.name,
    -                    0,
    -                    _sanitizeUrl,
    -                  );
    -                } else {
    -                  generateBindingUpdateOpCodes(update, attr.value, newIndex, attr.name, 0, null);
    -                }
    +                generateBindingUpdateOpCodes(
    +                  update,
    +                  attr.value,
    +                  newIndex,
    +                  attr.name,
    +                  0,
    +                  URI_ATTRS[lowerAttrName] ? _sanitizeUrl : null,
    +                );
                   } else {
                     ngDevMode &&
                       console.warn(
    
  • packages/core/test/acceptance/i18n_spec.ts+65 0 modified
    @@ -3534,6 +3534,71 @@ describe('runtime i18n', () => {
           'translatedText value',
         );
       });
    +
    +  describe('attribute sanitization', () => {
    +    @Component({template: ''})
    +    class SanitizeAppComp {
    +      url = 'javascript:alert("oh no")';
    +      count = 0;
    +    }
    +
    +    it('should sanitize translated attribute binding', () => {
    +      const fixture = initWithTemplate(SanitizeAppComp, '<a [attr.href]="url" i18n-href></a>');
    +      const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
    +      expect(link.getAttribute('href')).toMatch(/^unsafe:/);
    +    });
    +
    +    it('should sanitize translated property binding', () => {
    +      const fixture = initWithTemplate(SanitizeAppComp, '<a [href]="url" i18n-href></a>');
    +      const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
    +      expect(link.getAttribute('href')).toMatch(/^unsafe:/);
    +    });
    +
    +    it('should sanitize translated interpolation', () => {
    +      const fixture = initWithTemplate(SanitizeAppComp, '<a href="{{url}}" i18n-href></a>');
    +      const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
    +      expect(link.getAttribute('href')).toMatch(/^unsafe:/);
    +    });
    +
    +    it('should sanitize interpolation inside translated element', () => {
    +      const fixture = initWithTemplate(SanitizeAppComp, `<div i18n><a href="{{url}}"></a></div>`);
    +      const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
    +      expect(link.getAttribute('href')).toMatch(/^unsafe:/);
    +    });
    +
    +    it('should sanitize attribute binding inside translated element', () => {
    +      const fixture = initWithTemplate(
    +        SanitizeAppComp,
    +        `<div i18n><a [attr.href]="url"></a></div>`,
    +      );
    +      const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
    +      expect(link.getAttribute('href')).toMatch(/^unsafe:/);
    +    });
    +
    +    it('should sanitize property binding inside translated element', () => {
    +      const fixture = initWithTemplate(SanitizeAppComp, `<div i18n><a [href]="url"></a></div>`);
    +      const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
    +      expect(link.getAttribute('href')).toMatch(/^unsafe:/);
    +    });
    +
    +    it('should sanitize property binding inside an ICU', () => {
    +      const fixture = initWithTemplate(
    +        SanitizeAppComp,
    +        `<div i18n>{count, plural,
    +            =0 {no <strong>link</strong> yet}
    +            other {{{count}} Here is the <a href="{{url}}">link</a>!}
    +        }</div>`,
    +      );
    +
    +      expect(fixture.nativeElement.querySelector('a')).toBeFalsy();
    +
    +      fixture.componentInstance.count = 1;
    +      fixture.detectChanges();
    +      const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
    +      expect(link).toBeTruthy();
    +      expect(link.getAttribute('href')).toMatch(/^unsafe:/);
    +    });
    +  });
     });
     
     function initWithTemplate(compType: Type<any>, template: string) {
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.