Commit 882adc580cf for nodejs

commit 882adc580cfee14536423bfffe67b23b860b3525
Author: Rafael Gonzaga <rafael.nunu@hotmail.com>
Date:   Thu Oct 8 17:58:31 2026 -0300

    child_process: propagate config-file permission flags to children

    Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com>
    PR-URL: https://github.com/nodejs/node/pull/66559
    Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
    Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com>

diff --git a/lib/child_process.js b/lib/child_process.js
index 2d745634649..6b411f43f07 100644
--- a/lib/child_process.js
+++ b/lib/child_process.js
@@ -38,6 +38,7 @@ const {
   ObjectPrototypeHasOwnProperty,
   PromiseWithResolvers,
   RegExpPrototypeExec,
+  RegExpPrototypeSymbolReplace,
   SafeSet,
   StringPrototypeIncludes,
   StringPrototypeIndexOf,
@@ -75,6 +76,7 @@ const {
 } = require('internal/errors');
 const { clearTimeout, setTimeout } = require('timers');
 const { getValidatedPath } = require('internal/fs/utils');
+const { getOptionValue } = require('internal/options');
 const {
   validateAbortSignal,
   validateArray,
@@ -579,17 +581,37 @@ function copyPermissionModelFlagsToEnv(env, key, args) {
   const allowAllEnv = !permission.isAuditMode();

   const flagsToCopy = getPermissionModelFlagsToCopy();
+  const copiedFlags = new SafeSet();
   for (const arg of process.execArgv) {
     if (allowAllEnv && arg.startsWith('--allow-env')) {
       continue;
     }
     for (const flag of flagsToCopy) {
       if (arg.startsWith(flag)) {
+        copiedFlags.add(flag);
         env[key] = `${env[key] ? env[key] + ' ' + arg : arg}`;
       }
     }
   }

+  // Flags set through --config-file are not part of process.execArgv.
+  for (const flag of flagsToCopy) {
+    if (copiedFlags.has(flag) || (allowAllEnv && flag === '--allow-env')) {
+      continue;
+    }
+    const value = getOptionValue(flag);
+    if (value === true) {
+      env[key] = `${env[key] ? env[key] + ' ' : ''}${flag}`;
+    } else if (ArrayIsArray(value)) {
+      for (const item of value) {
+        // Values may contain spaces or quotes (e.g. the implicitly allowed
+        // entry point), so quote them as NODE_OPTIONS expects.
+        const quoted = RegExpPrototypeSymbolReplace(/["\\]/g, item, '\\$&');
+        env[key] = `${env[key] ? env[key] + ' ' : ''}"${flag}=${quoted}"`;
+      }
+    }
+  }
+
   if (allowAllEnv) {
     env[key] = `${env[key] ? env[key] + ' ' : ''}--allow-env=*`;
   }
diff --git a/test/fixtures/permission/child-process-inherit-test.js b/test/fixtures/permission/child-process-inherit-test.js
new file mode 100644
index 00000000000..2a095b7d142
--- /dev/null
+++ b/test/fixtures/permission/child-process-inherit-test.js
@@ -0,0 +1,16 @@
+const { spawnSync } = require('child_process');
+const { status, stdout, stderr } = spawnSync(process.execPath, [
+  '-p',
+  `JSON.stringify([
+    typeof process.permission,
+    process.permission.has("fs.read"),
+    process.permission.has("fs.write"),
+    process.permission.has("child"),
+    process.permission.has("worker"),
+  ])`,
+]);
+console.log(JSON.stringify({
+  status,
+  stdout: stdout.toString().trim(),
+  stderr: stderr.toString(),
+}));
diff --git a/test/fixtures/permission/config-child-inherit-allow-only.json b/test/fixtures/permission/config-child-inherit-allow-only.json
new file mode 100644
index 00000000000..7f26f3d672e
--- /dev/null
+++ b/test/fixtures/permission/config-child-inherit-allow-only.json
@@ -0,0 +1,9 @@
+{
+    "permission": {
+        "allow-child-process": true,
+        "allow-worker": true,
+        "allow-fs-read": [
+            "*"
+        ]
+    }
+}
diff --git a/test/fixtures/permission/config-child-inherit.json b/test/fixtures/permission/config-child-inherit.json
new file mode 100644
index 00000000000..c4d4f1a2515
--- /dev/null
+++ b/test/fixtures/permission/config-child-inherit.json
@@ -0,0 +1,11 @@
+{
+    "permission": {
+        "permission": true,
+        "allow-child-process": true,
+        "allow-worker": true,
+        "allow-fs-read": [
+            "*",
+            "/nonexistent/dir with \"quotes\" and \\backslash"
+        ]
+    }
+}
diff --git a/test/parallel/test-permission-config-file.mjs b/test/parallel/test-permission-config-file.mjs
index 6b01f3741f3..c27b1038e71 100644
--- a/test/parallel/test-permission-config-file.mjs
+++ b/test/parallel/test-permission-config-file.mjs
@@ -64,6 +64,40 @@ describe('Permission model config file support', () => {
     }
   });

+  it('should propagate config file permissions to child processes', {
+    skip: process.config.variables.node_without_node_options && 'missing NODE_OPTIONS support',
+  }, async () => {
+    const childTestPath = fixtures.path('permission/child-process-inherit-test.js');
+    const expected = JSON.stringify(['object', true, false, true, true]);
+
+    // --permission set in the config file
+    {
+      const configPath = fixtures.path('permission/config-child-inherit.json');
+      const result = await spawnPromisified(process.execPath, [
+        `--config-file=${configPath}`,
+        childTestPath,
+      ]);
+      assert.strictEqual(result.code, 0, result.stderr);
+      const child = JSON.parse(result.stdout);
+      assert.strictEqual(child.status, 0, child.stderr);
+      assert.strictEqual(child.stdout, expected);
+    }
+
+    // --permission set in the command line
+    {
+      const configPath = fixtures.path('permission/config-child-inherit-allow-only.json');
+      const result = await spawnPromisified(process.execPath, [
+        '--permission',
+        `--config-file=${configPath}`,
+        childTestPath,
+      ]);
+      assert.strictEqual(result.code, 0, result.stderr);
+      const child = JSON.parse(result.stdout);
+      assert.strictEqual(child.status, 0, child.stderr);
+      assert.strictEqual(child.stdout, expected);
+    }
+  });
+
   it('should load network and inspector permissions from config file', async () => {
     const configPath = fixtures.path('permission/config-net-inspector.json');
     const readOnlyConfigPath = fixtures.path('permission/config-fs-read-only.json');