From 358c50e2260a02ce57882601c0abd8d3f48ab3c5 Mon Sep 17 00:00:00 2001 From: Bjarki Date: Wed, 18 Nov 2020 07:40:24 +0000 Subject: [PATCH] feat(compiler): add schema for Trusted Types sinks (#39554) Create a schema with an associated function to classify Trusted Types sinks. Piggyback a typo fix. PR Close #39554 --- .../src/schema/dom_security_schema.ts | 2 +- .../src/schema/trusted_types_sinks.ts | 47 +++++++++++++++++++ .../test/schema/trusted_types_sinks_spec.ts | 34 ++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 packages/compiler/src/schema/trusted_types_sinks.ts create mode 100644 packages/compiler/test/schema/trusted_types_sinks_spec.ts diff --git a/packages/compiler/src/schema/dom_security_schema.ts b/packages/compiler/src/schema/dom_security_schema.ts index f6d4d8611c..7d1182d49a 100644 --- a/packages/compiler/src/schema/dom_security_schema.ts +++ b/packages/compiler/src/schema/dom_security_schema.ts @@ -19,7 +19,7 @@ import {SecurityContext} from '../core'; // // ================================================================================================= -/** Map from tagName|propertyName SecurityContext. Properties applying to all tags use '*'. */ +/** Map from tagName|propertyName to SecurityContext. Properties applying to all tags use '*'. */ let _SECURITY_SCHEMA!: {[k: string]: SecurityContext}; export function SECURITY_SCHEMA(): {[k: string]: SecurityContext} { diff --git a/packages/compiler/src/schema/trusted_types_sinks.ts b/packages/compiler/src/schema/trusted_types_sinks.ts new file mode 100644 index 0000000000..6b8e4bc3cf --- /dev/null +++ b/packages/compiler/src/schema/trusted_types_sinks.ts @@ -0,0 +1,47 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/** + * Set of tagName|propertyName corresponding to Trusted Types sinks. Properties applying to all + * tags use '*'. + * + * Extracted from, and should be kept in sync with + * https://w3c.github.io/webappsec-trusted-types/dist/spec/#integrations + */ +const TRUSTED_TYPES_SINKS = new Set([ + // NOTE: All strings in this set *must* be lowercase! + + // TrustedHTML + 'iframe|srcdoc', + '*|innerhtml', + '*|outerhtml', + + // NB: no TrustedScript here, as the corresponding tags are stripped by the compiler. + + // TrustedScriptURL + 'embed|src', + 'object|codebase', + 'object|data', +]); + +/** + * isTrustedTypesSink returns true if the given property on the given DOM tag is a Trusted Types + * sink. In that case, use `ElementSchemaRegistry.securityContext` to determine which particular + * Trusted Type is required for values passed to the sink: + * - SecurityContext.HTML corresponds to TrustedHTML + * - SecurityContext.RESOURCE_URL corresponds to TrustedScriptURL + */ +export function isTrustedTypesSink(tagName: string, propName: string): boolean { + // Make sure comparisons are case insensitive, so that case differences between attribute and + // property names do not have a security impact. + tagName = tagName.toLowerCase(); + propName = propName.toLowerCase(); + + return TRUSTED_TYPES_SINKS.has(tagName + '|' + propName) || + TRUSTED_TYPES_SINKS.has('*|' + propName); +} diff --git a/packages/compiler/test/schema/trusted_types_sinks_spec.ts b/packages/compiler/test/schema/trusted_types_sinks_spec.ts new file mode 100644 index 0000000000..49023439a7 --- /dev/null +++ b/packages/compiler/test/schema/trusted_types_sinks_spec.ts @@ -0,0 +1,34 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {isTrustedTypesSink} from '@angular/compiler/src/schema/trusted_types_sinks'; +import {describe, expect, it} from '@angular/core/testing/src/testing_internal'; + +{ + describe('isTrustedTypesSink', () => { + it('should classify Trusted Types sinks', () => { + expect(isTrustedTypesSink('iframe', 'srcdoc')).toBeTrue(); + expect(isTrustedTypesSink('p', 'innerHTML')).toBeTrue(); + expect(isTrustedTypesSink('embed', 'src')).toBeTrue(); + expect(isTrustedTypesSink('a', 'href')).toBeFalse(); + expect(isTrustedTypesSink('base', 'href')).toBeFalse(); + expect(isTrustedTypesSink('div', 'style')).toBeFalse(); + }); + + it('should classify Trusted Types sinks case insensitive', () => { + expect(isTrustedTypesSink('p', 'iNnErHtMl')).toBeTrue(); + expect(isTrustedTypesSink('p', 'formaction')).toBeFalse(); + expect(isTrustedTypesSink('p', 'formAction')).toBeFalse(); + }); + + it('should classify attributes as Trusted Types sinks', () => { + expect(isTrustedTypesSink('p', 'innerHtml')).toBeTrue(); + expect(isTrustedTypesSink('p', 'formaction')).toBeFalse(); + }); + }); +}