diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index c3af979eb74a..5938c0a3ad59 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -8595,7 +8595,7 @@ runInEachFileSystem((os: string) => { hostVars: 6, hostBindings: function UnsafeAttrsDirective_HostBindings(rf, ctx) { if (rf & 2) { - i0.ɵɵattribute("href", ctx.attrHref, i0.ɵɵsanitizeUrlOrResourceUrl)("src", ctx.attrSrc, i0.ɵɵsanitizeUrlOrResourceUrl)("action", ctx.attrAction, i0.ɵɵsanitizeUrl)("profile", ctx.attrProfile)("innerHTML", ctx.attrInnerHTML, i0.ɵɵsanitizeHtml)("title", ctx.attrSafeTitle); + i0.ɵɵattribute("href", ctx.attrHref, i0.ɵɵsanitizeUrlOrResourceUrl)("src", ctx.attrSrc, i0.ɵɵsanitizeUrlOrResourceUrl)("action", ctx.attrAction, i0.ɵɵsanitizeUrl)("profile", ctx.attrProfile)("innerHTML", ctx.attrInnerHTML, i0.ɵɵsanitizeMaybeScript)("title", ctx.attrSafeTitle); } } `; diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 82d96103762f..a7c6a9492021 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -477,6 +477,10 @@ export class Identifiers { name: 'ɵɵsanitizeUrlOrResourceUrl', moduleName: CORE, }; + static sanitizeMaybeScript: o.ExternalReference = { + name: 'ɵɵsanitizeMaybeScript', + moduleName: CORE, + }; static trustConstantHtml: o.ExternalReference = {name: 'ɵɵtrustConstantHtml', moduleName: CORE}; static trustConstantResourceUrl: o.ExternalReference = { name: 'ɵɵtrustConstantResourceUrl', diff --git a/packages/compiler/src/schema/dom_security_schema.ts b/packages/compiler/src/schema/dom_security_schema.ts index 26a4f8bf16a9..8d0da48ec275 100644 --- a/packages/compiler/src/schema/dom_security_schema.ts +++ b/packages/compiler/src/schema/dom_security_schema.ts @@ -28,7 +28,18 @@ export function SECURITY_SCHEMA(): {[k: string]: SecurityContext} { registerContext(SecurityContext.HTML, ['iframe|srcdoc', '*|innerHTML', '*|outerHTML']); registerContext(SecurityContext.STYLE, ['*|style']); - // NB: no SCRIPT contexts here, they are never allowed due to the parser stripping them. + // Writes to text-like properties of a ``, + changeDetection: ChangeDetectionStrategy.Eager, + }) + class TestCmp { + code = '/* xss */'; + } + + expect(() => { + const fixture = TestBed.createComponent(TestCmp); + fixture.detectChanges(); + }).toThrowError(/NG0905/); + }); + + it(`should error when '${propName}' is bound on an SVG ', + imports: [ScriptHostDir], + changeDetection: ChangeDetectionStrategy.Eager, + }) + class TestCmp {} + + expect(() => { + const fixture = TestBed.createComponent(TestCmp); + fixture.detectChanges(); + }).toThrowError(/NG0905/); + }); + } + + it('should allow values trusted via DomSanitizer.bypassSecurityTrustScript', () => { + @Component({ + template: '', + changeDetection: ChangeDetectionStrategy.Eager, + }) + class TestCmp { + private readonly sanitizer = inject(DomSanitizer); + code = this.sanitizer.bypassSecurityTrustScript('/* trusted */'); + } + + const fixture = TestBed.createComponent(TestCmp); + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('script').textContent).toContain('/* trusted */'); + }); +}); diff --git a/packages/core/test/sanitization/sanitization_spec.ts b/packages/core/test/sanitization/sanitization_spec.ts index bdfbc3882666..8e8243bc0f53 100644 --- a/packages/core/test/sanitization/sanitization_spec.ts +++ b/packages/core/test/sanitization/sanitization_spec.ts @@ -118,7 +118,7 @@ describe('sanitization', () => { [SecurityContext.RESOURCE_URL, ɵɵsanitizeResourceUrl], ]); Object.entries(schema).forEach(([key, context]) => { - if (context === SecurityContext.URL || SecurityContext.RESOURCE_URL) { + if (context === SecurityContext.URL || context === SecurityContext.RESOURCE_URL) { const [tag, prop] = key.split('|'); const contexts = contextsByProp.get(prop) || new Set(); contexts.add(context);