Repository navigation
process.permission.drop('inspector') aborts Node with a HandleScope fatal during exit #66973
Description
Version
v26.12.1 (same result on v26.10.0 and v26.11.1; also reproduced on v24.22.1)
Platform
Darwin 27.0.0 arm64
Subsystem
Permission Model / inspector
What steps will reproduce the bug?
This uses only built-in Node APIs. Save as repro.mjs and run node repro.mjs.
import { spawn } from 'node:child_process';
const child = spawn(process.execPath, [
'--inspect-wait=127.0.0.1:0',
'--permission',
'--allow-inspector',
'--allow-fs-read=*',
'--allow-fs-write=*',
'--eval',
"process.permission.drop('inspector'); console.log('clean-body');",
], { stdio: ['ignore', 'pipe', 'pipe'] });
let stderr = '';
let socket;
let nextId = 0;
child.stderr.on('data', (chunk) => {
stderr += chunk;
const endpoint = stderr.match(
/Debugger listening on (ws:\/\/127\.0\.0\.1:\d+\/\S+)/
)?.[1];
if (endpoint && !socket) {
socket = new WebSocket(endpoint);
socket.onopen = () => {
for (const method of [
'Debugger.enable',
'Runtime.enable',
'Runtime.runIfWaitingForDebugger',
]) {
socket.send(JSON.stringify({ id: ++nextId, method }));
}
};
}
if (stderr.includes('Waiting for the debugger to disconnect')) {
socket?.close();
}
});
child.on('close', (code, signal) => {
console.error(stderr);
console.log({ code, signal });
});How often does it reproduce? Is there a required condition?
The dropped-permission cases reproduced in every supported-version run observed: 3 of 5 cases per run on v24.21.0, v24.22.1, v26.10.0, v26.11.1, and v26.12.1. The required condition is that the process starts with --permission --allow-inspector, then calls process.permission.drop('inspector') before inspector cleanup.
What is the expected behavior? Why is that the expected behavior?
Dropping a permission should affect future access checks and report a denied operation as ERR_ACCESS_DENIED; the process should then exit cleanly. The Permission Model documentation describes enforce mode as throwing ERR_ACCESS_DENIED for denied operations and says permission.drop() affects future checks: doc/api/permissions.md at v26.12.1, doc/api/permissions.md at v26.12.1.
What do you see instead?
The child aborts with SIGABRT:
FATAL ERROR: v8::HandleScope::CreateHandle() Cannot create a handle without a HandleScope
...
node::permission::CreateAccessDeniedError
node::permission::Permission::ThrowAccessDenied
node::inspector::Agent::WaitForDisconnect
node::Environment::RunAtExitCallbacks
The control cases that retain inspector permission exit 0. A dropped-permission case that first prints an expected ERR_ACCESS_DENIED still aborts during exit.
Additional information
At v26.12.1, the inspector registers an AtExit callback that calls Agent::WaitForDisconnect() (src/inspector_agent.cc:896-901). WaitForDisconnect() begins with the inspector permission check (src/inspector_agent.cc:1053-1056). The permission macro calls ThrowAccessDenied (src/permission/permission.h:23-36), which constructs the V8 error (src/permission/permission.cc:259-288). The AtExit callback has no HandleScope, matching the fatal stack above.
The current main source at b12bad4 retains the same AtExit and WaitForDisconnect() permission-check path. The v26 changelog has no changes after v26.12.1 that mention this path.
Activity