@angular/compiler: Two-Way Property Binding Sanitization Bypass (XSS)
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
1Patches
13c70270c9667fix(compiler): sanitize two-way properties
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
4News mentions
0No linked articles in our index yet.