diff --git a/packages/compiler/src/output/output_jit_trusted_types.ts b/packages/compiler/src/output/output_jit_trusted_types.ts new file mode 100644 index 0000000000..242f7105c7 --- /dev/null +++ b/packages/compiler/src/output/output_jit_trusted_types.ts @@ -0,0 +1,135 @@ +/** + * @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 + */ + +/** + * @fileoverview + * A module to facilitate use of a Trusted Types policy within the JIT + * compiler. It lazily constructs the Trusted Types policy, providing helper + * utilities for promoting strings to Trusted Types. When Trusted Types are not + * available, strings are used as a fallback. + * @security All use of this module is security-sensitive and should go through + * security review. + */ + +import {global} from '../util'; + +/** + * While Angular only uses Trusted Types internally for the time being, + * references to Trusted Types could leak into our core.d.ts, which would force + * anyone compiling against @angular/core to provide the @types/trusted-types + * package in their compilation unit. + * + * Until https://github.com/microsoft/TypeScript/issues/30024 is resolved, we + * will keep Angular's public API surface free of references to Trusted Types. + * For internal and semi-private APIs that need to reference Trusted Types, the + * minimal type definitions for the Trusted Types API provided by this module + * should be used instead. + * + * Adapted from + * https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/trusted-types/index.d.ts + * but restricted to the API surface used within Angular. + */ + +export type TrustedScript = { + __brand__: 'TrustedScript' +}; + +export interface TrustedTypePolicyFactory { + createPolicy(policyName: string, policyOptions: { + createScript?: (input: string) => string, + }): TrustedTypePolicy; +} + +export interface TrustedTypePolicy { + createScript(input: string): TrustedScript; +} + + +/** + * The Trusted Types policy, or null if Trusted Types are not + * enabled/supported, or undefined if the policy has not been created yet. + */ +let policy: TrustedTypePolicy|null|undefined; + +/** + * Returns the Trusted Types policy, or null if Trusted Types are not + * enabled/supported. The first call to this function will create the policy. + */ +function getPolicy(): TrustedTypePolicy|null { + if (policy === undefined) { + policy = null; + if (global.trustedTypes) { + try { + policy = + (global.trustedTypes as TrustedTypePolicyFactory).createPolicy('angular#unsafe-jit', { + createScript: (s: string) => s, + }); + } catch { + // trustedTypes.createPolicy throws if called with a name that is + // already registered, even in report-only mode. Until the API changes, + // catch the error not to break the applications functionally. In such + // cases, the code will fall back to using strings. + } + } + } + return policy; +} + +/** + * Unsafely promote a string to a TrustedScript, falling back to strings when + * Trusted Types are not available. + * @security In particular, it must be assured that the provided string will + * never cause an XSS vulnerability if used in a context that will be + * interpreted and executed as a script by a browser, e.g. when calling eval. + */ +function trustedScriptFromString(script: string): TrustedScript|string { + return getPolicy()?.createScript(script) || script; +} + +/** + * Unsafely call the Function constructor with the given string arguments. It + * is only available in development mode, and should be stripped out of + * production code. + * @security This is a security-sensitive function; any use of this function + * must go through security review. In particular, it must be assured that it + * is only called from the JIT compiler, as use in other code can lead to XSS + * vulnerabilities. + */ +export function newTrustedFunctionForJIT(...args: string[]): Function { + if (!global.trustedTypes) { + // In environments that don't support Trusted Types, fall back to the most + // straightforward implementation: + return new Function(...args); + } + + // Chrome currently does not support passing TrustedScript to the Function + // constructor. The following implements the workaround proposed on the page + // below, where the Chromium bug is also referenced: + // https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor + const fnArgs = args.slice(0, -1).join(','); + const fnBody = args.pop()!.toString(); + const body = `(function anonymous(${fnArgs} +) { ${fnBody} +})`; + + // Using eval directly confuses the compiler and prevents this module from + // being stripped out of JS binaries even if not used. The global['eval'] + // indirection fixes that. + const fn = global['eval'](trustedScriptFromString(body) as string) as Function; + + // To completely mimic the behavior of calling "new Function", two more + // things need to happen: + // 1. Stringifying the resulting function should return its source code + fn.toString = () => body; + // 2. When calling the resulting function, `this` should refer to `global` + return fn.bind(global); + + // When Trusted Types support in Function constructors is widely available, + // the implementation of this function can be simplified to: + // return new Function(...args.map(a => trustedScriptFromString(a))); +}