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.
This commit is contained in:
SkyZeroZx
2026-06-02 23:33:10 -05:00
parent 0b19c92d44
commit 9ca51ab0e4
4 changed files with 47 additions and 3 deletions
@@ -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>
`
}]
}] });
@@ -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);
}
}
@@ -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 {
@@ -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) &&