Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/internal/process/permission.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ module.exports = ObjectFreeze({
isAuditMode() {
if (_audit === undefined) {
const { getOptionValue } = require('internal/options');
_audit = getOptionValue('--permission-audit');
// --permission takes precedence over --permission-audit.
_audit = getOptionValue('--permission-audit') &&
!getOptionValue('--permission');
}
return _audit;
},
Expand Down
6 changes: 4 additions & 2 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1111,7 +1111,9 @@ Environment::Environment(IsolateData* isolate_data,
if (options_->permission || options_->permission_audit) {
permission()->EnablePermissions();
static const std::array args = {std::string("*")};
if (options_->permission_audit) {
// --permission takes precedence over --permission-audit.
const bool audit_only = options_->permission_audit && !options_->permission;
if (audit_only) {
permission()->EnableWarningOnly();
}
// The process shouldn't be able to neither
Expand All @@ -1121,7 +1123,7 @@ Environment::Environment(IsolateData* isolate_data,
// In audit mode addon loading must stay enabled: the denial is
// published through the diagnostics channel by the permission
// check in DLOpen() instead of being rejected upfront.
if (!options_->permission_audit) {
if (!audit_only) {
options_->allow_native_addons = false;
}
permission()->Apply(this, args, permission::PermissionScope::kAddon);
Expand Down
3 changes: 2 additions & 1 deletion src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1416,7 +1416,8 @@ InitializeOncePerProcessInternal(
permission::ParseEnvAllowList(env_options->allow_env);
const bool allow_all =
std::find(allow.begin(), allow.end(), "*") != allow.end();
if (env_options->permission_audit && !allow_all) {
if (env_options->permission_audit && !env_options->permission &&
!allow_all) {
// In audit mode nothing is removed, but accesses to the variables that
// enforcing the permission model would remove are published.
permission::RecordAuditedEnvironmentVariables(allow);
Expand Down
28 changes: 28 additions & 0 deletions test/parallel/test-permission-audit-fs-does-not-deny.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,31 @@
test('permission-audit logs fs denial without throwing (eval)', () => {
runAudit('eval');
});

// --permission takes precedence over --permission-audit, so the read is denied.
function runEnforced(args, extraEnv) {
const { status, stdout, stderr } = spawnSync(
process.execPath,
[...args, '-e', `
try {
require('node:fs').readFileSync(${JSON.stringify(blockedFile)});
console.log('RESULT NO_THROW');
} catch (e) {
console.log('RESULT THREW ' + e.code);
}
`],
{ encoding: 'utf8', env: { ...process.env, ...extraEnv } },
);
assert.strictEqual(status, 0, stderr);
assert.ok(stdout.split('\n').includes('RESULT THREW ERR_ACCESS_DENIED'),

Check failure on line 76 in test/parallel/test-permission-audit-fs-does-not-deny.js

View workflow job for this annotation

GitHub Actions / x86_64-darwin: with shared libraries / build

--- stdout --- Test failure: '--permission takes precedence over --permission-audit in NODE_OPTIONS' Location: test/parallel/test-permission-audit-fs-does-not-deny.js:84:1 AssertionError [ERR_ASSERTION]: RESULT NO_THROW at runEnforced (/Users/runner/work/_temp/node-v27.0.0-nightly2026-10-26cb9a2297b8-slim/test/parallel/test-permission-audit-fs-does-not-deny.js:76:10) at TestContext.<anonymous> (/Users/runner/work/_temp/node-v27.0.0-nightly2026-10-26cb9a2297b8-slim/test/parallel/test-permission-audit-fs-does-not-deny.js:86:3) at Test.runInAsyncScope (node:internal/test_runner/test:897:14) at Test.run (node:internal/test_runner/test:1447:25) at Test.processPendingSubtests (node:internal/test_runner/test:1019:18) at Test.postRun (node:internal/test_runner/test:1587:19) at Test.run (node:internal/test_runner/test:1512:12) at async Test.processPendingSubtests (node:internal/test_runner/test:1019:7) { generatedMessage: false, code: 'ERR_ASSERTION', actual: false, expected: true, operator: '==', diff: 'simple' } Command: out/Release/node --test-reporter=./test/common/test-error-reporter.js --test-reporter-destination=stdout /Users/runner/work/_temp/node-v27.0.0-nightly2026-10-26cb9a2297b8-slim/test/parallel/test-permission-audit-fs-does-not-deny.js

Check failure on line 76 in test/parallel/test-permission-audit-fs-does-not-deny.js

View workflow job for this annotation

GitHub Actions / aarch64-darwin: with shared libraries / build

--- stdout --- Test failure: '--permission takes precedence over --permission-audit in NODE_OPTIONS' Location: test/parallel/test-permission-audit-fs-does-not-deny.js:84:1 AssertionError [ERR_ASSERTION]: RESULT NO_THROW at runEnforced (/Users/runner/work/_temp/node-v27.0.0-nightly2026-10-26cb9a2297b8-slim/test/parallel/test-permission-audit-fs-does-not-deny.js:76:10) at TestContext.<anonymous> (/Users/runner/work/_temp/node-v27.0.0-nightly2026-10-26cb9a2297b8-slim/test/parallel/test-permission-audit-fs-does-not-deny.js:86:3) at Test.runInAsyncScope (node:internal/test_runner/test:897:14) at Test.run (node:internal/test_runner/test:1447:25) at Test.processPendingSubtests (node:internal/test_runner/test:1019:18) at Test.postRun (node:internal/test_runner/test:1587:19) at Test.run (node:internal/test_runner/test:1512:12) at async Test.processPendingSubtests (node:internal/test_runner/test:1019:7) { generatedMessage: false, code: 'ERR_ASSERTION', actual: false, expected: true, operator: '==', diff: 'simple' } Command: out/Release/node --test-reporter=./test/common/test-error-reporter.js --test-reporter-destination=stdout /Users/runner/work/_temp/node-v27.0.0-nightly2026-10-26cb9a2297b8-slim/test/parallel/test-permission-audit-fs-does-not-deny.js
stdout);
}

test('--permission takes precedence over --permission-audit', () => {
runEnforced(['--permission', '--permission-audit']);
});

test('--permission takes precedence over --permission-audit in NODE_OPTIONS', () => {
runEnforced(['--permission'], { NODE_OPTIONS: '--permission-audit' });
runEnforced(['--permission-audit'], { NODE_OPTIONS: '--permission' });
});
10 changes: 10 additions & 0 deletions test/parallel/test-permission-env-scrub.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,16 @@ function run(...flags) {
assert.strictEqual(result.hasAll, false);
}

// --permission takes precedence over --permission-audit.
{
const result = run(
'--permission', '--permission-audit', '--allow-env=PERMISSION_ENV_ALLOWED');
assert.deepStrictEqual(result.keys, ['PERMISSION_ENV_ALLOWED', 'TZ']);
assert.strictEqual(result.has.PERMISSION_ENV_SECRET, false);
assert.strictEqual(result.has.PERMISSION_ENV_ALLOWED, true);
assert.strictEqual(result.hasAll, false);
}

// --allow-env requires the permission model.
spawnSyncAndExit(
process.execPath,
Expand Down
Loading