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) &&