Skip to main content
ertius.org

zola asset integrity

As a diligent Over Engineer, a while ago I added two things to how Zola generates links to assets:

  1. integrity attributes to CSS <link>s, which means the browser will reject CSS with the wrong hash
  2. ?h=0123456789abcdef faux-GET params to the asset URLs themselves, so that I can set long HTTP cache times on them

This looks like this in the Zola template:

<link
    integrity="sha384-{{ get_hash(path="css/some.css", sha_type=384, base64=true) | safe }}"
    rel="stylesheet" href="{{ get_url(cachebust=true, path="css/some.css") }}">

which produces output like this in the generated HTML:

<link
    integrity="sha384-6d001da919b965dc3a4672b9d7ddce374d165452a2285f2753988842092ea6b9946645375cff3ede89a991c9698bfcea"
    rel="stylesheet" href="/css/some.css?h=0123456789abcdef">

So, now, if a malicious TLA MITMs you or compromises the server serving static assets, the browser will reject them (from the console log):

[Error] Cannot load stylesheet http://127.0.0.1:1111/css/main.css?h=0123456789abcdef. Failed integrity metadata check. Content length: 7715, Expected content length:
7715, Expected metadata: sha384-6d001da919b965dc3a4672b9d7ddce374d165452a2285f2753988842092ea6b9946645375cff3ede89a991c9698bfcea

In other news, Zola has a serve command that runs a little dev server and reloads if any of the files change:

❯ mise run serve
[serve] $ zola serve --interface 0.0.0.0 --base-url / --port=${PORT:-1111} --drafts
Building site...
Checking all internal links with anchors.
> Successfully checked 0 internal link(s) with anchors.
-> Creating 1 pages (0 orphan) and 0 sections
Done in 7ms.

Web server is available at / (bound to 0.0.0.0:1111)

Listening for changes in /home/user/project/{config.toml,content,static,templates}
Press Ctrl+C to stop

Change detected @ 2026-01-12 15:03:46
-> Static file changed /home/user/project/static/css/some.css
Done in 2ms.

You can see at the bottom that it reloaded the CSS file that had changed.

Unfortunately, Zola doesn't tie these two features together and if you use integrity and edit the assets while zola is running, then it will start serving the new asset with the old HTML, and then the browser will quite reasonably tell you to fuck off and stop loading the CSS. Excitingly, browsers don't really show that this is happening anywhere except in the console log, and if you have multiple style sheets and edited only one, the behaviour you will see is:

  1. everything is fine
  2. edit a CSS file
  3. reload page
  4. styles are all fucked up, and if you're like me, assume it's because your edit was terrible
  5. think that's weird, let's blame caching or something
  6. restart zola
  7. everything is fine
  8. undo change to CSS file
  9. styles are all fucked up, and if you're like me, assume it's because your edit was terrible
  10. etc

On the bug, user legoktm provides a pretty good workaround, that just removes the integrity attribute entirely when in local dev serving mode:

Firstly, add a new macro in templates/macros.html:

{% macro sri(path) -%}
{% if config.mode != "serve" -%}
integrity="sha384-{{ get_hash(path=path, sha_type=384, base64=true) | safe }}"
{%- endif %}
{%- endmacro %}

Then at the top of templates/base.html (or wherever your <link> tags are done):

{% import "macros.html" as macros %}

and wherever you actually write out the <link> in the templates, do this instead:

<link rel="stylesheet" href="{{ get_url(path="static/some.css", cachebust=true) }}"
    {{-macros::sri(path="static/some.css")}} />

(note the - before the m in macros - it tells the Tera template language to strip whitespace. If you forget that, in serve mode the macros will be reduced to a newline instead of nothing).