From 5913e5c4e883e91a21dfc970be7c055a9151c30a Mon Sep 17 00:00:00 2001 From: Bjarki Date: Fri, 9 Oct 2020 11:57:32 +0000 Subject: [PATCH] feat(core): add Trusted Types workaround for Function constructor (#39209) Chrome currently does not support passing TrustedScript to the Function constructor, and instead fails with a Trusted Types violation when called. As the Function constructor is used in a handful of places within Angular, such as in the JIT compiler and named_array_type, the workaround proposed on the following page is implemented: https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor To be precise, it constructs a string representing an anonymous function in a way that is equivalent to what the Function constructor does, promotes it to a TrustedScript and then calls eval. To facilitate backwards compatibility, new Function is used directly in environments that do not support Trusted Types. PR Close #39209 --- .../core/src/util/security/trusted_types.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/packages/core/src/util/security/trusted_types.ts b/packages/core/src/util/security/trusted_types.ts index 2b7fdfaf45..94ca1acbe0 100644 --- a/packages/core/src/util/security/trusted_types.ts +++ b/packages/core/src/util/security/trusted_types.ts @@ -85,3 +85,49 @@ export function trustedScriptFromString(script: string): TrustedScript|string { export function trustedScriptURLFromString(url: string): TrustedScriptURL|string { return getPolicy()?.createScriptURL(url) || url; } + +/** + * 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 development code, as use in production code can lead to + * XSS vulnerabilities. + */ +export function newTrustedFunctionForDev(...args: string[]): Function { + if (typeof ngDevMode === 'undefined') { + throw new Error('newTrustedFunctionForDev should never be called in production'); + } + 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))); +}