Security error with the new 0.161.0 security for PostCSS

Upgrading with 0.161.1 i get an error obviously related to new security in 0.161.0.
But despite reading and trying to understand the advice in the release doc, I just fail to understand what i have to do (in security.node permission I guess) :face_with_peeking_eye:

Any hint or advice welcome.

PS : hugo server doesn’t show this error, confirming relation with postcss process

panic: POSTCSS: failed to transform "/css/style.css" (text/css): Error: Access to this API has been restricted. Use --allow-fs-read to manage permissions.
            at Object.existsSync (node:fs:286:18)
            at isFile (/Users/didiergeorgieff/Documents/Git/osteo-timer/node_modules/browserslist/node.js:38:19)
            at /Users/didiergeorgieff/Documents/Git/osteo-timer/node_modules/browserslist/node.js:317:11
            at eachParent (/Users/didiergeorgieff/Documents/Git/osteo-timer/node_modules/browserslist/node.js:49:18)
            at Object.findConfig (/Users/didiergeorgieff/Documents/Git/osteo-timer/node_modules/browserslist/node.js:305:20)
            at browserslist.loadConfig (/Users/didiergeorgieff/Documents/Git/osteo-timer/node_modules/browserslist/node.js:223:37)
            at browserslist (/Users/didiergeorgieff/Documents/Git/osteo-timer/node_modules/browserslist/index.js:411:31)
            at Browsers.parse (/Users/didiergeorgieff/Documents/Git/osteo-timer/node_modules/autoprefixer/lib/browsers.js:54:12)
            at new Browsers (/Users/didiergeorgieff/Documents/Git/osteo-timer/node_modules/autoprefixer/lib/browsers.js:42:26)
            at loadPrefixes (/Users/didiergeorgieff/Documents/Git/osteo-timer/node_modules/autoprefixer/lib/autoprefixer.js:113:20) {
          code: 'ERR_ACCESS_DENIED',
          permission: 'FileSystemRead',
          resource: '/Users/didiergeorgieff/Documents/Git/package.json'
        }

Do you have a “security” section in your project configuration?
Or in your environment variables?

Check by running:

hugo config | grep security -A5

No. I had no security section.

I am trying to add one but i still get new errors (all security related)

````.browserslistrc
0.5%
last 2 versions
not dead

````

################################ SECURITY > v0.161.0 ########################

[security]

enableInlineShortcodes = true

[security.exec]

allow = [

“^(dart-)?sass(-embedded)?$”,

“^go$”,

“^git$”,

“^npx$”,

“^node$”,

“^postcss$”,

“^tailwindcss$”

]

osEnv = [

“(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE|PROGRAMDATA)$”

]

[security.funcs]

getenv = [“^HUGO_”, “^CI$”]

[security.http]

methods = [“(?i)GET|POST”]

urls = [“.*”]

And I finally just got it working with this command (and the 2 modified files upper):

NODE_OPTIONS="--permission --allow-fs-read=/Users/didiergeorgieff/Documents/Git/osteo-timer --allow-fs-read=/Users/didiergeorgieff/Documents/Git" hugo server

But I am wondering if it is a dirty hack of will be our future ?
ANd also i hve to figure how to industrilize it for Netlify.

I don’t understand the formatting of your repsonse. For example, when I run hugo config | grep security -A5 I get this:

[security]
  [security.exec]
    allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^git$', '^node$', '^postcss$', '^tailwindcss$']
    osenv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE|PROGRAMDATA)$']

  [security.funcs]
    getenv = ['^HUGO_', '^CI$']

  [security.http]
    methods = ['(?i)GET|POST']
    urls = ['(?i)^https?://[a-z]', '! (?i)localhost', '! @']

  [security.node]
    [security.node.permissions]
      allowaddons = ['tailwindcss']
      allowread = ['.']
      allowworker = ['tailwindcss']

I’m running v0.161.1.

Sorry for the misunderstanding from my side.

% hugo config | grep security -A5
[security]
enableinlineshortcodes = true

[security.exec]
allow = [‘^(dart-)?sass(-embedded)?$’, ‘^go$’, ‘^git$’, ‘^npx$’, ‘^node$’, ‘^postcss$’, ‘^tailwindcss$’]
osenv = [‘(?i)^((HTTPS?|NO)PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+|(XDG_CONFIG)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE|PROGRAMDATA)$’]

[security.funcs]
getenv = [‘^HUGO_’, ‘^CI$’]

[security.http]
methods = [‘(?i)GET|POST’]
urls = [‘.*’]

[security.node]
[security.node.permissions]
allowaddons = [‘tailwindcss’]
allowchildprocess = [‘tailwindcss’]
allowread = [‘.’]
allowworker = [‘tailwindcss’]

Are you running v0.161.0 or v0.161.1?

0.161.1

I don’t have an answer for you at the moment, but the quickest solution is to disable the Node.js permission model.

[security.node.permissions]
disable = true

I have a site that passes the CSS resource through PostCSS with this postcss.config.mjs config file:

import autoprefixer from 'autoprefixer';
import { purgeCSSPlugin } from '@fullhuman/postcss-purgecss';
const purgecss = purgeCSSPlugin({
  content: ['./hugo_stats.json'],
  defaultExtractor: content => {
    const els = JSON.parse(content).htmlElements;
    return [
      ...(els.tags || []),
      ...(els.classes || []),
      ...(els.ids || []),
    ];
  },
  // https://purgecss.com/safelisting.html
  safelist: {
    deep: [/.*tippy.*/, /^sup/]
  }
});

export default {
  plugins: [
    autoprefixer,
    process.env.HUGO_ENVIRONMENT !== 'development' ? purgecss : null
  ]
};

No problems, running on Ubuntu 24.04.

Yes. Thanks. This is the cleanest quick one.
I will try to make my head around this and post something if relevant. The doc is fine but not so easy to understand at first.

Anyway million thanks (again) for your incredible reactivity (and accuracy).

Can you post your PostCSS config file? Privately if you wish.

My best guess is that this is browserlist (used in autoprefixer); which walks up the tree (and outside of the Hugo project) to find the first browser config. I had a similar issue in a project I tested with, and if I remember correctly they have fixed this in a recent version of browserlist so it works better with Node’s permission model. Also, there should be a way to add a browserlist config to the project itself to avoid this.

I already had added a .browserslistrc
0.5%
last 2 versions
not dead

and done
npx update-browserslist-db@latest

Mmmmm. As you ask for PostCSS this site is a old one i wanted to update.
And strangely I use “{{- $css = $css | css.PostCSS $options}}” and have no PostCSS file.

And I have import “github.com/bep/hugo-starter-tailwind-basic/v2”, and use a “tailwind.config.js” file.

I just do not remember the rationale around this. may be the cause is in this half baked postcss/tailwind and my fault.

Will try to understand my choice digging in commits.

I ran into this on a site hosted by Netlify:

ERROR error building site: render: [en v1.0.0 guest] failed to render pages: render of “/opt/build/cache/hugo_cache/modules/filecache/modules/pkg/mod/github.com/jmooring/hugo-module-feature-test@v0.6.0/content/other-tests/postcss.md” failed: “/opt/build/cache/hugo_cache/modules/filecache/modules/pkg/mod/github.com/jmooring/hugo-module-feature-test@v0.6.0/layouts/other-tests/postcss/page.html:15:15”: execute of template failed: template: other-tests/postcss/page.html:15:15: executing “main” at <.Content>: error calling Content: POSTCSS: failed to transform “/temp/css” (application/octet-stream): Error: Access to this API has been restricted. Use --allow-fs-read to manage permissions.

Repository: https://github.com/jmooring/hosting-netlify

There are no errors when I build locally, nor are there errors when using a CI/CD workflow with:

  • Amplify
  • Cloudflare Pages
  • Cloudflare Worker
  • Firebase
  • GitHub Pages
  • GitLab Pages
  • Render
  • Vercel

I could get that working on Netlify by shifting to ESM style imports in the postcss config:

There’s a longer story here, and possibly something to be improved on in Hugo, but it’s not entirely clear to me at the moment:

  • Your site also works if you run hugo mod vendor and commit the source, which suggests there some Node magic walking upwards.
  • Your site also works if you set HUGO_CACHEDIR to e.g. */opt/build/repo/.hugo_cache"
  • The error points at /opt/build/cache/node_modules/autoprefixer/package.json and Hugo’s default cache dir on Netlify is /opt/build/cache/hugo_cache/ – which indicates that the overlap makes Node try to resolve inside the node_modules cache.

I have tested the PR below OK on your projected in a similar setup as Netlify. I would say that Netlify (and I also think Cloudflare) way of placing a node_modules folder at the root of the cache dir is very shaky/buggy, but I’m guessing that ship has sailed.

In our documentation, the configuration files used with css.PostCSS, js.Babel, and resources.PostProcess are now shown as .mjs files using ESM syntax.

This works fine on Netlify as noted above, and with v0.163.3 and later the .mjs files can also be used in modules.

If you use the postcss-preset-env plugin, there is an additional error, even after converting the config to ESM:

ERROR error building site: render: [de v1.0.0 guest] failed to render pages: render of "/my/path/content/de/blog/myblog/index.md" failed: "/my/path/layouts/_default/baseof.html:35:42": execute of template failed: template: single.html:35:42: executing "single.html" at <$style.RelPermalink>: error calling RelPermalink: POSTCSS: failed to transform "/styles/index.css" (text/css): 
Error: Access to this API has been restricted. Use --allow-fs-read to manage permissions.
    at Object.existsSync (node:fs:289:18)
    at isFile (/my/path/node_modules/browserslist/node.js:42:19)
    at /my/path/node_modules/browserslist/node.js:200:16
    at eachParent (/my/path/node_modules/browserslist/node.js:53:18)
    at Object.getStat (/my/path/node_modules/browserslist/node.js:198:15)
    at Object.browserslist [as default] (/my/path/node_modules/browserslist/index.js:439:19)
    at plugin (/my/path/node_modules/postcss-preset-env/dist/index.js:437:61)
    at file:///my/path/postcss.config.mjs?t=1782769228990:12:9
    at ModuleJob.run (node:internal/modules/esm/module_job:413:25)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:660:26) {
  code: 'ERR_ACCESS_DENIED',
  permission: 'FileSystemRead',
  resource: '/my/browserslist-stats.json'
}

The problem is that Browserslist walks up the directory tree until it finds a browserlists-stats.json: browserslist/node.js at 03ba4e2724b4f77c408498d937c352c2e38f9e72 · browserslist/browserslist · GitHub

If there isn’t one in the project directory, Hugo blocks the reads by default.

I was able to solve this by explicitly allowing the reads in my config:

[security]
  [security.node]
    [security.node.permissions]
      allowRead = ['.', '/**/browserslist-stats.json']

Alternatively, you could also just create a browserslist-stats.json containing an empty object ({}).