angular-cli
angular-cli copied to clipboard
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.
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
I also faced this.
I also faced this issue. There are any options to disable this inline of critical css?
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
},
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.
@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>
.
@developit, thanks for the input. I had a chat with @janicklas-ralph, and mentioned that the default is idea although in some cases it can break CSS because of different CSS ordering. I’ll investigate a bit more.
@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
});
Found how to disable the inlineCritical styles aa3ea88 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 },
I tried @tiberiuzuld's solution but didn't get it working. Any other workarounds?
@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.
Thanks @jpduckwo, I didn't notice that 2nd optimization statement, got it working now!
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';
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.
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 }))
Just came across this situation with 12.2.7 where inlineCritical
is causing my app's global stylesheet to not be applied.
Despite having no print MQs this default optimization setting is adding media=print
to the link.
<link rel="stylesheet" ... href="styles.css" media="print">
"architect": ...
"build": ...
"configurations": {
"dev": {
...
"optimization": {
"scripts": true,
"styles": {
"minify": true,
"inlineCritical": false <--- fix
},
"fonts": true
}
I added this as an answer to an older StackOverflow question if anyone is interested.
I have similar but not equal problem
Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-eval'". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present.
"configurations": {
"production": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "6kb"
}
],
"optimization": {
"scripts": true,
"styles": {
"minify": true,
"inlineCritical": false
},
"fonts": true
},
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"serviceWorker": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
}
},
Hi @pacocom, it looks like the same problem. Maybe you need to apply the fix to other config areas of you angular config. Look for other "optimization"
settings in your angular.json that are set to something other than false.
It works now. Thanks.
@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?
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.
@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.
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?
Can somebody point to the logic which extracts the 'critical css' and then renders it as inline? I mean maybe it's possible to customize that to include CSP nonces you have defined somewhere.
I am trying to apply fix in Angular 13. Please post angular.json with fix. I changed it as suggested. But, I still have
body style="overflow: auto; position: static; touch-action: auto;"
I didn't put that inline style there in the body tag. I guess Angular wants a quick page paint.
Please post angular.json with fix. My angular.json file is the following. But, it doesn't stop inlining.
angularjson.txt
@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
},
...
@jpduckwo thank you trying to help me. I did paste in the lines you posted within build.options but it didn't work for me. Angular still inserted a style attribute into my page's body tag.
body style="overflow: auto; position: static; touch-action: auto;"
In the CSP report, Chrome and Edge browsers suggested that I use
style-src 'self' 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=';
and that does work to avoid that violation. Also, I am depending on ngx-image-cropper. That package inserts some inline styling as well. That styling seems to change with each page request. So, it doesn't seem possible to use a hash for them. Hopefully, Angular 14 will work better for me. Thanks again.
I read
https://github.com/angular/angular/issues/6361
https://github.com/angular/components/issues/24633
and see that there are different opinions.
https://0xdbe.github.io/AngularSecurity-DisableInlineCriticalCSS/
@jdavidhermoso I tried that configuration. It didn't stop Angular from adding inline styling to my body tag.
Hey @rickz21 the style tag on the body isn't related to this issue. This one is about angular automatically inlining critical css. It sounds like your styles on the body could be coming from something else. Maybe it's another dependency? Might be worth posting a new issue if you can create minimal reproduction
@jpduckwo Angular is the only dependency. I will do as you suggest. I will try to create a minimal demonstration.
Hello All,
In my case, my project uses Angular 13 latest; and this issue was happening only on PROD build. Not on Dev/Staging builds. I searched angular documentation; no where it is mentioned why only production grade build gets affected?
Any clarity would enlighten me on this.
Thanks
@ZenwalkerD check your angular.json, your settings for dev/staging will have a different optimization setting than prod. That's my guess :)
Thank's @tiberiuzuld :tada:
I used your solution https://github.com/angular/angular-cli/issues/20864#issuecomment-844823912 for a Chrome extension created with [email protected]. I also needed the code generated by the ng build
command to have no inline script.
I put the following optimization
configuration in my angular.json
file:
"configurations": {
"production": {
"...": "...",
"optimization": {
"scripts": true,
"styles": {
"minify": true,
"inlineCritical": false
},
"fonts": true
}
},
"development": {
"...": "..."
}
},