57

After upgrading to Angular 12 my Content Security Policy blocks styles from loading correctly.

The Angular 12 devkit seems to add a new inline event handler to the CSS bundle reference in index.html, example below.

<link rel="stylesheet" href="styles.5951e4ca367b697db091.css" crossorigin="anonymous" integrity="sha384-2031E8+oC87S0N7NzRGcF8fqx777KEJOgQ3KcUN4aX6xsR3BVaV5sh4fibR5joOc" media="print" onload="this.media='all'">

Error

Refused to execute inline event handler because it violates the following Content Security Policy directive...

This seems to be related to this RFC: https://github.com/angular/angular-cli/issues/18730 but I can't find any more information about how to use it with a strict (no 'unsafe-inline') CSP.

Alex Moss
  • 571
  • 1
  • 4
  • 5
  • I think the best solution for now is to add sha256 hash for that super simple script `this.media='all'`. see: https://github.com/angular/angular-cli/issues/20864#issuecomment-983672336 – Michal.S Feb 01 '22 at 08:23

2 Answers2

95

I had the same problem after Angular 12 upgrade.

The solution for me was to set the workspace option optimization "inlineCritical" to false. The inlineCritical option was changed in Angular 12 to default true to improve First Contentful Paint, see https://angular.io/guide/workspace-config#styles-optimization-options

here is an example workspace configuration

"project": {
  "architect": {
    "build": {
      "configurations": {
        "production": {
          "optimization": {
            "scripts": true,
            "styles": {
              "minify": true,
              "inlineCritical": false
            },
            "fonts": true
          }
        }
      }
    }
  }
},
Michael Hubeny
  • 950
  • 3
  • 3
  • 1
    Great! Thank you for sharing this, it solved exactly the same problem described by @alex – Marc Thomann May 18 '21 at 16:39
  • Cheers. I thought I had tried that but we have a somewhat complicated setup with different settings for the Angular Universal SSR build - must have got something wrong. The next question is how do we enable inlining critical styles while using CSP? Need to be able to provide a nonce for the inline script. – Alex Moss May 18 '21 at 16:50
  • 2
    My issue disabling this option was because there is a separate option for Angular Universal using Express e.g. `ngExpressEngine({ bootstrap: AppServerModule, inlineCriticalCss: false });` The docs for this option still say it defaults to false, but that appears not to be the case. – Alex Moss May 19 '21 at 07:39
  • 2
    Would've been nice if they'd actually defined what 'critical' meant. For instance I have some css that is critical for preloading '/assets/preload/preload.css' so why isn't that inlined? – Simon_Weaver Jun 10 '21 at 02:46
  • 1
    Thank you so much for this answer. This fixed my issue completely. – Allen Jul 17 '21 at 16:36
  • 1
    Thanks for the answer, solved my very similar problem! – bikeguy Sep 21 '21 at 17:48
  • 1
    This schould be marked as the correct answer. – derBndr Dec 13 '21 at 07:41
  • 1
    Don't forget (like I did) to make this modification for each environment that needs it! – Brandon Rader May 03 '22 at 16:02
  • I just get this issue after migrating one Angular project to version 12 and This answer is very helpful. Just add on one link to Angular github issue answer https://github.com/angular/angular-cli/issues/20864#issuecomment-844823912 – Hoang Subin Aug 12 '22 at 07:21
18

ATTENTION! Answer updated

UPDATED ANSWER (July 16th 2021)

The original solution posted below breaks Safari (MacOS) so we had to rollback and disable optimization.styles.inlineCritical.

Also, as commented by masterfloda below, the choice should be security over performance in most if not all cases. My original thinking was that it would be difficult to exploit (unsafe-hashes), but I have not done a thorough InfoSec risk scoring on that.

There is a ticket open for this on the Angular repo - #20864. I suggest you disable optimization.styles.inlineCritical for now and like and subscribe to that issue.


ORIGINAL ANSWER (June 9th 2021)

Yes, this issue was introduced by Angular v12's Styles optimization options (inlineCritical: true) which adds an onload event handler to the link tag for the main stylesheet as follows:

<link rel="stylesheet" href="styles.<hash>.css" media="all" onload="this.media='all'">

It can be solved without disabling inlineCritical or enabling 'unsafe-inline' in the CSP, as follows:

  • Hash the contents of the onload handler: this.media='all'
  • Report URI has a handy tool for that: Script And Style Hasher
  • Add the hash to the script-src directive in the CSP
  • For Chrome (but not for FF), you also need to add 'unsafe-hashes' to the script-src directive, otherwise it blocks with the following log in the console: Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present
John Hannon
  • 189
  • 6
  • 4
    I don't think this is a good answer for security reasons: I rather not optimize for First Contentful Paint than allowing `unsafe-hashes` in my CSP. – masterfloda Jul 13 '21 at 15:01
  • 1
    Thanks @masterfloda, you are right, unless the potential for an exploit is low enough for the tradeoff to be worth it (likely not). I've updated the answer. – John Hannon Jul 16 '21 at 07:42
  • 2
    I've done a risk scoring for use of unsafe-hashes. While the impact of an attack is high (e.g. session hijack due to disclosure of user's session token), the likelihood is low as long as you have adequate XSS protection (e.g. CSP no 'unsafe-inline', HTML endcode + sanitise on the render). An XSS injection vulnerability must exist and be discovered by the attacker before they will be able to exploit unsafe-hashes. – John Hannon Aug 13 '21 at 08:03
  • https://stackoverflow.com/a/69336136/1440240 – Ben Racicot Sep 26 '21 at 14:59
  • Worked perfectly for me, thanks! Here's my script directive for `this.media='all'`: `script-src 'unsafe-hashes' 'sha256-MhtPZXr7+LpJUY5qtMutB+qWfQtMaPccfe7QXtCcEYc=' 'self'` – Hadrien01 May 16 '23 at 08:54