Version
System:
OS: macOS 26.4.1
CPU: (10) arm64 Apple M5
Memory: 606.77 MB / 32.00 GB
Shell: 5.9 - /bin/zsh
Browsers:
Chrome: 149.0.7827.201
Chrome Canary: 152.0.7956.0
Safari: 26.4
npmPackages:
@modern-js/app-tools: 2.68.18 => 2.68.18
@modern-js/plugin-tailwindcss: ^2.68.18 => 2.70.4
@modern-js/runtime: 2.68.18 => 2.68.18
@modern-js/server-runtime: 2.68.18 => 2.68.18
@modern-js/tsconfig: 2.68.18 => 2.68.18
@modern-js/types: 2.68.18 => 2.68.18
Details
When running modern deploy, the deploy plugin calls ndepe.nodeDepEmit() which invokes @vercel/nft's nodeFileTrace() with base: "/" and no ignore option.
In large SSR projects, the server bundle (.output/bundles/main.js) contains code patterns like:
fs.readdirSync(__dirname) (from various packages)
path.resolve(__dirname, variable) + readFile (from @modern-js/prod-server internals)
require(__dirname + '/../themes/...') (from colors package, a winston dependency)
These patterns trigger nft's emitAssetDirectory / emitWildcardRequire, which globs parent directories. Since base: "/", the glob reaches /etc/**/*.
On macOS:
Error: EACCES: permission denied, open '/etc/sudoers'
at async CachedFileSystem._internalReadFile (@vercel/nft/out/fs.js:76:21)
at async Job.emitDependency (@vercel/nft/out/node-file-trace.js:334:28)
at async nodeFileTrace (@vercel/nft/out/node-file-trace.js:62:5)
at async traceFiles (ndepe/dist/utils.js:201:17)
at async nodeDepEmit (ndepe/dist/index.js:60:23)
On Linux CI (containers with stale symlinks):
Error: ENOENT: no such file or directory, stat '/etc/alternatives/which.sl1.gz'
The build output (.output/static) is generated successfully, but the subsequent ndepe dependency tracing step fails, preventing node_modules from being bundled into .output.
Call chain
modern deploy
→ @modern-js/app-tools deploy plugin (platforms/node.js)
→ ndepe.nodeDepEmit({ appDir, sourceDir: ".output" })
→ ndepe.traceFiles({ base: "/", /* no ignore */ })
→ @vercel/nft.nodeFileTrace(entries, { base: "/" })
→ static analysis finds __dirname patterns
→ emitAssetDirectory / emitWildcardRequire
→ Globbing /etc/**/*
→ EACCES or ENOENT on system files
Reproduce link
see Reproduce Steps
Reproduce Steps
mkdir nft-repro && cd nft-repro
cat > package.json << 'EOF'
{ "dependencies": { "@vercel/nft": "0.29.2" } }
EOF
cat > trigger.js << 'EOF'
var path = require('path');
var fs = require('fs');
function loadConfig(opts) { return fs.readFileSync(path.resolve(__dirname, opts.filename), 'utf-8'); }
function listFiles() { return fs.readdirSync(__dirname); }
module.exports = { loadConfig, listFiles };
EOF
pnpm install
node -e "
const {nodeFileTrace} = require('@vercel/nft');
const fs = require('fs');
nodeFileTrace([__dirname+'/trigger.js'], {base:'/', log:true,
readFile: p=>fs.promises.readFile(p,'utf8').catch(e=>e.code==='EACCES'?'':null),
stat: p=>fs.promises.stat(p).catch(()=>null),
readlink: p=>fs.promises.readlink(p).catch(()=>null)
}).then(r=>{console.log('files:',r.fileList.size,[...r.fileList].filter(f=>f.startsWith('etc/')).length,'in /etc')});
"
- Create a Modern.js SSR project with server-side dependencies that produce the
__dirname patterns in the server bundle (e.g. winston, @sentry/node, or any package using colors)
- Run
MODERN_ENV=production modern deploy
- On macOS: fails with
EACCES: permission denied, open '/etc/sudoers'
- On Linux CI with broken symlinks: fails with
ENOENT
The issue is more likely to occur in large projects where rspack doesn't fully inline all dependencies into the server bundle.
ndepe should default-ignore system directories when calling nodeFileTrace({ base: "/" }):
const systemIgnores = ["etc/**", "private/etc/**", "dev/**", "sys/**", "proc/**"];
const res = await nodeFileTrace(entryFiles, {
base,
processCwd: sourceDir,
cache,
...traceOptions,
ignore: [...(traceOptions?.ignore || []), ...systemIgnores],
});
Alternatively, expose a deploy.traceOptions configuration in modern.config.ts so users can configure ignore patterns without patching.
I've also created a issue for nft vercel/nft#601
Version
System: OS: macOS 26.4.1 CPU: (10) arm64 Apple M5 Memory: 606.77 MB / 32.00 GB Shell: 5.9 - /bin/zsh Browsers: Chrome: 149.0.7827.201 Chrome Canary: 152.0.7956.0 Safari: 26.4 npmPackages: @modern-js/app-tools: 2.68.18 => 2.68.18 @modern-js/plugin-tailwindcss: ^2.68.18 => 2.70.4 @modern-js/runtime: 2.68.18 => 2.68.18 @modern-js/server-runtime: 2.68.18 => 2.68.18 @modern-js/tsconfig: 2.68.18 => 2.68.18 @modern-js/types: 2.68.18 => 2.68.18Details
When running
modern deploy, the deploy plugin callsndepe.nodeDepEmit()which invokes@vercel/nft'snodeFileTrace()withbase: "/"and noignoreoption.In large SSR projects, the server bundle (
.output/bundles/main.js) contains code patterns like:fs.readdirSync(__dirname)(from various packages)path.resolve(__dirname, variable) + readFile(from@modern-js/prod-serverinternals)require(__dirname + '/../themes/...')(fromcolorspackage, awinstondependency)These patterns trigger nft's
emitAssetDirectory/emitWildcardRequire, which globs parent directories. Sincebase: "/", the glob reaches/etc/**/*.On macOS:
On Linux CI (containers with stale symlinks):
The build output (
.output/static) is generated successfully, but the subsequent ndepe dependency tracing step fails, preventingnode_modulesfrom being bundled into.output.Call chain
Reproduce link
see Reproduce Steps
Reproduce Steps
__dirnamepatterns in the server bundle (e.g.winston,@sentry/node, or any package usingcolors)MODERN_ENV=production modern deployEACCES: permission denied, open '/etc/sudoers'ENOENTThe issue is more likely to occur in large projects where rspack doesn't fully inline all dependencies into the server bundle.
ndepeshould default-ignore system directories when callingnodeFileTrace({ base: "/" }):Alternatively, expose a
deploy.traceOptionsconfiguration inmodern.config.tsso users can configureignorepatterns without patching.I've also created a issue for nft vercel/nft#601