Build error via GitHub: "WC_POST_CSS" is not whitelisted

My Netlify site is a fairly plain standalone GitHub repo. I have a customized /layouts/partials/site_head.html file which works fine on my local machine and on my own web server, but seems to be causing issues on Netlify in the past year. I was using Netlify for CMS purposes, but given the build errors, I have instead just been manually editing the .md files to deploy on my own server. I would very much like to return to using Netlify.

Here is the full deploy log.. The build error is:

ERROR 2023/12/31 01:57:20 render of "page" failed: execute of template failed: template: publication/single.html:5:3: executing "publication/single.html" at <partial "site_head" .>: error calling partial: "/opt/build/repo/layouts/partials/site_head.html:208:13": execute of template failed: template: partials/site_head.html:208:13: executing "partials/site_head.html" at <getenv "WC_POST_CSS">: error calling getenv: access denied: "WC_POST_CSS" is not whitelisted in policy "security.funcs.getenv"; the current security configuration is:
8:57:20 PM: [security]
8:57:20 PM:   enableInlineShortcodes = false
8:57:20 PM:   [security.exec]
8:57:20 PM:     allow = ["^dart-sass-embedded$", "^go$", "^npx$", "^postcss$"]
8:57:20 PM:     osEnv = ["(?i)^(PATH|PATHEXT|APPDATA|TMP|TEMP|TERM)$"]
8:57:20 PM:   [security.funcs]
8:57:20 PM:     getenv = ["^HUGO_"]
8:57:20 PM:   [security.http]
8:57:20 PM:     methods = ["(?i)GET|POST"]
8:57:20 PM:     urls = [".*"]

I haven’t been able to locate a file in my repo containing security.funcs.getenv or anything security related. No luck in looking through the support docs and forum. Maybe it is a newer addition to the Hugo version my site is built on and that runs on local machine and web server (0.89.2)?

Full build logs, site_head.html, and requested support details are below.

Any help is much appreciated!


URL: https://d-aleman.netlify.app/

Deploy URL: https://app.netlify.com/sites/d-aleman/deploys/6590ca6885448800088e3016
Deploy ID: 6590ca6885448800088e3016

context: production
Deployment Type: Deployment with GitHub
Last Broken Deploy URL: https://app.netlify.com/sites/d-aleman/deploys/6590ca6885448800088e3016
Last Successful Deploy URL: https://app.netlify.com/sites/d-aleman/deploys/6283dd69f13fe40009efdd39


site_head.html:

{{ $scr := .Scratch }}
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  {{ $is_sponsor := site.Params.i_am_a_sponsor | default false }}
  {{ $hide_generator := site.Params.power_ups.hide_generator | default false }}
  {{ if not (and $is_sponsor $hide_generator) }}
    <meta name="generator" content="Wowchemy {{ site.Data.wowchemy.version }} for Hugo" />
  {{ end }}

  {{ if .Params.private }}
    <meta name="robots" content="noindex" />
  {{- end -}}

  {{/* Parse theme and font */}}
  {{ partial "functions/parse_theme" . }}

  {{/* Pre-connect to Google Fonts if the site's Font Theme uses them. */}}
  {{ with ($scr.Get "google_fonts") }}
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
  {{ end }}

  {{/* Load Google Fonts if the site's Font Theme uses them. */}}
  {{/* Note: we cannot use SRI with Google Fonts because the CSS is dynamically generated according to the user agent. */}}
  {{ with ($scr.Get "google_fonts") }}
    {{ if hasPrefix . "family=" }}
      {{/* If `google_fonts` starts with "family=", use API v2 (https://developers.google.com/fonts/docs/css2) */}}
      <link rel="preload" as="style" {{ printf "href=\"https://fonts.googleapis.com/css2?%s&display=swap\"" . | safeHTMLAttr }}>
      <link rel="stylesheet" {{ printf "href=\"https://fonts.googleapis.com/css2?%s&display=swap\"" . | safeHTMLAttr }} media="print" onload="this.media='all'">
    {{ else }}
      {{ errorf "There is a new version of Google Fonts. Learn how to upgrade your font pack at https://wowchemy.com/docs/customization/#custom-font" }}
    {{ end }}
  {{ end }}

  {{ if site.Params.marketing.google_optimize | and hugo.IsProduction }}
    <script src="https://www.googleoptimize.com/optimize.js?id={{ site.Params.marketing.google_optimize }}"></script>
  {{- end -}}

  {{ with site.Params.marketing.google_site_verification }}
    <meta name="google-site-verification" content="{{ . }}" />
  {{- end -}}
  {{ with site.Params.marketing.baidu_site_verification }}
    <meta name="baidu-site-verification" content="{{ . }}" />
  {{- end -}}
  {{ with site.Params.marketing.bing_site_verification }}
    <meta name="msvalidate.01" content="{{ . }}" />
  {{- end -}}

  {{/* Attempt to load superuser. */}}
  {{ $superuser_name := "" }}
  {{ $superuser_username := "" }}
  {{ $superuser_role := "" }}
  {{ range first 1 (where (where site.Pages "Section" "authors") "Params.superuser" true) }}
    {{ $superuser_name = .Title }}
    {{ $superuser_username = path.Base (path.Split .Path).Dir }}
    {{ $superuser_role = .Params.role }}
  {{ end }}
  {{ $scr.Set "superuser_username" $superuser_username }}{{/* Set superuser globally for page_author.html. */}}

  {{ with $superuser_name }}<meta name="author" content="{{ . }}" />{{ end }}

  {{/* Generate page description. */}}
  {{ $desc := "" }}
  {{ if .Params.summary }}
    {{ $desc = .Params.summary }}
  {{ else if .Params.abstract }}
    {{ $desc = .Params.abstract }}
  {{ else if .IsPage }}
    {{ $desc = .Summary }}
  {{ else if site.Params.description }}
    {{ $desc = site.Params.description }}
  {{ else }}
    {{ $desc = $superuser_role }}
  {{ end }}
  <meta name="description" content="{{ $desc }}" />

  {{ range .Translations }}
    <link rel="alternate" hreflang="{{ .Lang }}" href="{{ .Permalink }}" />
  {{ end }}
  <link rel="alternate" hreflang="{{ site.LanguageCode | default "en-us" }}" href="{{ .Permalink }}" />

  {{ $css := site.Data.assets.css }}
  {{ $js := site.Data.assets.js }}
  {{ if ne ($scr.Get "primary") "#fff" }}
    <meta name="theme-color" content="{{ $scr.Get "primary" }}" />
  {{ end }}

  {{/* Config LaTeX math rendering. */}}
  {{ if or .Params.math site.Params.math }}
    {{ $mathjax_config := resources.Get "js/mathjax-config.js" }}
    <script src="{{ $mathjax_config.RelPermalink }}"></script>
  {{ end }}

  {{/* Attempt to load local vendor CSS, otherwise load from CDN. */}}

  {{/* Only load non-essential CSS in this media-swapping way */}}
  {{- $stylesheets := slice -}}
  {{- $lib_names := slice "fontawesome/all.min" -}}
  {{- range $lib_names -}}
    {{- $stylesheets = $stylesheets | append (resources.Get (printf "css/libs/%s.css" . ) ) -}}
  {{- end -}}
  {{ $stylesheets = $stylesheets | resources.Concat "css/vendor-bundle.css" | minify }}
  {{- if hugo.IsProduction -}}
    {{- $stylesheets = $stylesheets | fingerprint "md5" -}}
  {{- end -}}
  <link rel="stylesheet" href="{{$stylesheets.RelPermalink}}" media="print" onload="this.media='all'">

  {{ $scr.Set "vendor_css_filename" "main.min.css" }}
  {{ $scr.Set "vendor_js_filename" "main.min.js" }}
  {{ if and (fileExists (printf "static/css/vendor/%s" ($scr.Get "vendor_css_filename"))) (fileExists (printf "static/js/vendor/%s" ($scr.Get "vendor_js_filename"))) }}
    {{ $scr.Set "use_cdn" 0 }}
    <link rel="stylesheet" href="{{ printf "/css/vendor/%s" ($scr.Get "vendor_css_filename") | relURL }}" />
  {{ else }}
    {{ $scr.Set "use_cdn" 1 }}
    {{ if site.Params.icon.pack.ai }}
      {{ printf "<link rel=\"stylesheet\" href=\"%s\" integrity=\"%s\" crossorigin=\"anonymous\" media=\"print\" onload=\"this.media='all'\">" (printf $css.academicons.url $css.academicons.version) $css.academicons.sri | safeHTML }}
    {{ end }}

    {{/* Workaround `.HasShortcode "gallery"` not parsing page resources (widget page sections) */}}
    {{ $has_gallery := false }}
    {{/* Note: unless there is a root `/index.md` with `type: widget_page`, Hugo treats homepage as `page` type. */}}
    {{ if (eq .Type "widget_page") | or (and .IsHome (eq .Type "page")) }}
      {{/* Check if widget page sections use a gallery */}}
      {{ $page := "/home/index.md" }}
      {{ $context := cond .IsHome (site.GetPage $page) . }}
      {{ range $context.Resources.ByType "page" }}
        {{ if .HasShortcode "gallery" }}
          {{ $has_gallery = true }}
        {{ end }}
      {{ end }}
    {{ else }}
      {{/* Single page */}}
      {{ if .HasShortcode "gallery" }}
        {{ $has_gallery = true }}
      {{ end }}
    {{ end }}
    {{ $scr.Set "HasNestedGalleryShortcode" $has_gallery }}
    {{ if $has_gallery | or site.Params.require_fancybox }}
      {{ printf "<link rel=\"stylesheet\" href=\"%s\" integrity=\"%s\" crossorigin=\"anonymous\" media=\"print\" onload=\"this.media='all'\">" (printf $css.fancybox.url $css.fancybox.version) $css.fancybox.sri | safeHTML }}
    {{ end }}

    {{/* Default to disabling highlighting, but allow the user to override it in .Params or site.Params.
         Use $scr to store "highlight_enabled", so that we can read it again in footer.html. */}}
    {{ $scr.Set "highlight_enabled" false }}
    {{ if isset .Params "highlight" }}
      {{ $scr.Set "highlight_enabled" .Params.highlight }}
    {{ else if isset site.Params "highlight" }}
      {{ $scr.Set "highlight_enabled" site.Params.highlight }}
    {{ end }}
    {{ if ($scr.Get "highlight_enabled") }}
      {{ $v := $css.highlight.version }}
      {{ with site.Params.highlight_style }}
        {{ printf "<link rel=\"stylesheet\" href=\"%s\" crossorigin=\"anonymous\" title=\"hl-light\" media=\"print\" onload=\"this.media='all'\">" (printf $css.highlight.url $css.highlight.version .) | safeHTML }}
        {{ printf "<link rel=\"stylesheet\" href=\"%s\" crossorigin=\"anonymous\" title=\"hl-dark\" media=\"print\" onload=\"this.media='all'\" disabled>" (printf $css.highlight.url $css.highlight.version .) | safeHTML }}
      {{ else }}
        {{ if eq ($scr.Get "light") true }}
          {{ printf "<link rel=\"stylesheet\" href=\"%s\" crossorigin=\"anonymous\" title=\"hl-light\" media=\"print\" onload=\"this.media='all'\">" (printf $css.highlight.url $css.highlight.version "github") | safeHTML }}
          {{ printf "<link rel=\"stylesheet\" href=\"%s\" crossorigin=\"anonymous\" title=\"hl-dark\" media=\"print\" onload=\"this.media='all'\" disabled>" (printf $css.highlight.url $css.highlight.version "dracula") | safeHTML }}
        {{ else }}
          {{ printf "<link rel=\"stylesheet\" href=\"%s\" crossorigin=\"anonymous\" title=\"hl-light\" media=\"print\" onload=\"this.media='all'\" disabled>" (printf $css.highlight.url $css.highlight.version "github") | safeHTML }}
          {{ printf "<link rel=\"stylesheet\" href=\"%s\" crossorigin=\"anonymous\" title=\"hl-dark\" media=\"print\" onload=\"this.media='all'\">" (printf $css.highlight.url $css.highlight.version "dracula") | safeHTML }}
        {{ end }}
      {{ end }}
    {{ end }}

    {{/* Maps CSS. */}}
    {{ $map_provider := lower site.Params.map.provider }}
    {{ if in (slice "mapnik" "mapbox") $map_provider }}
      {{ printf "<link rel=\"stylesheet\" href=\"%s\" integrity=\"%s\" crossorigin=\"anonymous\" media=\"print\" onload=\"this.media='all'\">" (printf $css.leaflet.url $css.leaflet.version) $css.leaflet.sri | safeHTML }}
    {{ end }}

    {{ if eq (lower site.Params.search.provider) "algolia" }}
      {{ printf "<link rel=\"stylesheet\" href=\"%s\" integrity=\"%s\" crossorigin=\"anonymous\">" (printf $css.instantsearch.url $css.instantsearch.version) $css.instantsearch.sri | safeHTML }}
      {{ printf "<link rel=\"stylesheet\" href=\"%s\" integrity=\"%s\" crossorigin=\"anonymous\">" (printf $css.instantsearchTheme.url $css.instantsearchTheme.version) $css.instantsearchTheme.sri | safeHTML }}
    {{ end }}

    {{/* Load async scripts. */}}
    {{ range $k, $v := site.Data.assets.js }}
      {{/* TODO: investigate why `where ... "async" true` does not work. */}}
      {{ $load := $v.async }}

      {{/* Only load MathJax if required. */}}
      {{ if (eq $k "mathJax") | and (not (or $.Params.math site.Params.math)) }}
        {{ $load = false }}
      {{ end }}

      {{ if $load }}
        {{ printf "<script src=\"%s\" integrity=\"%s\" crossorigin=\"anonymous\" async></script>" (printf $v.url $v.version) $v.sri | safeHTML }}
      {{ end }}
    {{ end }}
  {{ end }}

  {{ $license := printf "/*! Wowchemy v%s | https://wowchemy.com/ */\n" site.Data.wowchemy.version }}
  {{ $license := $license | printf "%s/*! Copyright 2016-present George Cushen (https://georgecushen.com/) */\n" }}
  {{ $license := $license | printf "%s/*! License: https://github.com/wowchemy/wowchemy-hugo-themes/blob/main/LICENSE.md */\n" }}
  {{ $css_bundle_head := $license | resources.FromString "css/bundle-head.css" }}
  {{ $css_options := dict "targetPath" "css/wowchemy.css" }}
  {{- if hugo.IsProduction -}}
    {{- $css_options = merge $css_options (dict "outputStyle" "compressed") -}}
  {{- end -}}
  {{ $sass_template := resources.Get "scss/main.scss" }}
  {{ $style := $sass_template | resources.ExecuteAsTemplate "main_parsed.scss" . | toCSS $css_options }}
  {{- if hugo.IsProduction -}}
    {{- $style = $style | minify -}}
  {{- end -}}
  {{ $style := slice $css_bundle_head $style | resources.Concat "css/wowchemy.css" }}
  {{- if eq (getenv "WC_POST_CSS") "true" -}}
    {{- $style = $style | postCSS -}}
  {{- end -}}
  {{- if hugo.IsProduction -}}
    {{- $style = $style | fingerprint "md5" -}}
  {{- end -}}
  {{- if eq (getenv "WC_POST_CSS") "true" -}}
    {{/* PostProcess must be last action in the pipeline */}}
    {{- $style = $style | resources.PostProcess -}}
  {{- end -}}
  <link rel="stylesheet" href="{{ $style.RelPermalink }}" />

  {{ partial "marketing/google_analytics" . }}
  {{ partial "marketing/google_tag_manager" . }}
  {{ partial "marketing/microsoft_clarity" . }}
  {{ partial "marketing/baidu_tongji" . }}

  {{/* Netlify Identity integration. */}}
  {{ $use_cms := templates.Exists "wowchemycms/single.wowchemycms_config.yml" | default (site.Params.cms.netlify_cms | default false) }}
  {{ if .IsHome | and $use_cms }}
  
    <!-- Default netlify identify widget script -->
    <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
    
    <!--
    Modified according to https://stackoverflow.com/questions/52174873/using-netlifys-hosted-identity-service-with-self-hosted-netlify-cms
    to try to get Netlify CMS access working on morlab webserver ... doesn't seem to work
    -->
    <!-- <script src="https://identity.netlify.com/v1/netlify-identity.js"></script>
    <script>
      if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", function () {
          netlifyIdentity.init({
            APIUrl: "https://d-aleman.netlify.com/.netlify/identity"
          });
        });
      } else {
        netlifyIdentity.init({
          APIUrl: "https://d-aleman.netlify.com/.netlify/identity"
        });
      }
    </script>
    -->
  {{ end }}

  {{ with .OutputFormats.Get "RSS" }}
    <link rel="alternate" href="{{ .RelPermalink }}" type="application/rss+xml" title="{{ site.Title }}" />
  {{ end }}

  {{ with site.Home.OutputFormats.Get "WebAppManifest" }}
    <link rel="manifest" href="{{ .RelPermalink }}" />
  {{ end }}

  <link rel="icon" type="image/png" href="{{ (partial "functions/get_icon" 32).RelPermalink }}" />
  <link rel="apple-touch-icon" type="image/png" href="{{ (partial "functions/get_icon" 180).RelPermalink }}" />

  <link rel="canonical" href="{{ .Permalink }}" />

  {{/* Get page image for sharing. */}}
  {{ $sharing_image := resources.GetMatch (path.Join "media" "sharing.*") }}
  {{ $featured_image := (.Resources.ByType "image").GetMatch "*featured*" }}
  {{ $avatar_image := (.Resources.ByType "image").GetMatch "avatar*" }}
  {{ $has_logo := fileExists "assets/media/logo.png" | or (fileExists "assets/media/logo.svg") }}
  {{ $og_image := "" }}
  {{ $twitter_card := "summary_large_image" }}
  {{ if (and (eq .Kind "term") $avatar_image) }}
    {{/* Match image processing in About widget to prevent generating more images than necessary. */}}
    {{ $og_image = ($avatar_image.Fill "270x270 Center").Permalink }}
    {{ $twitter_card = "summary" }}
  {{ else if $featured_image }}
    {{ $og_image = $featured_image.Permalink }}
  {{ else if $sharing_image }}
    {{ $og_image = $sharing_image.Permalink }}
  {{ else if $has_logo }}
    {{ $og_image = (partial "functions/get_logo" (dict "constraint" "fit" "size" 300)).Permalink }}
    {{ $twitter_card = "summary" }}
  {{ else }}
    {{ $og_image = (partial "functions/get_icon" 512).Permalink }}
    {{ $twitter_card = "summary" }}
  {{ end }}
  {{ $scr.Set "og_image" $og_image }}{{/* Set `og_image` globally for `rss.xml`. */}}

  <!-- Replaced site title with user-defined long-title in config -->
  {{ $title := "" }}
  {{ $basetitle := "" }}
  {{ if site.Params.longtitle }}
    {{ $basetitle = site.Params.longtitle }}
  {{ else }}
    {{ $basetitle = site.Title }}
  {{ end }}
  {{ with .Params.seo.title }}
    {{ $title = replace . "{brand}" $basetitle }}
  {{ else }}
    {{ $title = .Title | default $basetitle }}
    {{ if ne $title $basetitle }}{{ $title = printf "%s | %s" $title $basetitle }}{{ end }}
  {{ end }}
  
  <meta property="twitter:card" content="{{ $twitter_card }}" />
  {{ with site.Params.twitter }}
    <meta property="twitter:site" content="@{{ . }}" />
    <meta property="twitter:creator" content="@{{ . }}" />
  {{ end }}
  <meta property="og:site_name" content="{{ site.Title }}" />
  <meta property="og:url" content="{{ .Permalink }}" />
  <meta property="og:title" content="{{ $title }}" />
  <meta property="og:description" content="{{ $desc }}" />
  {{- with $og_image -}}
    <meta property="og:image" content="{{ . }}" />
    <meta property="twitter:image" content="{{ . }}" />
  {{- end -}}
  <meta property="og:locale" content="{{ site.LanguageCode | default "en-us" }}" />
  {{ if .IsPage }}
    {{ if not .PublishDate.IsZero }}
      <meta
        property="article:published_time"
        content="{{ .PublishDate.Format "2006-01-02T15:04:05-07:00" | safeHTML }}"
      />
    {{ else if not .Date.IsZero }}
      <meta property="article:published_time" content="{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}" />
    {{ end }}
    {{ if not .Lastmod.IsZero }}<meta property="article:modified_time" content="{{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }}">{{ end }}
  {{ else }}
    {{ if not .Date.IsZero }}
      <meta property="og:updated_time" content="{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}" />
    {{ end }}
  {{ end }}

  {{ partial "jsonld/main" (dict "page" . "summary" $desc) }}
  {{ partial "cookie_consent" . }}
  {{ partial "custom_head" . }}

  <title>{{$title}}</title>
</head>

This is a Hugo question, not a Netlify question. To your hugo or config.toml, add the following:

[security]
   [security.funcs]
     getenv = ["^HUGO_", "^WC_POST_CSS$"]

For further help, please post on Hugo forums.

Thanks! I didn’t realize it was a Hugo issue, so thank you again for the suggestion outside Netlify’s scope.

Unfortunately, I still get the same error with the security variables added to netlify.toml, which looks like this:

[build]
  command = "hugo --gc --minify -b $URL"
  publish = "public"

[build.environment]
  HUGO_VERSION = "0.95.0"
  HUGO_ENABLEGITINFO = "true"

[context.production.environment]
  HUGO_ENV = "production"

[context.deploy-preview]
  command = "hugo --gc --minify --buildFuture -b $DEPLOY_PRIME_URL"

[context.branch-deploy]
  command = "hugo --gc --minify -b $DEPLOY_PRIME_URL"

[[plugins]]
  package = "netlify-plugin-hugo-cache-resources"
  [plugins.inputs]
    debug = true
    
[security]
   [security.funcs]
     getenv = ["^HUGO_", "^WC_POST_CSS$"]

I guess I will try to find some Hugo support on the topic.

Found the solution quite quickly with the advice to look to Hugo issues rather than Netlify issues. For anyone else who has the same error and is looking here, you have to add the following to config.yaml:

security:
  funcs:
    getenv:
      - ^HUGO_
      - ^WC_

Source: https://github.com/HugoBlox/hugo-blox-builder/issues/2595

As I mentioned, this was supposed to be added to hugo or config.toml not netlify.toml, but I guess you had config.yml.