VYPR
Medium severity5.3GHSA Advisory· Published Jun 15, 2026· Updated Jun 15, 2026

@angular/compiler: Two-Way Property Binding Sanitization Bypass (XSS)

CVE-2026-54265

Description

Angular's @angular/compiler fails to sanitize two-way property bindings on sensitive DOM properties, enabling XSS via user-controlled input.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Angular's @angular/compiler fails to sanitize two-way property bindings on sensitive DOM properties, enabling XSS via user-controlled input.

Vulnerability

An issue in the @angular/compiler package allows bypassing DOM property sanitization when two-way binding syntax ([(property)] or bindon-) is used on security-sensitive native DOM properties such as innerHTML, srcdoc, src, href, data, and sandbox. The template compiler omits the appropriate schema-derived sanitizer for the TwoWayProperty operation, whereas equivalent one-way bindings are correctly sanitized [1][2]. This affects all Angular versions prior to the commit that introduced the fix [3].

Exploitation

An attacker must be able to control the value bound to a sensitive property via user input, and the application must use two-way binding on that property without additional manual sanitization (e.g., via DomSanitizer) [1][2]. When these conditions are met, the attacker can supply a malicious payload that is rendered unsanitized in the browser, leading to client-side cross-site scripting (XSS) [1][2].

Impact

Successful exploitation allows arbitrary JavaScript execution in the target user's browser context. This can result in session hijacking, exposure of sensitive data, or unauthorized actions performed on behalf of the user [1][2].

Mitigation

The vulnerability is fixed in commit 3c70270 (pull request #69107), which applies sanitizer functions to two-way property bindings [3][4]. Users should update to a patched version of Angular containing this fix. As a workaround, avoid using two-way binding on sensitive properties, or apply manual sanitization with DomSanitizer before passing values [1][2].

AI Insight generated on Jun 15, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

1
3c70270c9667

fix(compiler): sanitize two-way properties

https://github.com/angular/angularSkyZeroZxJun 3, 2026via body-scan-shorthand
4 files changed · +47 3
  • packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/GOLDEN_PARTIAL.js+18 0 modified
    @@ -897,6 +897,15 @@ export class MyComponent {
         <iframe [sandbox]="evil"></iframe>
         <a href="{{evil}}{{evil}}"></a>
         <div attr.style="{{evil}}{{evil}}"></div>
    +    <div [(innerHTML)]="evil"></div>
    +    <div bindon-innerHTML="evil"></div>
    +    <iframe [(srcdoc)]="evil"></iframe>
    +    <iframe bindon-srcdoc="evil"></iframe>
    +    <img [(src)]="evil" />
    +    <iframe [(src)]="evil"></iframe>
    +    <object [(data)]="evil"></object>
    +    <link [(href)]="evil" />
    +    <iframe [(sandbox)]="evil"></iframe>
       `, isInline: true });
     }
     i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
    @@ -911,6 +920,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE
         <iframe [sandbox]="evil"></iframe>
         <a href="{{evil}}{{evil}}"></a>
         <div attr.style="{{evil}}{{evil}}"></div>
    +    <div [(innerHTML)]="evil"></div>
    +    <div bindon-innerHTML="evil"></div>
    +    <iframe [(srcdoc)]="evil"></iframe>
    +    <iframe bindon-srcdoc="evil"></iframe>
    +    <img [(src)]="evil" />
    +    <iframe [(src)]="evil"></iframe>
    +    <object [(data)]="evil"></object>
    +    <link [(href)]="evil" />
    +    <iframe [(sandbox)]="evil"></iframe>
       `
                     }]
             }] });
    
  • packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/sanitization.js+19 3 modified
    @@ -1,7 +1,5 @@
     template: function MyComponent_Template(rf, ctx) {
    -  if (rf & 1) {
    -    $r3$.ɵɵdomElement(0, "div", 0)(1, "link", 1)(2, "div")(3, "img", 2)(4, "iframe", 3)(5, "a", 1)(6, "div");
    -  }
    +  …
       if (rf & 2) {
         $r3$.ɵɵdomProperty("innerHTML", ctx.evil, $r3$.ɵɵsanitizeHtml);
         $r3$.ɵɵadvance();
    @@ -16,5 +14,23 @@ template: function MyComponent_Template(rf, ctx) {
         $r3$.ɵɵdomProperty("href", $r3$.ɵɵinterpolate2("", ctx.evil, "", ctx.evil), $r3$.ɵɵsanitizeUrl);
         $r3$.ɵɵadvance();
         $r3$.ɵɵattribute("style", $r3$.ɵɵinterpolate2("", ctx.evil, "", ctx.evil), $r3$.ɵɵsanitizeStyle);
    +    $r3$.ɵɵadvance();
    +    $r3$.ɵɵtwoWayProperty("innerHTML", ctx.evil, $r3$.ɵɵsanitizeHtml);
    +    $r3$.ɵɵadvance();
    +    $r3$.ɵɵtwoWayProperty("innerHTML", ctx.evil, $r3$.ɵɵsanitizeHtml);
    +    $r3$.ɵɵadvance();
    +    $r3$.ɵɵtwoWayProperty("srcdoc", ctx.evil, $r3$.ɵɵsanitizeHtml);
    +    $r3$.ɵɵadvance();
    +    $r3$.ɵɵtwoWayProperty("srcdoc", ctx.evil, $r3$.ɵɵsanitizeHtml);
    +    $r3$.ɵɵadvance();
    +    $r3$.ɵɵtwoWayProperty("src", ctx.evil, $r3$.ɵɵsanitizeUrl);
    +    $r3$.ɵɵadvance();
    +    $r3$.ɵɵtwoWayProperty("src", ctx.evil, $r3$.ɵɵsanitizeResourceUrl);
    +    $r3$.ɵɵadvance();
    +    $r3$.ɵɵtwoWayProperty("data", ctx.evil, $r3$.ɵɵsanitizeResourceUrl);
    +    $r3$.ɵɵadvance();
    +    $r3$.ɵɵtwoWayProperty("href", ctx.evil, $r3$.ɵɵsanitizeResourceUrl);
    +    $r3$.ɵɵadvance();
    +    $r3$.ɵɵtwoWayProperty("sandbox", ctx.evil, $r3$.ɵɵvalidateAttribute);
       }
     }
    
  • packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/sanitization.ts+9 0 modified
    @@ -10,6 +10,15 @@ import {Component} from '@angular/core';
         <iframe [sandbox]="evil"></iframe>
         <a href="{{evil}}{{evil}}"></a>
         <div attr.style="{{evil}}{{evil}}"></div>
    +    <div [(innerHTML)]="evil"></div>
    +    <div bindon-innerHTML="evil"></div>
    +    <iframe [(srcdoc)]="evil"></iframe>
    +    <iframe bindon-srcdoc="evil"></iframe>
    +    <img [(src)]="evil" />
    +    <iframe [(src)]="evil"></iframe>
    +    <object [(data)]="evil"></object>
    +    <link [(href)]="evil" />
    +    <iframe [(sandbox)]="evil"></iframe>
       `
     })
     export class MyComponent {
    
  • packages/compiler/src/template/pipeline/src/phases/resolve_sanitizers.ts+1 0 modified
    @@ -56,6 +56,7 @@ export function resolveSanitizers(job: CompilationJob): void {
             case ir.OpKind.Property:
             case ir.OpKind.Attribute:
             case ir.OpKind.DomProperty:
    +        case ir.OpKind.TwoWayProperty:
               let sanitizerFn: o.ExternalReference | null = null;
               if (
                 Array.isArray(op.securityContext) &&
    

Vulnerability mechanics

Root cause

"The Angular template compiler's sanitizer resolution phase did not handle `TwoWayProperty` operations, causing two-way bindings on sensitive DOM properties to be emitted without the required sanitizer function."

Attack vector

An attacker who can control the value bound to a security-sensitive native DOM property (such as `innerHTML`, `srcdoc`, `src`, `href`, `data`, or `sandbox`) via Angular's two-way binding syntax (`[(property)]="value"` or `bindon-property="value"`) can bypass Angular's built-in sanitization. Because the compiler failed to attach the appropriate sanitizer function (e.g., `ɵɵsanitizeHtml`, `ɵɵsanitizeUrl`) to `TwoWayProperty` operations, the raw attacker-controlled value is written directly to the DOM property without sanitization. This enables client-side Cross-Site Scripting (XSS) by injecting arbitrary HTML or JavaScript into the page.

Affected code

The vulnerability resides in the Angular template compiler's sanitizer resolution phase (`packages/compiler/src/template/pipeline/src/phases/resolve_sanitizers.ts`). The `TwoWayProperty` operation was omitted from the switch case that applies schema-derived sanitizer functions, so two-way bindings (e.g., `[(innerHTML)]`) were emitted without the sanitizer that one-way bindings (`[innerHTML]`) receive. The patch adds `case ir.OpKind.TwoWayProperty:` to that switch statement, ensuring the same sanitizer resolution logic applies to two-way property operations.

What the fix does

The patch adds `case ir.OpKind.TwoWayProperty:` to the `resolveSanitizers` function in `resolve_sanitizers.ts`, so that two-way property bindings undergo the same schema-derived sanitizer resolution as one-way property bindings (`OpKind.DomProperty`). The compliance test files (`sanitization.ts`, `sanitization.js`, `GOLDEN_PARTIAL.js`) were updated to include two-way binding templates for `innerHTML`, `srcdoc`, `src`, `data`, `href`, and `sandbox`, and the expected compiled output now shows the corresponding sanitizer functions (e.g., `ɵɵsanitizeHtml`, `ɵɵsanitizeUrl`, `ɵɵsanitizeResourceUrl`, `ɵɵvalidateAttribute`) being passed to `ɵɵtwoWayProperty`. This ensures that two-way bindings on sensitive properties are sanitized identically to their one-way counterparts.

Preconditions

  • configThe application must use two-way binding syntax (`[(property)]="..."` or `bindon-property="..."`) on a security-sensitive native DOM property such as `innerHTML`, `srcdoc`, `src`, `href`, `data`, or `sandbox`.
  • inputThe value bound to that property must be influenceable by user-controlled input (e.g., from a form field, URL parameter, or API response).
  • configThe application does not perform separate manual sanitization (e.g., via `DomSanitizer`) before passing the value to the bound property.

Generated on Jun 15, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.