Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 -

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.

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 -

 /**
     * 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 -

{
      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 -

{ 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

enter image description here

If I remove this CSP, it gets render correctly -

enter image description here

like image 832
Himanshu Avatar asked Nov 09 '20 06:11

Himanshu


1 Answers

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.

So you have two options, add unsafe-eval as content source:

<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. For instance the newest angular versions uses AOT by default, even in ng serve mode.

like image 140
Poul Kruijt Avatar answered Nov 08 '22 19:11

Poul Kruijt