Hugo/Content-Security-Policy Impedance Mismatch

October 19, 2021

As part of the site upgrades, I’ve also been clamping down on the response headers. The full list is below, but I ran into a couple of problems trying to remove 'unsafe-inline' from the style-src section of the the Content-Security-Policy header.

The two issues I encountered were:

  1. Chroma renders syntax highlighting fragments with inline style attributes, rather than CSS classes.
  2. Goldmark renders table cell alignment with inline style attributes.

Neither of these defaults is ideal if you don’t want to allow inline styles.

After reading this documentation, I was able to configure Hugo and Chroma to use CSS classes instead of inline style attributes by doing the following:

# switch to theme assets directory
cd themes/hugo-pt2021/assets

# generate static chroma stylesheet
hugo gen chromaclasses --style=monokai > chroma.css

# append chroma to style.sass
echo '@import "chroma"' >> style.sass

Finally, I added the following to config.toml:

  # syntax highlighting
    # use classes instead of inline style attributes
    noClasses = false

That leaves me with table cell alignment issue, which does not appear to be as easy to fix. According to the documentation, the Goldmark Table extension does support modifying the table cell alignment rendering behavior, but the options are limited:

  1. align attribute: deprecated in HTML5
  2. style='text-align: ...': what I am trying to avoid
  3. none: requires AST post-processing

Options #1 and #2 are out, and I don’t see an obvious way to configure Hugo to do something useful with #3.

One nuclear option would be to:

  1. define a custom table shortcode,
  2. move table data to the data directory, then
  3. bypass the table renderer entirely

Frankly the table markup syntax is so atrocious that I’m not sure the nuclear option would be all that bad.

In any case, here are my response headers so far:

# set security headers (2021-10-17)
Header append "Strict-Transport-Security" "max-age=31536000"
Header append "X-Frame-Options" "SAMEORIGIN"
Header append "X-Content-Type-Options" "nosniff"
Header append "Cross-Origin-Opener-Policy" "same-origin"
Header append "Cross-Origin-Resource-Policy" "same-origin"
Header append "Access-Control-Allow-Origin" ""
Header append "Access-Control-Allow-Methods" "POST, GET, HEAD, OPTIONS"

# updates (2021-10-20)
Header append "Referrer-Policy" "strict-origin-when-cross-origin"
Header append "Permissions-Policy" "camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), usb=()"

# 'unsafe-inline' needed for hugo table cell alignment :/
Header append "Content-Security-Policy" "default-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline'"


Update (2021-10-20): Based on the the scan results from, I constrained Access-Control-Allow-Origin, added Referrer-Policy, and added Permissions-Policy.

Update 2 (2021-10-25): I went with the nuclear option.