angular-cli: inlineCritical CSS asynchronous loading method breaks with CSP

Bug Report

Affected Package

@angular/cli

Is this a regression?

Nay

Description

The method used for inlining critical css and asynchronously loading it, breaks and doesn’t load the external stylesheet when you have a content security policy that doesn’t include script-src 'unsafe-inline'. As the name suggests this isn’t a very secure way of operating for various reasons such as script injection.

You can fix by disabling inlineCritical in the optimizations - however maybe there is a better way to load the styles, maybe in a JS file?

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

It’s the onload="this.media='all'" that breaks it

Minimal Reproduction

https://github.com/jpduckwo/ng12-csp-issue

run: ng serve

Exception or Error

Refused to execute inline event handler because it violates the following Content Security Policy directive: “default-src ‘self’”. Either the ‘unsafe-inline’ keyword, a hash (‘sha256-…’), or a nonce (‘nonce-…’) is required to enable inline execution. Note also that ‘script-src’ was not explicitly set, so ‘default-src’ is used as a fallback. image

Your Environment

Angular Version:


@angular-devkit/architect       0.1200.1
@angular-devkit/build-angular   12.0.1
@angular-devkit/core            12.0.1
@angular-devkit/schematics      12.0.1
@schematics/angular             12.0.1
rxjs                            6.6.7
typescript                      4.2.4

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 101
  • Comments: 36 (2 by maintainers)

Commits related to this issue

Most upvoted comments

Found how to disable the inlineCritical styles https://github.com/angular/angular-cli/commit/aa3ea885ed69cfde0914abae547e15d6d499a908 https://angular.io/guide/workspace-config#styles-optimization-options In angular.json in the build configuration Instead of

"optimization": true

put

"optimization": {
  "scripts": true,
  "styles": {
    "minify": true,
    "inlineCritical": false
  },
  "fonts": true
},

disable the inline css feature, OR make our CSP more insecure

Just disable inline CSS.

External stylesheets worked for dial-up internet, IDK why people insist on complicating the most simple things when speeds are 100x what they used to be.

EDIT: Enjoy your bugs.

@alan-agius4 and maybe others…

What is the status of this issue? What i understand is that we have to disable the inline css feature, OR make our CSP more insecure. Both dont sound like great solutions.

Will there be a good fix to not require us to either sacrifice perf or security? If not, will there be documentation for angular with a CSP guide on how to create a Security performance balance?

We can probably solve this by changing the preload strategy of critters from media to default.

Let me reach out to the Chrome team so see if there are any drawbacks of doing so.

@sander1095 One work-around is of course to add script-src 'unsafe-hashes' 'sha256-MhtPZXr7+LpJUY5qtMutB+qWfQtMaPccfe7QXtCcEYc=' This is not really more insecure, as it only allows the this.media='all' script to execute, which is the culprit of this issue.

The workaround from @tiberiuzuld fixed the issue for me.

It might be a good idea to set up an integration test with a restrictive CSP and a basic app generated from the cli. In this way this sort of issue could be caught early. Angular should be able to work with this:

default-src 'none';script-src 'self';connect-src 'self';style-src 'self';form-action 'self';base-uri 'self';img-src 'self' data:;font-src 'self';

@alan-agius4 another option would be to have the JS bundle itself flip those preloads to stylesheets (as a backup). It could scan for the preload/disabled <link> elements, add an onload listener, then force a synchronous load event if the sheet has already been loaded:

[].forEach.call(document.querySelectorAll('link[rel="stylesheet"][media="print"]'), n => {
  n.onload = () => n.media='';
  n.href += ''; // onload if not already loading
});

We were facing the same issue. We had 2 optimization flags. The first build.options.optimization was set to false. I initially changed the 2nd one with the production configuration. The workaround didn’t work. I then changed the first one (initially at false) to the workaround and it started working.

"build": {
  ...
  "options": {
    ...
	"optimization": {   // Originally set to false.
	  "scripts": true,
	  "styles": {
		"minify": true,
		"inlineCritical": false
	  },
	  "fonts": true
	},
	...
  },
  "configurations": {
	"production": {
		...
	  "optimization": {   // Originally set to true.
		"scripts": true,
		"styles": {
		  "minify": true,
		  "inlineCritical": false
		},
		"fonts": true
	  },
	  ...
    }
  }

Not sure if it’s a bug, but I would expect optimization false to also implies inlineCritical:false. At least, it’s working now.

Hello, I’m on Angular 13.0.2 and I have the same problem as @pacocom. I tried to apply the fix to other config areas of my angular config (as suggested by @jpduckwo ) but still the same error. Any ideas?

An interesting article with an overview of this concept :

The necessary change, to actually skip the onload event listener in the ssr html is to provide the additional inlineCriticalCss property when using the ngExpressEngine:

server.engine('html', ngExpressEngine({ bootstrap: AppServerModule, inlineCriticalCss:false }))

@rickz21 try putting the optimizations in the build > options portion of the JSON

e.g.

...
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "assets": [
...
              "src/assets"
            ],
            "styles": [
              "src/styles.scss"
            ],
            "scripts": [],
            "outputHashing": "all",
            "optimization": {
              "scripts": true,
              "styles": {
                "minify": true,
                "inlineCritical": false
              },
              "fonts": true
            },
...

I also faced this issue. There are any options to disable this inline of critical css?

I also faced this.

@AElmoznino that is the correct solution to restore the previous behaviour. However there may be multiple "optimization": true statements in your angular.json that you need to update. E.g. multiple configs for test / production etc.

@alan-agius4 I think the “body” mode could work - it injects styles at the <link> location, then adds the <link rel=stylesheet href=css> just before </body>.