From 9ca51ab0e450aeafebfb959b00df9cecf4a40221 Mon Sep 17 00:00:00 2001 From: SkyZeroZx <73321943+SkyZeroZx@users.noreply.github.com> Date: Tue, 2 Jun 2026 23:33:10 -0500 Subject: [PATCH] fix(compiler): sanitize two-way properties Apply schema-derived sanitizer resolution to TwoWayProperty ops so native two-way DOM bindings emit the same sanitizer as one-way property bindings. Add compiler compliance coverage for innerHTML, srcdoc, URL, resource URL, and security-sensitive attribute cases. --- .../property_bindings/GOLDEN_PARTIAL.js | 18 +++++++++++++++ .../property_bindings/sanitization.js | 22 ++++++++++++++++--- .../property_bindings/sanitization.ts | 9 ++++++++ .../pipeline/src/phases/resolve_sanitizers.ts | 1 + 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/GOLDEN_PARTIAL.js index e006527a27e8..e4b8e4d946a3 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/GOLDEN_PARTIAL.js @@ -897,6 +897,15 @@ export class MyComponent {
+
+
+ + + + + + + `, 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
+
+
+ + + + + + + ` }] }] }); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/sanitization.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/sanitization.js index dc3f88510887..90cd5d37f34a 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/sanitization.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/sanitization.js @@ -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); } } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/sanitization.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/sanitization.ts index ece0f51a4c02..560367c78d73 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/sanitization.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/sanitization.ts @@ -10,6 +10,15 @@ import {Component} from '@angular/core';
+
+
+ + + + + + + ` }) export class MyComponent { diff --git a/packages/compiler/src/template/pipeline/src/phases/resolve_sanitizers.ts b/packages/compiler/src/template/pipeline/src/phases/resolve_sanitizers.ts index 7396cbb1b224..c1b9ba3c6f44 100644 --- a/packages/compiler/src/template/pipeline/src/phases/resolve_sanitizers.ts +++ b/packages/compiler/src/template/pipeline/src/phases/resolve_sanitizers.ts @@ -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) &&