Title: permission: process.permission.drop() aborts (SIGABRT) when two --allow-fs-read grants collide at their first wildcard · Issue #66901 · nodejs/node · GitHub
Open Graph Title: permission: process.permission.drop() aborts (SIGABRT) when two --allow-fs-read grants collide at their first wildcard · Issue #66901 · nodejs/node
X Title: permission: process.permission.drop() aborts (SIGABRT) when two --allow-fs-read grants collide at their first wildcard · Issue #66901 · nodejs/node
Description: Version v26.12.0 Platform Windows 11 x64. The code path is platform independent, so Linux and macOS are expected to behave the same. Subsystem permission What steps will reproduce the bug? Two --allow-fs-read grants whose text is identic...
Open Graph Description: Version v26.12.0 Platform Windows 11 x64. The code path is platform independent, so Linux and macOS are expected to behave the same. Subsystem permission What steps will reproduce the bug? Two --al...
X Description: Version v26.12.0 Platform Windows 11 x64. The code path is platform independent, so Linux and macOS are expected to behave the same. Subsystem permission What steps will reproduce the bug? Two --al...
Opengraph URL: https://github.com/nodejs/node/issues/66901
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"permission: process.permission.drop() aborts (SIGABRT) when two --allow-fs-read grants collide at their first wildcard","articleBody":"### Version\n\nv26.12.0\n\n### Platform\n\nWindows 11 x64. The code path is platform independent, so Linux and macOS are expected to behave the same.\n\n### Subsystem\n\npermission\n\n### What steps will reproduce the bug?\n\nTwo `--allow-fs-read` grants whose text is identical up to and including the first `*`, plus any `process.permission.drop()` call, abort the process.\n\n```sh\nmkdir -p /tmp/sig\ncat > /tmp/sig/app.js \u003c\u003c'EOF'\nprocess.permission.drop('fs.read', process.argv[1]);\nconsole.log('survived');\nEOF\n\nnode --permission \\\n --allow-fs-read='/tmp/sig/*' \\\n --allow-fs-read='/tmp/sig/*.js' \\\n /tmp/sig/app.js\n```\n\n### How often does it reproduce? Is there a required condition?\n\nEvery time, given both conditions:\n\n1. two `--allow-fs-read` grants identical up to and including their first `*`, and\n2. any `process.permission.drop('fs.read', ...)` call.\n\nStartup on its own is fine. Measured controls:\n\n| configuration | exit code |\n| --- | --- |\n| two colliding grants, no `drop()` | 0 |\n| two colliding grants + `drop(entrypoint)` | **134** |\n| single grant + `drop(entrypoint)` | 0 |\n| two colliding grants + `drop()` on an unrelated path | 0 |\n| two non-colliding grants + `drop(entrypoint)` | 0 |\n| collision on the `--allow-fs-write` list + `drop('fs.read', ...)` | 0 |\n\nThe last row is consistent with only the dropped scope's tree being replayed. v22.23.3 is unaffected because `process.permission.drop` does not exist on that line.\n\n### What is the expected behavior? Why is that the expected behavior?\n\n`process.permission.drop()` should either drop the requested scope or throw a catchable error. It should not abort the process.\n\nThe grant combination is a reasonable thing to write. The permission documentation encourages wildcard grants and states that characters after a `*` are ignored, so `--allow-fs-read='/tmp/*' --allow-fs-read='/tmp/*.js'` reads as redundant but harmless rather than fatal, and nothing indicates it becomes fatal once the application calls `drop()`.\n\n### What do you see instead?\n\n`SIGABRT`, exit code 134:\n\n```\nAssertion failed: !path_prefix.empty()\n at src/permission/fs_permission.h:62\n in node::permission::FSPermission::RadixTree::Node::CreateChild(std::string_view)\n\n----- JavaScript stack trace -----\n1: drop (node:internal/process/permission:66:16)\n2: /tmp/sig/app.js:1:20\n```\n\n`CHECK` is unconditional in release builds, so application code cannot catch this. A process that drops privileges after startup, which is the intended use of the API, terminates.\n\n### Additional information\n\n**Why startup is safe but `drop()` is not.**\n\n`FSPermission::GrantAccess` only inserts a path when `!Lookup(path)`. `/tmp/sig/*.js` is already covered by `/tmp/sig/*`, so the colliding insert never happens while the flags are applied and the tree is built correctly.\n\nCommit 02bae370c (\"permission: keep explicit fs grants when dropping a covering directory\", 2026-10-13) moved the `granted_paths_` `push_back` out of that `!Lookup(path)` branch so every explicit grant is recorded even when covered. `RebuildTree` then replays the whole recorded list unconditionally, so the first `drop()` inserts both colliding grants into a fresh tree and `CreateChild` trips its own `CHECK`. That makes this a regression introduced alongside the grant-retention change rather than longstanding behaviour.\n\n**Where it aborts.**\n\n`Node::CreateChild` reaches `CHECK(!path_prefix.empty())` when the remaining prefix is empty, which happens when the second grant diverges only after a `*` that both grants share. Since `Lookup` never descends into a `wildcard_child`, the subtree being built past the first `*` is unreachable to lookups, so the insert that aborts appears to be doing no useful work.\n\n**Possible directions**, offered as context rather than as a tested patch, since the right choice interacts with how `granted_paths_` is replayed: either skip insertion once a wildcard has been consumed, given that everything after the first `*` is ignored by lookups, or have `CreateChild` return early on an empty `path_prefix` instead of asserting.\n\nI considered whether this belonged in the security process and concluded it does not: it requires in-process application code to call `drop()`, and it does not meet the denial-of-service criteria in the threat model, which require the behaviour to be directly exploitable by an untrusted source without application mistakes. Filing publicly on that basis. Happy to move it if maintainers see it differently.\n\n`process.permission.drop` is documented as Stability 1.1 - Active Development.\n","author":{"url":"https://github.com/itxcrusher","@type":"Person","name":"itxcrusher"},"datePublished":"2026-10-21T14:33:26Z","url":"https://github.com/nodejs/node/issues/66901"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:4ad3cf35-a0c2-cc88-030e-709860d796eb |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | C892:197704:B6054C:ED00A1:6AED4606 |
| html-safe-nonce | 709917b4843ccd1d22cbd26e577f0acfe8f1a98f24fbf7079aac57cb45964b3e |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDODkyOjE5NzcwNDpCNjA1NEM6RUQwMEExOjZBRUQ0NjA2IiwidmlzaXRvcl9pZCI6IjYxMTU3MjQ3MTMzNDM2NjU2NzAiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | fa7a33084a39c85d2da1993af5e8bc5938ab82083cb8ce0538f9b66b3be583b6 |
| hovercard-subject-tag | issue:5962570273 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/66901/issue_layout |
| twitter:image | https://opengraph.githubassets.com/62166c9f6442b0897da47022c0741a9ec2dd0024d2e88374fc77a7fb89e87e7d/nodejs/node/issues/66901 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/62166c9f6442b0897da47022c0741a9ec2dd0024d2e88374fc77a7fb89e87e7d/nodejs/node/issues/66901 |
| og:image:alt | Version v26.12.0 Platform Windows 11 x64. The code path is platform independent, so Linux and macOS are expected to behave the same. Subsystem permission What steps will reproduce the bug? Two --al... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | itxcrusher |
| hostname | github.com |
| expected-hostname | github.com |
| None | 56fb42677ae87ed3e64410323d67980aa65d0485568a7858f874e7edfc6d1c11 |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 9c9a37e2423ed382cf39f70b5aa4b4858b40a3b0 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width