6

在应用内容安全策略后,在 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)

当我尝试动态构建模块时,使用Compiler类中的compileModuleAsync()方法会导致此错误。

如果我不使用内容安全策略,那么应用程序可以正常工作并且不会给出这样的控制台错误。以下是政策详情——

<meta http-equiv="Content-Security-Policy" content="default-src 'self';" />

根据调用堆栈的观察,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);
    }

这是我试图构建用 loadChildren 编写的配置的 routes.json -

{
      path: '',
      componentName: 'dummy',
      children: [
        {
          path: '',
          pathMatch: 'full',
          redirectTo: 'set-focus-action',
        },
        {
          path: 'set-focus-action',
          loadChildren: {
            routes: [
              {
                path: '',
                componentName: 'dynamicType1',
              },
            ],
          },
        },
      ],
    }

以下是构建模块的代码 -

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;
      })
    );
  }

另外,我正在为这个编译器使用 JitCompilerFactory -

{ provide: COMPILER_OPTIONS, useValue: {}, multi: true },
        {
          provide: CompilerFactory,
          useClass: JitCompilerFactory,
          deps: [COMPILER_OPTIONS],
        },
        {
          provide: Compiler,
          useFactory: createCompiler,
          deps: [CompilerFactory],
        }

如果有任何其他细节,请告诉我。任何建议都会非常有帮助。

以下是 stackblitz 的链接,此问题可重现 https://stackblitz.com/github/HimanshuGoel/unsafe-eval-issue?file=src%2Findex.html

在此处输入图像描述

如果我删除此 CSP,它会正确渲染 -

在此处输入图像描述

4

2 回答 2

6

不幸的是,没有直接的解决方法。角度 JIT 编译器需要使用new Function,要生成动态模块,您需要 JIT 编译器。

所以你有两个选择,添加unsafe-eval为内容源:

<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-eval';" />

或者返回绘图板重新评估您对动态模块的需求。一般来说,建议不要使用 JIT,因为它带来的大小增加和速度降低。例如,最新的 Angular 版本默认使用 AOT,即使在ng serve模式下也是如此。

于 2020-11-11T07:40:47.057 回答
0

似乎这个问题的原因是当前的 Angular 缺陷

这是该问题的简约再现。我们需要的只是将 CSP 元标记添加到标准 stackblitz 应用程序的页头:

<meta http-equiv="Content-Security-Policy" content="default-src 'self';" />

对 CSP 的支持将由Webpack 配置提供

webpack 能够添加nonce到它加载的所有脚本

但是,目前不支持 angular

提前 (AOT) 编译(又名 ng build --prod)将所有 JavaScript 代码从 index.html 文件中分离出来。不幸的是,CSS 的处理不是那么整洁,并且样式在所有组件中保持内联(请参阅此票以进行跟踪)。所以,我们不得不忍受令人不快的 style-src 'unsafe-inline'。

至于脚本,如果我们想让插件工作,'unsafe-inline' 也是必需的。不过angular/angular#26152会有一种方法 :将基于 nonce 的 CSP 与 strict-dynamic 指令相结合。因此,如果一个受 nonce 信任的脚本在运行时创建了一个新脚本,这个新脚本也将被认为是合法的。

因此,根据团队 Angular 的建议,当前使用 CSP 标头的唯一方法是使用'unsafe-inline'并进行一些重构(即不使用延迟加载的模块???恐怖......)

于 2020-11-11T11:57:57.673 回答