Skip to content

Commit 956e2a2

Browse files
authored
lib,permission: add permission.drop
Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com> PR-URL: #62672 Refs: #62223 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 460c350 commit 956e2a2

36 files changed

Lines changed: 928 additions & 2 deletions

doc/api/permissions.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ flag. For WASI, use the [`--allow-wasi`][] flag. For FFI, use the
7878

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

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

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

95+
##### `permission.drop(scope[, reference])`
96+
97+
API call to drop permissions at runtime. This operation is **irreversible**.
98+
99+
When called without a reference, the entire scope is dropped. When called
100+
with a reference, only the permission for that specific resource is revoked.
101+
Dropping a permission only affects future access checks. It does not close or
102+
revoke access to resources that are already open, such as file descriptors,
103+
network sockets, child processes, or worker threads. Applications are
104+
responsible for closing or terminating those resources when they are no longer
105+
needed.
106+
107+
You can only drop the exact resource that was explicitly granted. The
108+
reference passed to `drop()` must match the original grant. If a permission
109+
was granted using a wildcard (`*`), only the entire scope can be dropped
110+
(by calling `drop()` without a reference). If a directory was granted
111+
(e.g. `--allow-fs-read=/my/folder`), you cannot drop individual files
112+
inside it - you must drop the same directory that was originally granted.
113+
114+
```js
115+
const fs = require('node:fs');
116+
117+
// Read config at startup while we still have permission
118+
const config = fs.readFileSync('/etc/myapp/config.json', 'utf8');
119+
120+
// Drop read access to /etc/myapp after initialization
121+
process.permission.drop('fs.read', '/etc/myapp');
122+
123+
// This will now throw ERR_ACCESS_DENIED
124+
process.permission.has('fs.read', '/etc/myapp/config.json'); // false
125+
126+
// Drop child process permission entirely
127+
process.permission.drop('child');
128+
```
129+
95130
#### File System Permissions
96131

97132
The Permission Model, by default, restricts access to the file system through the `node:fs` module.

doc/api/process.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3168,6 +3168,65 @@ process.permission.has('fs.read', './README.md');
31683168
process.permission.has('fs.read');
31693169
```
31703170
3171+
### `process.permission.drop(scope[, reference])`
3172+
3173+
<!-- YAML
3174+
added: REPLACEME
3175+
-->
3176+
3177+
> Stability: 1.1 - Active Development
3178+
3179+
* `scope` {string}
3180+
* `reference` {string}
3181+
3182+
Drops the specified permission from the current process. This operation is
3183+
**irreversible** — once a permission is dropped, it cannot be restored through
3184+
any Node.js API.
3185+
3186+
If no reference is provided, the entire scope is dropped. For example,
3187+
`process.permission.drop('fs.read')` will revoke ALL file system read
3188+
permissions.
3189+
3190+
When a reference is provided, only the permission for that specific resource
3191+
is dropped. For example, `process.permission.drop('fs.read', '/etc/myapp')`
3192+
will revoke read access to that directory while keeping other read
3193+
permissions intact.
3194+
3195+
**Important:** You can only drop the exact resource that was explicitly
3196+
granted. The reference passed to `drop()` must match the original grant:
3197+
3198+
* If a permission was granted using a wildcard (`*`), such as
3199+
`--allow-fs-read=*`, individual paths cannot be dropped - only the entire
3200+
scope can be dropped (by calling `drop()` without a reference).
3201+
* If a directory was granted (e.g. `--allow-fs-read=/my/folder`), you cannot
3202+
drop access to individual files inside it. You must drop the same directory
3203+
that was granted. Any remaining grants continue to apply.
3204+
3205+
The available scopes are the same as [`process.permission.has()`][]:
3206+
3207+
* `fs` - All File System (drops both read and write)
3208+
* `fs.read` - File System read operations
3209+
* `fs.write` - File System write operations
3210+
* `child` - Child process spawning operations
3211+
* `worker` - Worker thread spawning operation
3212+
* `net` - Network operations
3213+
* `inspector` - Inspector operations
3214+
* `wasi` - WASI operations
3215+
* `addon` - Native addon operations
3216+
3217+
```js
3218+
const fs = require('node:fs');
3219+
3220+
// Read configuration during startup
3221+
const config = fs.readFileSync('/etc/myapp/config.json', 'utf8');
3222+
3223+
// Drop read access to the config directory after initialization
3224+
process.permission.drop('fs.read', '/etc/myapp');
3225+
3226+
// This will now throw ERR_ACCESS_DENIED
3227+
fs.readFileSync('/etc/myapp/config.json');
3228+
```
3229+
31713230
## `process.pid`
31723231
31733232
<!-- YAML
@@ -4585,6 +4644,7 @@ cases:
45854644
[`process.hrtime()`]: #processhrtimetime
45864645
[`process.hrtime.bigint()`]: #processhrtimebigint
45874646
[`process.kill()`]: #processkillpid-signal
4647+
[`process.permission.has()`]: #processpermissionhasscope-reference
45884648
[`process.setUncaughtExceptionCaptureCallback()`]: #processsetuncaughtexceptioncapturecallbackfn
45894649
[`promise.catch()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
45904650
[`queueMicrotask()`]: globals.md#queuemicrotaskcallback

lib/internal/process/permission.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ module.exports = ObjectFreeze({
4343

4444
return permission.has(scope, reference);
4545
},
46+
drop(scope, reference) {
47+
validateString(scope, 'scope');
48+
if (reference != null) {
49+
if (isBuffer(reference)) {
50+
validateBuffer(reference, 'reference');
51+
} else {
52+
validateString(reference, 'reference');
53+
}
54+
}
55+
56+
permission.drop(scope, reference);
57+
},
4658
availableFlags() {
4759
if (_ffi === undefined) {
4860
const { getOptionValue } = require('internal/options');

lib/internal/process/pre_execution.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ function initializePermission() {
680680
};
681681
// Guarantee path module isn't monkey-patched to bypass permission model
682682
ObjectFreeze(require('path'));
683-
const { has } = require('internal/process/permission');
683+
const { has, drop } = require('internal/process/permission');
684684
const warnFlags = [
685685
'--allow-addons',
686686
'--allow-child-process',
@@ -732,6 +732,7 @@ function initializePermission() {
732732
configurable: false,
733733
value: {
734734
has,
735+
drop,
735736
},
736737
});
737738
} else {

src/permission/addon_permission.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ void AddonPermission::Apply(Environment* env,
1414
deny_all_ = true;
1515
}
1616

17+
void AddonPermission::Drop(Environment* env,
18+
PermissionScope scope,
19+
const std::string_view& param) {
20+
deny_all_ = true;
21+
}
22+
1723
bool AddonPermission::is_granted(Environment* env,
1824
PermissionScope perm,
1925
const std::string_view& param) const {

src/permission/addon_permission.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ class AddonPermission final : public PermissionBase {
1515
void Apply(Environment* env,
1616
const std::vector<std::string>& allow,
1717
PermissionScope scope) override;
18+
void Drop(Environment* env,
19+
PermissionScope scope,
20+
const std::string_view& param = "") override;
1821
bool is_granted(Environment* env,
1922
PermissionScope perm,
2023
const std::string_view& param = "") const override;

src/permission/child_process_permission.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ void ChildProcessPermission::Apply(Environment* env,
1515
deny_all_ = true;
1616
}
1717

18+
void ChildProcessPermission::Drop(Environment* env,
19+
PermissionScope scope,
20+
const std::string_view& param) {
21+
deny_all_ = true;
22+
}
23+
1824
bool ChildProcessPermission::is_granted(Environment* env,
1925
PermissionScope perm,
2026
const std::string_view& param) const {

src/permission/child_process_permission.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ class ChildProcessPermission final : public PermissionBase {
1515
void Apply(Environment* env,
1616
const std::vector<std::string>& allow,
1717
PermissionScope scope) override;
18+
void Drop(Environment* env,
19+
PermissionScope scope,
20+
const std::string_view& param = "") override;
1821
bool is_granted(Environment* env,
1922
PermissionScope perm,
2023
const std::string_view& param = "") const override;

src/permission/ffi_permission.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ void FFIPermission::Apply(Environment* env,
1414
deny_all_ = true;
1515
}
1616

17+
void FFIPermission::Drop(Environment* env,
18+
PermissionScope scope,
19+
const std::string_view& param) {
20+
deny_all_ = true;
21+
}
22+
1723
bool FFIPermission::is_granted(Environment* env,
1824
PermissionScope perm,
1925
const std::string_view& param) const {

src/permission/ffi_permission.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ class FFIPermission final : public PermissionBase {
1515
void Apply(Environment* env,
1616
const std::vector<std::string>& allow,
1717
PermissionScope scope) override;
18+
void Drop(Environment* env,
19+
PermissionScope scope,
20+
const std::string_view& param = "") override;
1821
bool is_granted(Environment* env,
1922
PermissionScope perm,
2023
const std::string_view& param = "") const override;

0 commit comments

Comments
 (0)