44

I cannot make base Angular2 (final) application works with the following restrictive CSP.

default-src 'none';
script-src 'self';
style-src 'self';
font-src 'self';
img-src 'self' data:;
connect-src 'self'

There are one unsafe-eval error in lang.js and two in zone.js. Could you provide a solution ?

Step to reproduce with Angular CLI

I have created a GitHub repository. You can also follow the instructions below.

Use the last Angular CLI with Webpack 6.0.8 and the new application created with the instructions below.

ng new csp-test

Insert in the index.html the meta tag defining the following restrictive Content Security Policy.

<meta 
  http-equiv="Content-Security-Policy" 
  content="default-src 'none';script-src 'self';style-src 'self';font-src 'self';img-src 'self' data:;connect-src 'self'">

Then serve the application.

ng serve

Access http://localhost:4200/, the page does not load since scripts are blocked by CSP.

Errors

Error in Chrome

lang.js

lang.js:335 Uncaught 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: "script-src 'self'".

with the source code.

335: return new (Function.bind.apply(Function, [void 0].concat(fnArgNames.concat(fnBody))))().apply(void 0, fnArgValues);

zone.js

zone.js:344 Unhandled Promise rejection: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".
 ; Zone: <root> ; Task: Promise.then ; Value: 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: "script-src 'self'".

zone.js:346 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: "script-src 'self'".(…)

with the source code.

343: if (rejection) {
344:     console.error('Unhandled Promise rejection:', rejection instanceof Error ? rejection.message : rejection, '; Zone:', e.zone.name, '; Task:', e.task && e.task.source, '; Value:', rejection, rejection instanceof Error ? rejection.stack : undefined);
345: }
346: console.error(e);
Yuri
  • 4,254
  • 1
  • 29
  • 46
Nicolas Henneaux
  • 11,507
  • 11
  • 57
  • 82
  • Are you by chance, using generated values in template? For example something like `` or ``? Angular doesn't allow unsafe values as protection against XSS – Scrambo Aug 11 '16 at 13:25

5 Answers5

23

Edited answer for @angular/cli>=8.2

From this Github thread, one can use the index property in angular.json to control the generation of the application's HTML index:

build: {
  ...
  "configurations": {
    "production": {
      "index": {
        "input": "src/index.production.html",
         "output": "index.html"
       },
      ...
    }
  }
}

Original answer

I've found a way to have restrictive CSP on my production environment while still being able to use the JTI compliler for development.

  • Add a second file: index.production.html to the src folder.
  • Copy the contents of index.html to that file, and add the restrictive CSP header.
<meta http-equiv="Content-Security-Policy" 
content="default-src 'none';
  frame-src 'self';
  script-src 'self';
  style-src 'self' 'unsafe-inline';
  font-src 'self';
  img-src 'self' data:;
  connect-src 'self'">
  • Then, add to your angular.json the following:
build: {
  ...
  "configurations": {
    "production": {
      "fileReplacements": [
        {
          "replace": "src/index.html",
          "with": "src/index.production.html"
        }
      ],
      ...
    }
  }
}

This makes sure that when you run a production build, it will use the index.production.html with the restrictive CSP, and when you're running it locally, you can use the JTI compiler.

Blockost
  • 493
  • 1
  • 7
  • 18
Jesse
  • 3,522
  • 6
  • 25
  • 40
  • 4
    This is a great solution but is not working anymore. `fileReplacements` property doesn't take into account non-bundled files like `index.html`. Updated solution is https://github.com/angular/angular-cli/issues/14599#issuecomment-527131237 – Blockost Jan 07 '20 at 11:07
  • This combined with the updated solution in the above comment sort of worked for me. I was able to add the CSP meta into my prod build, but securityheaders.com still says I am missing them, also many (not all) of my woff2 files are now denied? – Eric Soyke Jun 19 '20 at 21:20
  • 1
    I had to add `'unsafe-inline'` to `script-src` to make it work (Angular 12) – Christophe Le Besnerais Aug 11 '21 at 08:54
  • Adding `'unsafe-inline'` to `'script-src'` doesn't break with the objective of `'Content-Security-Policy'`. Can't Angular compile and add all the script hashes to the `CSP` header? – IsaacCampos Aug 03 '22 at 21:55
5

Using ahead-of-time compilation solves the problem. The following command can be used to build an application working with restrictive CSP.

ng build --prod

To test it locally you can use

ng serve --prod
Nicolas Henneaux
  • 11,507
  • 11
  • 57
  • 82
  • 8
    When you run ng serve: This is a simple server for use in testing or debugging Angular applications locally. It hasn't been reviewed for security issues. **DON'T USE IT FOR PRODUCTION!** – chris31389 Jan 29 '19 at 09:02
  • 1
    Even using ng build --prod i.e. generating AOT builds, won't solve the CSP problem. – saikarthik parachi Nov 29 '21 at 20:41
3

Using the offline template compiler should fix this.

http://www.syntaxsuccess.com/viewarticle/offline-compilation-in-angular-2.0 https://github.com/angular/angular/issues/1744

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
0

This problem happened to me with Angular 9 deployed in a WildFly Server. the bug was in the WildFly Server configuration, in the response-header setted in the standalone.xml and it wasn`t in the index.html as I thougth.

standalone.xml:

<filters>
    <response-header name="server-header" header-name="Server" header-value="JBoss-EAP/7"/>
    <response-header name="x-powered-by-header" header-name="X-Powered-By" header-value="Undertow/1"/>
    >>> Here >>> <response-header name="Content-Security-Policy" header-name="Content-Security-Policy" header-value="default-src 'self'; style-src 'self' 'unsafe-inline'"/>
    <response-header name="x-frame-options" header-name="X-Frame-Options" header-value="SAMEORIGIN"/>
    <response-header name="x-xss-protection" header-name="X-XSS-Protection" header-value="1; mode=block"/>
    <response-header name="x-content-type-options" header-name="X-Content-Type-Options" header-value="nosniff"/>
    <response-header name="strict-transport-security" header-name="Strict-Transport-Security" header-value="max-age=31536000; includeSubDomains;"/>
    <response-header name="my-custom-header" header-name="my-custom-header" header-value="my-custom-value"/>
</filters>

Note: restart WildFly Server:

./jboss-cli.sh --connect command=:reload

jboss-cli is located into bin folder of WildFly Server:

ssuperczynski
  • 3,190
  • 3
  • 44
  • 61
CrgioPeca88
  • 973
  • 7
  • 12
0

Note, I got this from the Angular 10 docs, here: https://angular.io/guide/security

I solved this problem when I added this header on server responses.

Content-Security-Policy: trusted-types angular; require-trusted-types-for 'script';
activedecay
  • 10,129
  • 5
  • 47
  • 71