Skip to content

Commit ff892e8

Browse files
committed
lib,permission: add permission.drop
Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com>
1 parent f4c50a8 commit ff892e8

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
@@ -3214,6 +3214,65 @@ process.permission.has('fs.read', './README.md');
32143214
process.permission.has('fs.read');
32153215
```
32163216
3217+
### `process.permission.drop(scope[, reference])`
3218+
3219+
<!-- YAML
3220+
added: REPLACEME
3221+
-->
3222+
3223+
> Stability: 1.1 - Active Development
3224+
3225+
* `scope` {string}
3226+
* `reference` {string}
3227+
3228+
Drops the specified permission from the current process. This operation is
3229+
**irreversible** — once a permission is dropped, it cannot be restored through
3230+
any Node.js API.
3231+
3232+
If no reference is provided, the entire scope is dropped. For example,
3233+
`process.permission.drop('fs.read')` will revoke ALL file system read
3234+
permissions.
3235+
3236+
When a reference is provided, only the permission for that specific resource
3237+
is dropped. For example, `process.permission.drop('fs.read', '/etc/myapp')`
3238+
will revoke read access to that directory while keeping other read
3239+
permissions intact.
3240+
3241+
**Important:** You can only drop the exact resource that was explicitly
3242+
granted. The reference passed to `drop()` must match the original grant:
3243+
3244+
* If a permission was granted using a wildcard (`*`), such as
3245+
`--allow-fs-read=*`, individual paths cannot be dropped - only the entire
3246+
scope can be dropped (by calling `drop()` without a reference).
3247+
* If a directory was granted (e.g. `--allow-fs-read=/my/folder`), you cannot
3248+
drop access to individual files inside it. You must drop the same directory
3249+
that was granted. Any remaining grants continue to apply.
3250+
3251+
The available scopes are the same as [`process.permission.has()`][]:
3252+
3253+
* `fs` - All File System (drops both read and write)
3254+
* `fs.read` - File System read operations
3255+
* `fs.write` - File System write operations
3256+
* `child` - Child process spawning operations
3257+
* `worker` - Worker thread spawning operation
3258+
* `net` - Network operations
3259+
* `inspector` - Inspector operations
3260+
* `wasi` - WASI operations
3261+
* `addon` - Native addon operations
3262+
3263+
```js
3264+
const fs = require('node:fs');
3265+
3266+
// Read configuration during startup
3267+
const config = fs.readFileSync('/etc/myapp/config.json', 'utf8');
3268+
3269+
// Drop read access to the config directory after initialization
3270+
process.permission.drop('fs.read', '/etc/myapp');
3271+
3272+
// This will now throw ERR_ACCESS_DENIED
3273+
fs.readFileSync('/etc/myapp/config.json');
3274+
```
3275+
32173276
## `process.pid`
32183277
32193278
<!-- YAML
@@ -4642,6 +4701,7 @@ cases:
46424701
[`process.hrtime()`]: #processhrtimetime
46434702
[`process.hrtime.bigint()`]: #processhrtimebigint
46444703
[`process.kill()`]: #processkillpid-signal
4704+
[`process.permission.has()`]: #processpermissionhasscope-reference
46454705
[`process.setUncaughtExceptionCaptureCallback()`]: #processsetuncaughtexceptioncapturecallbackfn
46464706
[`promise.catch()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
46474707
[`queueMicrotask()`]: globals.md#queuemicrotaskcallback

lib/internal/process/permission.js

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

3636
return permission.has(scope, reference);
3737
},
38+
drop(scope, reference) {
39+
validateString(scope, 'scope');
40+
if (reference != null) {
41+
if (isBuffer(reference)) {
42+
validateBuffer(reference, 'reference');
43+
} else {
44+
validateString(reference, 'reference');
45+
}
46+
}
47+
48+
permission.drop(scope, reference);
49+
},
3850
availableFlags() {
3951
if (_ffi === undefined) {
4052
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
@@ -660,7 +660,7 @@ function initializePermission() {
660660
};
661661
// Guarantee path module isn't monkey-patched to bypass permission model
662662
ObjectFreeze(require('path'));
663-
const { has } = require('internal/process/permission');
663+
const { has, drop } = require('internal/process/permission');
664664
const warnFlags = [
665665
'--allow-addons',
666666
'--allow-child-process',
@@ -712,6 +712,7 @@ function initializePermission() {
712712
configurable: false,
713713
value: {
714714
has,
715+
drop,
715716
},
716717
});
717718
} 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)