Skip to content
Merged
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
29 changes: 20 additions & 9 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1965,18 +1965,29 @@ def configure_node(o):
msvc_dir = target_arch # 'x64' or 'arm64'

vc_tools_dir = os.environ.get('VCToolsInstallDir', '')
if vc_tools_dir:
clang_profile_lib = os.path.join(vc_tools_dir, 'lib', msvc_dir, lib_name)
if os.path.isfile(clang_profile_lib):
o['variables']['clang_profile_lib'] = clang_profile_lib
else:
raise Exception(
f'PGO profile runtime library not found at {clang_profile_lib}. '
'Ensure the ClangCL toolset is installed.')
else:
if not vc_tools_dir:
raise Exception(
'VCToolsInstallDir not set. Run from a Visual Studio command prompt.')

# Primary location: VS2026 and VS2022 x64
candidates = [os.path.join(vc_tools_dir, 'lib', msvc_dir, lib_name)]

# Secondary location: VS2022 arm64 fallback
clang_major = options.clang_cl.split('.', 1)[0]
candidates.append(os.path.normpath(os.path.join(
vc_tools_dir, '..', '..', 'Llvm', msvc_dir,
'lib', 'clang', clang_major, 'lib', 'windows', lib_name)))

clang_profile_lib = next(
(p for p in candidates if os.path.isfile(p)), None)
if clang_profile_lib:
o['variables']['clang_profile_lib'] = clang_profile_lib
else:
raise Exception(
f'PGO profile runtime library {lib_name} not found. Searched:\n ' +
'\n '.join(candidates) +
'\nEnsure the ClangCL toolset is installed.')

if flavor != 'win' and options.enable_thin_lto:
raise Exception(
'Use --enable-lto instead.')
Expand Down
37 changes: 36 additions & 1 deletion doc/api/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ flag. For WASI, use the [`--allow-wasi`][] flag. For FFI, use the

When enabling the Permission Model through the [`--permission`][]
flag a new property `permission` is added to the `process` object.
This property contains one function:
This property contains the following functions:

##### `permission.has(scope[, reference])`

Expand All @@ -92,6 +92,41 @@ process.permission.has('fs.read'); // true
process.permission.has('fs.read', '/home/rafaelgss/protected-folder'); // false
```

##### `permission.drop(scope[, reference])`

API call to drop permissions at runtime. This operation is **irreversible**.

When called without a reference, the entire scope is dropped. When called
with a reference, only the permission for that specific resource is revoked.
Dropping a permission only affects future access checks. It does not close or
revoke access to resources that are already open, such as file descriptors,
network sockets, child processes, or worker threads. Applications are
responsible for closing or terminating those resources when they are no longer
needed.

You can only drop the exact resource that was explicitly granted. The
reference passed to `drop()` must match the original grant. If a permission
was granted using a wildcard (`*`), only the entire scope can be dropped
(by calling `drop()` without a reference). If a directory was granted
(e.g. `--allow-fs-read=/my/folder`), you cannot drop individual files
inside it - you must drop the same directory that was originally granted.

```js
const fs = require('node:fs');

// Read config at startup while we still have permission
const config = fs.readFileSync('/etc/myapp/config.json', 'utf8');

// Drop read access to /etc/myapp after initialization
process.permission.drop('fs.read', '/etc/myapp');

// This will now throw ERR_ACCESS_DENIED
process.permission.has('fs.read', '/etc/myapp/config.json'); // false

// Drop child process permission entirely
process.permission.drop('child');
```

#### File System Permissions

The Permission Model, by default, restricts access to the file system through the `node:fs` module.
Expand Down
60 changes: 60 additions & 0 deletions doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -3168,6 +3168,65 @@ process.permission.has('fs.read', './README.md');
process.permission.has('fs.read');
```

### `process.permission.drop(scope[, reference])`

<!-- YAML
added: REPLACEME
-->

> Stability: 1.1 - Active Development

* `scope` {string}
* `reference` {string}

Drops the specified permission from the current process. This operation is
**irreversible** — once a permission is dropped, it cannot be restored through
any Node.js API.

If no reference is provided, the entire scope is dropped. For example,
`process.permission.drop('fs.read')` will revoke ALL file system read
permissions.

When a reference is provided, only the permission for that specific resource
is dropped. For example, `process.permission.drop('fs.read', '/etc/myapp')`
will revoke read access to that directory while keeping other read
permissions intact.

**Important:** You can only drop the exact resource that was explicitly
granted. The reference passed to `drop()` must match the original grant:

* If a permission was granted using a wildcard (`*`), such as
`--allow-fs-read=*`, individual paths cannot be dropped - only the entire
scope can be dropped (by calling `drop()` without a reference).
* If a directory was granted (e.g. `--allow-fs-read=/my/folder`), you cannot
drop access to individual files inside it. You must drop the same directory
that was granted. Any remaining grants continue to apply.

The available scopes are the same as [`process.permission.has()`][]:

* `fs` - All File System (drops both read and write)
* `fs.read` - File System read operations
* `fs.write` - File System write operations
* `child` - Child process spawning operations
* `worker` - Worker thread spawning operation
* `net` - Network operations
* `inspector` - Inspector operations
* `wasi` - WASI operations
* `addon` - Native addon operations

```js
const fs = require('node:fs');

// Read configuration during startup
const config = fs.readFileSync('/etc/myapp/config.json', 'utf8');

// Drop read access to the config directory after initialization
process.permission.drop('fs.read', '/etc/myapp');

// This will now throw ERR_ACCESS_DENIED
fs.readFileSync('/etc/myapp/config.json');
```

## `process.pid`

<!-- YAML
Expand Down Expand Up @@ -4585,6 +4644,7 @@ cases:
[`process.hrtime()`]: #processhrtimetime
[`process.hrtime.bigint()`]: #processhrtimebigint
[`process.kill()`]: #processkillpid-signal
[`process.permission.has()`]: #processpermissionhasscope-reference
[`process.setUncaughtExceptionCaptureCallback()`]: #processsetuncaughtexceptioncapturecallbackfn
[`promise.catch()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
[`queueMicrotask()`]: globals.md#queuemicrotaskcallback
Expand Down
8 changes: 6 additions & 2 deletions doc/api/sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -820,8 +820,12 @@ added:

* `changeset` {Uint8Array} A binary changeset or patchset.
* `options` {Object} The configuration options for how the changes will be applied.
* `filter` {Function} Skip changes that, when targeted table name is supplied to this function, return a truthy value.
By default, all changes are attempted.
* `filter` {Function} for each table affected by at least
one change in the changeset, the `filter` callback is invoked with the
table name as the first argument. If the return value is falsy, then no
attempt is made to apply any changes to the table.
Otherwise, if the return value is truthy or no `filter` callback is provided,
all changes related to the table are attempted.
* `onConflict` {Function} A function that determines how to handle conflicts. The function receives one argument,
which can be one of the following values:

Expand Down
1 change: 1 addition & 0 deletions lib/eslint.config_partial.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ export default [
'node-core/alphabetize-errors': 'error',
'node-core/alphabetize-primordials': 'error',
'node-core/avoid-prototype-pollution': 'error',
'node-core/iterator-result-done-first': 'error',
'node-core/lowercase-name-for-primitive': 'error',
'node-core/non-ascii-character': 'error',
'node-core/no-array-destructuring': 'error',
Expand Down
2 changes: 1 addition & 1 deletion lib/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -1032,7 +1032,7 @@ async function once(emitter, name, options = kEmptyObject) {
}

function createIterResult(value, done) {
return { value, done };
return { done, value };
}

function eventTargetAgnosticRemoveListener(emitter, name, listener, flags) {
Expand Down
8 changes: 4 additions & 4 deletions lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,7 @@ if (getOptionValue('--experimental-stream-iter')) {
done = true;
cleanup();
}
return { value: undefined, done: true };
return { done: true, value: undefined };
}
const toRead = remaining > 0 ?
MathMin(readSize, remaining) : readSize;
Expand All @@ -673,20 +673,20 @@ if (getOptionValue('--experimental-stream-iter')) {
if (bytesRead === 0) {
done = true;
cleanup();
return { value: undefined, done: true };
return { done: true, value: undefined };
}
if (pos >= 0) pos += bytesRead;
if (remaining > 0) remaining -= bytesRead;
const chunk = bytesRead < toRead ?
buf.subarray(0, bytesRead) : buf;
return { value: [chunk], done: false };
return { done: false, value: [chunk] };
},
return() {
if (!done) {
done = true;
cleanup();
}
return { value: undefined, done: true };
return { done: true, value: undefined };
},
};
},
Expand Down
12 changes: 12 additions & 0 deletions lib/internal/process/permission.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ module.exports = ObjectFreeze({

return permission.has(scope, reference);
},
drop(scope, reference) {
validateString(scope, 'scope');
if (reference != null) {
if (isBuffer(reference)) {
validateBuffer(reference, 'reference');
} else {
validateString(reference, 'reference');
}
}

permission.drop(scope, reference);
},
availableFlags() {
if (_ffi === undefined) {
const { getOptionValue } = require('internal/options');
Expand Down
3 changes: 2 additions & 1 deletion lib/internal/process/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ function initializePermission() {
};
// Guarantee path module isn't monkey-patched to bypass permission model
ObjectFreeze(require('path'));
const { has } = require('internal/process/permission');
const { has, drop } = require('internal/process/permission');
const warnFlags = [
'--allow-addons',
'--allow-child-process',
Expand Down Expand Up @@ -732,6 +732,7 @@ function initializePermission() {
configurable: false,
value: {
has,
drop,
},
});
} else {
Expand Down
16 changes: 8 additions & 8 deletions lib/internal/streams/iter/push.js
Original file line number Diff line number Diff line change
Expand Up @@ -400,17 +400,17 @@ class PushQueue {
if (this.#writerState === 'closing' && this.#slots.length === 0) {
this.endDrained();
}
return { __proto__: null, value: result, done: false };
return { __proto__: null, done: false, value: result };
}

// Buffer empty and writer closing = drain complete
if (this.#writerState === 'closing') {
this.endDrained();
return { __proto__: null, value: undefined, done: true };
return { __proto__: null, done: true, value: undefined };
}

if (this.#writerState === 'closed') {
return { __proto__: null, value: undefined, done: true };
return { __proto__: null, done: true, value: undefined };
}

if (this.#writerState === 'errored' && this.#error) {
Expand Down Expand Up @@ -474,14 +474,14 @@ class PushQueue {
const pending = this.#pendingReads.shift();
const result = this.#drain();
this.#resolvePendingWrites();
pending.resolve({ __proto__: null, value: result, done: false });
pending.resolve({ __proto__: null, done: false, value: result });
} else if (this.#writerState === 'closing' && this.#slots.length === 0) {
this.endDrained();
const pending = this.#pendingReads.shift();
pending.resolve({ __proto__: null, value: undefined, done: true });
pending.resolve({ __proto__: null, done: true, value: undefined });
} else if (this.#writerState === 'closed') {
const pending = this.#pendingReads.shift();
pending.resolve({ __proto__: null, value: undefined, done: true });
pending.resolve({ __proto__: null, done: true, value: undefined });
} else if (this.#writerState === 'errored' && this.#error) {
const pending = this.#pendingReads.shift();
pending.reject(this.#error);
Expand Down Expand Up @@ -694,11 +694,11 @@ function createReadable(queue) {
},
async return() {
queue.consumerReturn();
return { __proto__: null, value: undefined, done: true };
return { __proto__: null, done: true, value: undefined };
},
async throw(error) {
queue.consumerThrow(error);
return { __proto__: null, value: undefined, done: true };
return { __proto__: null, done: true, value: undefined };
},
};
},
Expand Down
16 changes: 12 additions & 4 deletions lib/internal/test_runner/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1812,15 +1812,22 @@ class Suite extends Test {
publishError(err);
}
this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure));
} finally {
if (testChannel.end.hasSubscribers) {
publishEnd();
}
}

this.#publishEnd = publishEnd;
this.buildPhaseFinished = true;
}

#publishEnd = null;

#publishSuiteEnd() {
const publishEnd = this.#publishEnd;
this.#publishEnd = null;
if (publishEnd !== null && testChannel.end.hasSubscribers) {
publishEnd();
}
}

#ctx;
getCtx() {
this.#ctx ??= new TestContext(this);
Expand Down Expand Up @@ -1872,6 +1879,7 @@ class Suite extends Test {
}
} finally {
stopPromise?.[SymbolDispose]();
this.#publishSuiteEnd();
}

this.postRun();
Expand Down
4 changes: 2 additions & 2 deletions lib/internal/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,8 @@ class URLSearchParamsIterator {
const len = values.length;
if (index >= len) {
return {
value: undefined,
done: true,
value: undefined,
};
}

Expand All @@ -261,8 +261,8 @@ class URLSearchParamsIterator {
}

return {
value: result,
done: false,
value: result,
};
}

Expand Down
Loading
Loading