current commit
260926.27 · src,lib: add --allow-env permission
'use strict';

const common = require('../common');
const { isMainThread } = require('worker_threads');

if (!isMainThread) {
  common.skip('This test only works on a main thread');
}

const assert = require('assert');
const { spawnSyncAndAssert } = require('../common/child_process');

const env = {
  ...process.env,
  PERMISSION_ENV_SECRET: 'secret',
};

// Reading a variable that was removed at startup publishes a denial every
// time, and warns once.
spawnSyncAndAssert(
  process.execPath,
  [
    '--permission', '-e',
    `
    const assert = require('assert');
    const dc = require('diagnostics_channel');
    const events = [];
    dc.subscribe('node:permission-model:env', (message) => {
      events.push([message.permission, message.resource]);
    });

    assert.strictEqual(process.env.PERMISSION_ENV_SECRET, undefined);
    assert.strictEqual(process.env.PERMISSION_ENV_SECRET, undefined);
    assert.strictEqual('PERMISSION_ENV_SECRET' in process.env, false);
    // Variables that were never set are not reported.
    assert.strictEqual(process.env.PERMISSION_ENV_NEVER_SET, undefined);

    assert.deepStrictEqual(events, [
      ['Env', 'PERMISSION_ENV_SECRET'],
      ['Env', 'PERMISSION_ENV_SECRET'],
      ['Env', 'PERMISSION_ENV_SECRET'],
    ]);
    `,
  ],
  { env },
  {
    stderr(output) {
      const warnings = output.match(
        /Warning: The permission model removed the environment variable "PERMISSION_ENV_SECRET" at startup\. Use --allow-env to manage permissions\./g);
      assert.strictEqual(warnings?.length, 1, output);
      assert.doesNotMatch(output, /PERMISSION_ENV_NEVER_SET/);
    },
  });

// --no-warnings silences the warning.
spawnSyncAndAssert(
  process.execPath,
  ['--permission', '--no-warnings', '-e', 'process.env.PERMISSION_ENV_SECRET'],
  { env },
  { stderr: '' });

// In audit mode nothing is removed. Accesses to variables that --allow-env
// does not grant access to are published instead, without a warning.
spawnSyncAndAssert(
  process.execPath,
  [
    '--permission-audit', '--allow-env=PERMISSION_ENV_ALLOWED', '-e',
    `
    const assert = require('assert');
    const dc = require('diagnostics_channel');
    const events = [];
    dc.subscribe('node:permission-model:env', (message) => {
      events.push(message.resource);
    });

    assert.strictEqual(process.env.PERMISSION_ENV_SECRET, 'secret');
    assert.strictEqual('PERMISSION_ENV_SECRET' in process.env, true);
    assert.strictEqual(process.env.PERMISSION_ENV_ALLOWED, 'allowed');
    process.env.TZ;
    // Variables created at runtime would not have been removed either.
    process.env.PERMISSION_ENV_RUNTIME = 'runtime';
    assert.strictEqual(process.env.PERMISSION_ENV_RUNTIME, 'runtime');

    assert.deepStrictEqual(events, [
      'PERMISSION_ENV_SECRET',
      'PERMISSION_ENV_SECRET',
    ]);
    `,
  ],
  { env: { ...env, PERMISSION_ENV_ALLOWED: 'allowed' } },
  {
    stderr(output) {
      assert.doesNotMatch(output, /removed the environment variable/);
    },
  });

// --use-env-proxy stops applying the proxy variables the permission model
// removes, which a single warning points out. Reading them afterwards does
// not warn again.
{
  const proxyEnv = { ...process.env };
  for (const name of ['HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY']) {
    delete proxyEnv[name];
    delete proxyEnv[name.toLowerCase()];
  }
  delete proxyEnv.NODE_USE_ENV_PROXY;
  proxyEnv.HTTP_PROXY = 'http://proxy.invalid:8080';
  proxyEnv.NO_PROXY = 'localhost';

  spawnSyncAndAssert(
    process.execPath,
    [
      '--permission', '--use-env-proxy', '-e',
      'process.env.HTTP_PROXY; process.env.NO_PROXY;',
    ],
    { env: proxyEnv },
    {
      stderr(output) {
        const warnings = output.match(
          /Warning: --use-env-proxy is enabled, but the permission model removed HTTP_PROXY, NO_PROXY from the environment at startup/g);
        assert.strictEqual(warnings?.length, 1, output);
        assert.doesNotMatch(output, /removed the environment variable/);
      },
    });

  // NODE_USE_ENV_PROXY enables it too.
  spawnSyncAndAssert(
    process.execPath,
    ['--permission', '-e', '0'],
    { env: { ...proxyEnv, NODE_USE_ENV_PROXY: '1' } },
    { stderr: /--use-env-proxy is enabled, but the permission model removed HTTP_PROXY, NO_PROXY/ });

  // Only the variables that were removed are named.
  spawnSyncAndAssert(
    process.execPath,
    ['--permission', '--use-env-proxy', '--allow-env=HTTP_PROXY', '-e', '0'],
    { env: proxyEnv },
    {
      stderr(output) {
        assert.match(output, /the permission model removed NO_PROXY from/);
      },
    });

  // No warning once they are allowed.
  spawnSyncAndAssert(
    process.execPath,
    [
      '--permission', '--use-env-proxy', '--allow-env=HTTP_PROXY,NO_PROXY',
      '-e', '0',
    ],
    { env: proxyEnv },
    { stderr: '' });

  // Nor without --use-env-proxy, which does not read them.
  spawnSyncAndAssert(
    process.execPath,
    ['--permission', '-e', '0'],
    { env: proxyEnv },
    { stderr: '' });

  // Nor in audit mode, which removes nothing.
  spawnSyncAndAssert(
    process.execPath,
    ['--permission-audit', '--use-env-proxy', '-e', '0'],
    { env: proxyEnv },
    { stderr: '' });
}