[英]Dynamically compiled lazy loaded dynamic routes in Angular causing 'unsafe-eval' error
In the index.html file of the angular application after applying the Content Security Policy, the application is giving 'unsafe-eval' console error as below -在应用内容安全策略后,在 angular 应用程序的 index.html 文件中,应用程序给出了“unsafe-eval”控制台错误,如下所示 -
core.js:4442 ERROR Error: Uncaught (in promise): EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "default-src 'self'".
EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "default-src 'self'".
at new Function (<anonymous>)
at JitEvaluator.evaluateCode (compiler.js:6740)
at JitEvaluator.evaluateStatements (compiler.js:6714)
at CompilerFacadeImpl.jitExpression (compiler.js:19300)
at CompilerFacadeImpl.compileNgModule (compiler.js:19238)
at Function.get (core.js:25864)
at getNgModuleDef (core.js:1853)
at new NgModuleFactory$1 (core.js:24270)
at Compiler_compileModuleSync__POST_R3__ (core.js:27085)
at Compiler_compileModuleAsync__POST_R3__ [as compileModuleAsync] (core.js:27090)
at resolvePromise (zone-evergreen.js:798)
at resolvePromise (zone-evergreen.js:750)
at zone-evergreen.js:860
at ZoneDelegate.invokeTask (zone-evergreen.js:399)
at Object.onInvokeTask (core.js:27483)
at ZoneDelegate.invokeTask (zone-evergreen.js:398)
at Zone.runTask (zone-evergreen.js:167)
at drainMicroTaskQueue (zone-evergreen.js:569)
This error is getting caused by using the compileModuleAsync() method from Compiler class as I am trying to build the module dynamically.当我尝试动态构建模块时,此错误是由于使用Compiler类中的compileModuleAsync()方法引起的。
If I don't use the Content Security Policy, then the application works fine and it doesn't give such console error.如果我不使用内容安全策略,则应用程序运行良好,并且不会出现此类控制台错误。 Below is the policy details -以下是政策详情——
<meta http-equiv="Content-Security-Policy" content="default-src 'self';" />
As per the observation from callstack, the below function part of Angular Framework uses new Function()
expression and leads to security issue -根据调用堆栈的观察,Angular Framework 的以下函数部分使用new Function()
表达式并导致安全问题 -
/**
* Evaluate a piece of JIT generated code.
* @param sourceUrl The URL of this generated code.
* @param ctx A context object that contains an AST of the code to be evaluated.
* @param vars A map containing the names and values of variables that the evaluated code might
* reference.
* @param createSourceMap If true then create a source-map for the generated code and include it
* inline as a source-map comment.
* @returns The result of evaluating the code.
*/
evaluateCode(sourceUrl, ctx, vars, createSourceMap) {
let fnBody = `"use strict";${ctx.toSource()}\n//# sourceURL=${sourceUrl}`;
const fnArgNames = [];
const fnArgValues = [];
for (const argName in vars) {
fnArgValues.push(vars[argName]);
fnArgNames.push(argName);
}
if (createSourceMap) {
// using `new Function(...)` generates a header, 1 line of no arguments, 2 lines otherwise
// E.g. ```
// function anonymous(a,b,c
// /**/) { ... }```
// We don't want to hard code this fact, so we auto detect it via an empty function first.
const emptyFn = new Function(...fnArgNames.concat('return null;')).toString();
const headerLines = emptyFn.slice(0, emptyFn.indexOf('return null;')).split('\n').length - 1;
fnBody += `\n${ctx.toSourceMapGenerator(sourceUrl, headerLines).toJsComment()}`;
}
const fn = new Function(...fnArgNames.concat(fnBody));
return this.executeFunction(fn, fnArgValues);
}
This is the routes.json in which I am trying to build configuration written in the loadChildren -这是我试图构建在 loadChildren 中编写的配置的 routes.json -
{
path: '',
componentName: 'dummy',
children: [
{
path: '',
pathMatch: 'full',
redirectTo: 'set-focus-action',
},
{
path: 'set-focus-action',
loadChildren: {
routes: [
{
path: '',
componentName: 'dynamicType1',
},
],
},
},
],
}
Below is the code to build the module -以下是构建模块的代码 -
private featureModule(loadChildren: string): Observable<Type<any>> {
return this.getRoutes(loadChildren).pipe(
switchMap((routesConfig) => {
const module = NgModule(this.createFeatureModule(routesConfig))(
class {}
);
return from(this.compiler.compileModuleAsync(module));
}),
map((m) => {
return m.moduleType;
})
);
}
Also, I am using JitCompilerFactory for this compiler -另外,我正在为此编译器使用 JitCompilerFactory -
{ provide: COMPILER_OPTIONS, useValue: {}, multi: true },
{
provide: CompilerFactory,
useClass: JitCompilerFactory,
deps: [COMPILER_OPTIONS],
},
{
provide: Compiler,
useFactory: createCompiler,
deps: [CompilerFactory],
}
Please let me know in-case any other details.如果有任何其他细节,请告诉我。 Any suggestions would be really helpful.任何建议都会非常有帮助。
Below is a link for stackblitz where this issue is getting reproducible https://stackblitz.com/github/HimanshuGoel/unsafe-eval-issue?file=src%2Findex.html以下是 stackblitz 的链接,此问题正在重现https://stackblitz.com/github/HimanshuGoel/unsafe-eval-issue?file=src%2Findex.html
If I remove this CSP, it gets render correctly -如果我删除此 CSP,它会正确呈现 -
There is unfortunately no direct way around it.不幸的是,没有直接的方法可以解决它。 The angular JIT compiler needs to use new Function
, and to generate a dynamic module, you need the JIT compiler. angular JIT 编译器需要使用new Function
,而要生成动态模块,则需要 JIT 编译器。
So you have two options, add unsafe-eval
as content source:所以你有两个选择,添加unsafe-eval
作为内容源:
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-eval';" />
Or re-evaluate your need for a dynamic module by heading back to the drawing board.或者通过返回绘图板重新评估您对动态模块的需求。 In general it is advised to not use JIT at all, because of the size increase and speed reduction it brings.一般来说,建议根本不要使用 JIT,因为它带来的大小增加和速度降低。 For instance the newest angular versions uses AOT by default, even in ng serve
mode.例如,最新的 angular 版本默认使用 AOT,即使在ng serve
模式下也是如此。
It seems the reason for this issue is a current Angular deficiency似乎这个问题的原因是当前的 Angular 缺陷
This is a minimalistic reproduction of the issue.这是该问题的简约再现。 All we need is to add the CSP meta tag to the page head on the standard stackblitz app:我们所需要的只是将 CSP 元标记添加到标准 stackblitz 应用程序的页面头部:
<meta http-equiv="Content-Security-Policy" content="default-src 'self';" />
Support for CSP would be provided by a Webpack configuration Webpack 配置将提供对 CSP 的支持
webpack is capable of adding
nonce
to all scripts that it loads webpack 能够将nonce
添加到它加载的所有脚本中
however, this is not currently supported by angular :但是, angular 目前不支持此功能:
Ahead-of-Time (AOT) compilation (aka ng build --prod) separates out all JavaScript code from the index.html file.提前 (AOT) 编译(又名 ng build --prod)从 index.html 文件中分离出所有 JavaScript 代码。 Unfortunately, processing of the CSS is not as neat and styles remain inline in all the components (see this ticket for tracking).不幸的是,CSS 的处理并不那么整洁,并且样式在所有组件中都保持内联(请参阅此标签以进行跟踪)。 So, we have to put up with unpleasant style-src 'unsafe-inline'.所以,我们不得不忍受令人不快的 style-src 'unsafe-inline'。
As for the scripts, 'unsafe-inline' is also required if we want plugins to work.至于脚本,如果我们想让插件工作,还需要'unsafe-inline'。 There will be a way with angular/angular#26152 though: a combination of nonce-based CSP with strict-dynamic directive. angular/angular#26152会有一种方法:基于随机数的 CSP 与严格动态指令的组合。 Hence, if a script trusted with a nonce creates a new script at runtime, this new script will also be considered legitimate.因此,如果受随机数信任的脚本在运行时创建了一个新脚本,这个新脚本也将被认为是合法的。
so as per team Angular's recommendation the only current way to use the CSP header is with 'unsafe-inline'
and do some refactoring (ie not using lazy loaded modules??? the horror...)因此,根据团队 Angular 的建议,当前使用 CSP 标头的唯一方法是使用'unsafe-inline'
并进行一些重构(即不使用延迟加载模块???恐怖...)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.