Are we forced to use 'unsafe-inline' in our CSP when using Vue.JS? - vue.js

Is there a way to make Vue.js to work with CSP properly?
When I run my spa application (resulting from npm run generate with Nuxt.js), I will get several warnings such as these:
Refused to apply inline style because it violates the following
Content Security Policy directive: "style-src 'self' 'strict-dynamic'
'nonce-124lk5fjOc4jn7qqLYEsG2jEvxYuqu8J' 'unsafe-inline' https:". Note
that 'unsafe-inline' is ignored if either a hash or nonce value is
present in the source list.
Knowing CSP, there are two correct ways of fixing this:
Using nonces, where Vue.js would have to sign all the generated scripts and styles with a nonce attribute. But I don't think this would solve anything, since it appears some CSS is added inline.
Using hashes, which is actually the preferred way of doing it, since the hash secures exactly what we want the client to execute on the browser.
However, in order to use hashes, Vue.js/Webpack must be able to calculate the hash for all its scripts and styles, and:
for each compilation, tell them to the developer that will then add these hashes to a NGINX configuration file,
or,
be able to generate meta tags containing the hashes, making this process 100% transparent to the developer, who doesn't need to configure anything else to guarantee a good CSP protection.
Does Vue.js support this in any way? Is there anyone in the world who was able to make CSP working with Vue.js without any 'unsafe-inline'?

According to the Vue.js docs, the runtime build is fully CSP-compliant.
Nuxt is supporting a csp config to create hashes via webpack sent as header on dynamic SSR mode and meta elements otherwise (see https://github.com/nuxt/nuxt.js/pull/5354)

you could use the --no-unsafe-inline option in your npm run build script
https://cli.vuejs.org/guide/cli-service.html#vue-cli-service-build

Not sure if this is better as a comment or not but it kinda works so putting it here for now.
Our deployment strategy might be a bit different, but essentially we trigger a lambda to update the cloudfront csp with our CI/CD.
We noted that the inline scripting was static despite different app versions/bumps. Our current workaround is:
Deploy on a dev server - get the sha256 hash from the chrome dev tools (you could probably calculate it yourself to avoid deploying)
Updated our terraform cloudfront lambda CSP with the hash
On the new deploy the hash matches and we don't need unsafe-inline
Some big limitations re: if nuxt changes the inline script on new versions we'll have to manually update our hash in the CSP. Also, depending on your styling framework there may be a number of inline-styles which aren't captured here.

Related

How to configure CSP with inline-style in Vue or Nuxt?

I want to deploy a Nuxt application but I have a problem with Csp. I added all the Sha256 to my Csp but some lines are still stuck. I think it's the lines with :style="". What would be the solution? Is it really risky to add an "unsafe-inline"?
Everything works if I add "unsafe-inline" but I'm not sure that's great for site security.
I also specify that all my other Headers are well configured
If you add hashes for event attributes such as onclick, onerror etc, it won't work. You can make it work if the browser has implemented full support for 'unsafe-hashes', but there are likely still a lot of users who are not at that level. Otherwise you'll need to rewrite the event attributes to event listeners if you don't want to add 'unsafe-inline'.

Vue JS and vite-plugin-ssr for Server or Client Side Rendering, depending on the user-agent, ideally to be hosted on AWS Lambda Edge

I'm trying to use vite-plugin-ssr with VueJS, and dynamically decide whether to SSR or serve the Vue code for client rendering, according to the User-Agent passed to isbot (https://github.com/omrilotan/isbot).
The strategy is :
If isbot() returns true, the returned HTML shall be SSR.
Otherwise, the VueJS build files shall be returned for the client browser to achieve the rendering.
I found this interesting article https://dev.to/divporter/vue-serverless-side-rendering-with-aws-lambda-edge-1ep8 using AWS Lambda Edge to either achieve the SSR when a bot is detected, or let CloudFront source and return the VueJS files stored on S3, but it's a little bit outdated now.
I'm trying to use https://vite-plugin-ssr.com/ but cannot figure out how their Vue examples work. It seems either a page is rendered server side, or client side, but I can't find how to test the User-Agent upfront, and only then decide if the page shall be server side rendered or not.
Has anyone deployed such use case ?
Many thanks !

Is It Possible to Have a Meaningful/Secure Content Security Policy With Next.js + Styled-Components and a Static Host (eg. S3)

Recently Google's Lighthouse tool alerted me to the fact that I wasn't providing a Content Security Policy. However, when I try to add one (or at least one without the word "unsafe" in it), I wind up with a bunch of violations, seemingly coming from Next.js and Styled-Components.
Both libraries seem to use dynamic script/style tags which violate any sane CSP. But the only way I've found to work around them is to use a "nonce". However, that seems to require having an actual server running: if you're using Next to generate static files (to host on a static host like AWS S3), you can't provide nonces.
My question is simple: am I missing anything? Is there some non-nonce-based way, or a static-host-nonce-based way, to host a site on S3 using Next.js and Styled Components?
Or is it just impossible to use those libraries together with a strict CSP (without a server-generated nonce)?
I hope you:
do not use inline styles like <tag style='display:none;'> or JS call of element.setAttribute('style', ...).
do not use built-in inline event handlers like <tag onclick='...'> and JS-navigation like <a href='javascript:void(0)'>
because all of above require 'unsafe-inline' in styles/scripts respectively since 'unsafe-hashes' token is not supported by Safary and bugly supported by Firefox.
For Single Page Applications (SPA) (without server-side rendering), using 'nonce-value' is not useful, because the SPA does not reload the page, but only partially updates its contents, but you must generate new nonce for each page loading.
For serverless apps (like static file hosting) and SPA apps you can use 'hash-value' instead of 'nonce-value' to allow inline scripts and styles.
If you use Webpack, it has some plugins, for instance, csp-html-webpack-plugin plugin will generate content for your Content Security Policy meta tag and input the correct data into your HTML template, generated by html-webpack-plugin. All inline JS and CSS will be hashed, and inserted into the policy.

Laravel Mix VueJS production page blank

After long frustrating hours of research I finally found the cause for my Laravel7 VueJS (within blade templates) application running on nginx throwing a blank screen. Yet I lack the explanation or correct config.
When I accessed any route it would be seen for around 200ms and then switch to a white screen without any errors in npm run prod config.
Body tag would be completely empty (it looked uncommented in the inspector)
[...]
<body>
<!-- -->
</body>
[...]
Funny enough (sarcastically speaking) on page reload the login page would work normally but after accessing any other route it would revert to the behaviour described above.
After switching to npm run dev the console threw the following error:
[Vue warn]: It seems you are using the standalone build of Vue.js in an environment with Content Security Policy that prohibits unsafe-eval. The template compiler cannot work in this environment. Consider relaxing the policy to allow unsafe-eval or pre-compiling your templates into render functions.
and
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' http: https: data: blob: 'unsafe-inline'".
Which made me realize my nginx security config which I generated using the fabulous Tool provided by DigitalOcean included the following line:
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
After removing and restarting nginx everything works and looks as it does on my dev environment.
From what I get I now theoretically make my site vulnerable to XSS attacks but I do not fully understand what the option does or if it is safe to run without it with these circumstance.
The CSP header just lets your browser know what sources of JS are allowed. However, it is really a backup plan - what causes XSS is some code on your system that would render unauthorised JavaScript.
For example, the comment boxes on Stack Overflow allow me to enter the following text: <script src="http://evilserver.com/malicious.js"></script>. Stack Overflow know what they are doing, so they make sure that what is rendered is HTML, and not a literal <script> tag. Thus, you need to make sure you take the necessary care when rendering user supplied content and you will be OK (in general you do this in your output layer, not when accepting/storing input).
If you accept HTML input from users (as Stack Overflow does) then you have to be especially careful to allow only certain tags, and to ensure that any JS vectors are blocked up. So, the following text:
is actually in an <i> tag, which is rendered literally in the web app
That one is OK, since it is safe. You do need to make sure that any attributes are carefully checked, and any invalid markup is rejected. These can also be the source of security problems. This is a complex enough problem that, if you need users to be able to input HTML, you should not attempt to filter it yourself. Use a well-tested and well-regarded library instead.

Enabling Content-Security-Policy on a minimal web app

I'm new to CSP and my goal is to enable the simplest possible CSP header.
Based on reading the spec and MDN docs I thought my app should work but unfortunately no luck on Chrome Canary v70.
I setup a minimal repo to reproduce. Can you see where I've gone wrong?
Turns out I was misunderstanding the details a bit. I'll post my solution here in case it helps someone else in the same boat.
My goal is to serve a React SPA with CSP enabled. The app happens to use Material-UI, which uses JSS, which injects inline styles - which of course are blocked by default with CSP.
Because it's a static SPA for the frontend and modifying HTTP headers is out of scope of the SPA, I instead generate a nonce on the web server. The nonce gets injected into the CSP HTTP Header and also in the index.html tag consistent with what JSS expects.
The upside is CSP is protecting the SPA, and we don't have to use unsafe-inline escape hatch. The downside, but a small one, is that index.html is dynamic now and can't be cached. But seeing as the file is already tiny (<1kb) the benefits of CSP seem worth that tradeoff.