Use dynamic css to comply with Content-Security-Policy

I’m about 2 weeks into my walk with Hugo and love it.

I spent all day trying to figure out how to comply with Content-Security-Policy and still allow a customer to edit a Data .yml file to change the background image on their index.html.

Before applying the Content-Security-Policy I was using style=“background-image: URL({{ VARIABLE }}” and it worked great.

Then after applying Content-Security-Policy, my image didn’t show up and in the developer tools, I had an error in the console informing me I was violating the Content-Security-Policy. Yes, you can get rid of the error bypassing one of the policies, but if you do, you’re opening a security crack that Content-Security-Policy is supposed to protect viewers of your site.

After hours and hours of researching, I finally ran into this page.

https://gohugo.io/hugo-pipes/resource-from-template/

I created this assets/css/dynamic.scss file.

{{ $data := index .Site.Data }}
{{ if $data.homepagehero.hero.enable }}
{{ with $data.homepagehero.hero }}

.bg-hero {
    background-image: url("{{ .bg_image | relURL }}");
}

{{end}}
{{end}}

Then in my theme /layouts/partials/head.html

{{ $sassTemplate := resources.Get "css/dynamic.scss" }}
{{ $dstyle := $sassTemplate | resources.ExecuteAsTemplate "css/dynamic.scss" . | resources.ToCSS }}
{{ $dstyle := $dstyle | fingerprint | resources.PostProcess }}
<link rel="stylesheet"
    href="{{ $dstyle.RelPermalink }}" integrity="{{ $dstyle.Data.Integrity }}">

Now in an HTML file, I can use the dynamically created CSS class (bg-hero) to assign a customer-defined background image.

<div class="bg-hero bg-no-repeat bg-cover"></div>

This also makes my website compatible with Content-Security-Policy.

Hope this helps someone else.

@bep I tagged you to show another real-world use case for this outstanding feature.

2 Likes

Thanks. As a side note related to this: Google pagespeed/lighthouse prefers inlined styles (as opposed to the link you use here). That also works with fingerprinting and CSP, but you might have to pipe the CSS through safeCSS:
<script>{{$dstyle.Content | safeCSS}}</script>
Also, if you have more than one external style, you can bundle them into one…

1 Like

Thank you @chrillek for the information.

When I read the CSP docs, to use <script> or <style> I would have to modify the CSP policy to allow this.

From the below link:

Note: Disallowing inline styles and inline scripts is one of the biggest security wins CSP provides. However, if you absolutely have to use it, there are a few mechanisms that will allow them.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src#unsafe_inline_styles

When I read this, I opted to not use inline scripts or styles.

I’m using Tailwindcss which has complex pre and post-processing. I tried for an hour to bundle the Tailwindcss and my little dynamic.css but was unsuccessful, kept getting Hugo template build errors.

You’re correct, Lighthouse would rather see one CSS file loading, but I could not figure out how to get them bundled.

It seems to come down to a choice between two CSS files loading or using <style> or <script> and modifying the CSP policy?

I chose the former, to be more secure based on my reading of the CSP docs.

Thoughts and thank you,

Karl

1 Like
{{ $CSS := slice (resources.Get "css/a.css")
    (resources.Get "css/b.css") | 
    resources.Concat "css/c.css" |  
    resources.PostCSS |  minify  }}
<link rel="stylesheet" href={{$CSS.relPermalink}}>

might work. I’m not sure about the relPermalink thing, that’s from the documentation. I’m using Permalink which works just fine here.

1 Like

@chrillek,

Thank you very much for assisting me. I had an ah, ha moment and figured out how to get my dynamic CSS bundled with Tailwindcss.

Without your outstanding example, I would have never got this sorted out. I needed to bundle before doing any Tailwindcss processing.

Here is my final code that is CSP compliant and gets lighthouse scores of 100 across the board.

The RelPermalink emits a relative url instead of an absolute url.

Best regards,

Karl

{{ $dynamicScssTemplate := resources.Get "css/dynamic.scss" }}

{{ $dynamicStyles := $dynamicScssTemplate | resources.ExecuteAsTemplate "css/dynamic.scss" . }}

{{ $styles := resources.Get "css/styles.scss" }}

{{ $CSS := slice $styles $dynamicStyles | resources.Concat "css/bundle.css "}}

{{ $bundledStyles := $CSS | toCSS | postCSS (dict "config" "./assets/css/postcss.config.js") }}

{{ if .Site.IsServer }}

<link rel="stylesheet"

    href="{{ $bundledStyles.RelPermalink }}">

{{ else }}

{{ $bundledStyles := $bundledStyles | minify | fingerprint | resources.PostProcess }}

<link rel="stylesheet"

    href="{{ $bundledStyles.RelPermalink }}"

    integrity="{{ $bundledStyles.Data.Integrity }}">

{{ end }}
1 Like

Happy to help and good to see that it works.
Nearly unrelated: I do not really understand why <style> with a hash is discouraged as CSP while <link> with the same hash is ok. But so be it.

In the comment, they discourage both inline styles and scripts, however, both do allow assigning a hash.

I’m going to try to use the default locked-down CSP policy in my projects. With the bundling solution you provided I’m getting the best from a security and lighthouse scoring perspective.

I’m no expert on CSP, just trying to comply. LOL.

Best regards,

Karl

A linked stylesheet, with or without a hash is CSP compliant. Inline styles and scripts are discouraged, quite rightly as the browser has no way to know if that code was included intentionally (and should be trusted) or not. Best option is to not trust, or execute, code in that situation.

The integrity value in a linked asset is required for subresource integrity validation, which is not related to your CSP, but is a very good security measure nonetheless.

FWIW, using an inline image, rather than a css background image is generally preferable anyway as a hero image is rarely just decorative. It would also mean you can simply specify it in frontmatter and is easily changed by a client.

You get better performance, better semantics and better SEO using an inline image vs a background image in css and you get more control over it as you can provide multiple image formats, e.g. webp, and use srcset. A wider view of that here: https://nystudio107.com/blog/the-css-background-image-property-as-an-anti-pattern

@nternetinspired thank you for the information, I read the article and learned some nice tips.

My hero image does not have to be a background-image. I got it working using an img.

What I could not get to work is an image that is animated resulting in a nice wave at the bottom of the hero. I need the background-image so I can do the required magic.

When I tried to use an img for the wave, it didn’t render correctly so I reverted to the backgound-image.

Thank you for your reply,

Karl

@nternetinspired I should have been more explicit. The wave uses background repeat to give me the endless image, I animate the background-position-x property to get the wave effect.

I was not able to get the wave.svg image to repeat using an img tag. So when I animate left, the wave.svg does not display full screen.

When using the wave.svg as a background-image, it works great.

Best,

Karl

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.