Динамически скомпилированные динамические маршруты с отложенной загрузкой в ​​Angular, вызывающие ошибку unsafe-eval

В файле index.html приложения angular после применения политики безопасности контента приложение выдает консольную ошибку 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)

Эта ошибка возникает из-за использования метода compileModuleAsync() из класса Compiler, поскольку я пытаюсь создать модуль динамически.

Если я не использую Политику безопасности контента, то приложение работает нормально и не выдает такой ошибки консоли. Ниже приведены детали политики -

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

Это routes.json, в котором я пытаюсь создать конфигурацию, написанную в loadChildren -

       {
      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, он будет правильно отображаться -

2 ответа

К сожалению, прямого пути нет. Компилятор angular JIT должен использовать new Function, а для создания динамического модуля вам понадобится JIT-компилятор.

Итак, у вас есть два варианта, добавьте unsafe-eval как источник контента:

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

Или еще раз оцените свою потребность в динамическом модуле, вернувшись к чертежной доске. В общем, рекомендуется вообще не использовать JIT из-за увеличения размера и уменьшения скорости. Например, новейшие версии angular по умолчанию используют AOT, даже в ng serve Режим.

Похоже, что причина этой проблемы - текущий недостаток 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: комбинация CSP на основе nonce с директивой strict-dynamic. Следовательно, если сценарий, которому доверено одноразовое имя, создает новый сценарий во время выполнения, этот новый сценарий также будет считаться допустимым.

поэтому, согласно рекомендации команды Angular, единственный текущий способ использования заголовка CSP - с 'unsafe-inline' и сделать некоторый рефакторинг (т.е. не использовать ленивые загруженные модули??? ужас...)

Другие вопросы по тегам