tldr; script using fs.readFileSync throws EACCESS when called using npm
, but not using node
On an ancient (2016) Docker image, I need to run a postinstall
NPM script involving Bower (bower install --allow-root
), but whenever I do, I get EACCES: permission denied, open '/root/.config/configstore/bower-github.json'
. I found out that doing npx bower
results in the same. Running npx bower
outside of Docker works fine.
Usually, I would easily have dealt with these issues, as they normally arise whenever someone has been executing a command using sudo
when they should not have. The fix for those issues is usually to either change the owner back to the current user or just run the bower command with sudo and --allow-root
(example 1, example 2).
This, however, is not one of these issues. I am already root!
The full error is like any of the similar issues:
root@eaa32456c249:/var/www/myproj# npx bower --allow-root
/var/www/myproj/node_modules/bower/lib/node_modules/configstore/index.js:54
throw err;
^
Error: EACCES: permission denied, open '/root/.config/configstore/bower-github.json'
You don't have access to this file.
at Object.openSync (node:fs:585:3)
at Object.readFileSync (node:fs:453:35)
at Configstore.get (/var/www/myproj/node_modules/bower/lib/node_modules/configstore/index.js:35:38)
at new Configstore (/var/www/myproj/node_modules/bower/lib/node_modules/configstore/index.js:28:48)
at readCachedConfig (/var/www/myproj/node_modules/bower/lib/config.js:19:23)
at defaultConfig (/var/www/myproj/node_modules/bower/lib/config.js:11:12)
at Object.<anonymous> (/var/www/myproj/node_modules/bower/lib/index.js:16:32)
at Module._compile (node:internal/modules/cjs/loader:1101:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
at Module.load (node:internal/modules/cjs/loader:981:32) {
errno: -13,
syscall: 'open',
code: 'EACCES',
path: '/root/.config/configstore/bower-github.json'
I cannot elevate my rights any further and adding --allow-root
does not do anything. I even inspected the module in question, seeing that the call that always failed was simply this:
readFileSync(this.path, 'utf8');
where this.path
was of course '/root/.config/configstore/bower-github.json'
.
I then wrote this small test module that does the same, and it ran without issues:
root@eaa32456c249:/var/www/myproj# cat test.js
const execSync = require('child_process').execSync;
const fs = require('fs');
const path = '/root/.config/configstore/bower-github.json';
console.log('exec whoami: ', execSync('whoami').toString());
try {
const result = execSync('ls -l ' + path, { encoding: 'utf8' });
console.log('exec ls -l: ', result);
} catch (err) {}
try {
const parsed = JSON.parse(fs.readFileSync(path, 'utf8', { encoding: 'utf8' }));
console.log('parsed: ', parsed);
} catch (err) {
console.error(err.message);
}
root@eaa32456c249:/var/www/myproj# node test.js
exec whoami: root
exec ls -l: -rw-r--r-- 1 root root 3 Dec 8 22:55 /root/.config/configstore/bower-github.json
parsed: {}
A mystery!
2
Answers
tldr; NPM versions 7 and 8 will run as the owner of the root package directory. In other words, if you want to run as root, do
chown root.root -R .
on the root dir of your project. Earler and later versions lack this behavior, so upgrading NPM will also avoid the issue.Update NPM 9 reverts the change, meaning the above just applies to NPM 7 and 8. See PR.
After letting this simmer for a good while, I just figured I might check who the code was running as, so I opened the module in question (
node_modules/configstore/index.js
) and added this to the lines preceding the call that failed:Something fishy is indeed going on, as those lines printed:
So somehow, running
npx bower
asroot
makesbower
run as a user with uid=1000? Runningnpm run postinstall
results in the same issue.OK ... let's have a closer look at this. What if I run the
bower
CLI module manually usingnode
?It works - I am still root! So obviously both
npx
andnpm
is somehow doing something funky under the covers with regards to who the commands are running as!NPM,
root
and the owner of the CWDAfter my discovery of the above fact, I did some googling and came across this NPM issue, while not actually having a direct explanation set me onto the the trail of Node trying to execute as the owner of the files. And for the first time I actually checked who the owner of the files were:
Simply doing
chown root.root -R node_modules
did nothing, so I continued the search. I then read this article, which had this snippet:OK, let's try setting
unsafe-perm
to true. No go, I was still running as uid=1000. I then ventured into the actual NPM library on the search for something relevant touid
and found the answer in:For NPM 8, it uses this bit of code to determine who to run as:
and as the docs of
infer-owner
module says:Note the
cwd
part. It does not look atnode_modules
, but the current working directory! So I then didchown root.root -R .
on the root dir of the project and lo and behold, it worked!Addendum
This behavior is only for NPM versions 7 and 8. NPM versions < 7 (shipped with Node 12-14) work perfectly fine when running as root. So the quickest fix might simply be to use Node 14. Starting with NPM 9 the old behavior is back (probably shipped with Node 20).
This is a very specific solution/scenario – you’re using Docusaurus. This may happen if you set
path: '/'
indocusaurus.config.js
. Set it to something else, maybe you are trying to setrouteBasePath
and notpath
.