From db0efdf053cbe6b32462ccd1f3851e5df16c3771 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Fri, 16 Nov 2018 12:16:37 +0100
Subject: [PATCH] feat: trustLevel

New config option replaces existing option exposeEnv. Set trustLevel=high in the bot config if you trust the contents of the repositories you are renovating. Doing so results in env being exposed, access to localhost, etc.

Closes #2739
---
 lib/config/cli.js                                |  7 ++++++-
 lib/config/definitions.js                        |  8 ++++----
 lib/config/migration.js                          |  8 ++++++++
 lib/datasource/npm.js                            |  8 ++++----
 lib/manager/composer/artifacts.js                |  2 +-
 lib/manager/gomod/artifacts.js                   |  2 +-
 lib/manager/npm/extract/index.js                 |  2 +-
 lib/manager/npm/post-update/index.js             |  2 +-
 lib/workers/global/index.js                      |  2 +-
 lib/workers/repository/init/apis.js              |  2 +-
 lib/workers/repository/init/config.js            |  4 ++--
 test/config/__snapshots__/migration.spec.js.snap |  2 ++
 test/config/migration.spec.js                    |  2 ++
 test/datasource/npm.spec.js                      |  4 ++--
 website/docs/self-hosted-configuration.md        | 11 +++++++++--
 15 files changed, 45 insertions(+), 21 deletions(-)

diff --git a/lib/config/cli.js b/lib/config/cli.js
index 086d63fa6a..454c407f86 100644
--- a/lib/config/cli.js
+++ b/lib/config/cli.js
@@ -17,7 +17,12 @@ function getCliName(option) {
 
 function getConfig(input) {
   // massage migrated configuration keys
-  const argv = input.map(a => a.replace('--endpoints=', '--host-rules='));
+  const argv = input.map(a =>
+    a
+      .replace('--endpoints=', '--host-rules=')
+      .replace('--expose-env=true', '--trust-level=high')
+      .replace('--expose-env', '--trust-level=high')
+  );
   const options = configDefinitions.getOptions();
 
   const config = {};
diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index 94eeeeb567..75c6926a3b 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -188,12 +188,12 @@ const options = [
     default: null,
   },
   {
-    name: 'exposeEnv',
+    name: 'trustLevel',
     description:
-      'Enable this to expose bot process.env to repositories for npmrc substitution and package installation',
+      'Set this to "high" if the bot should trust the repository owners/contents',
     stage: 'global',
-    type: 'boolean',
-    default: false,
+    type: 'string',
+    default: 'low',
   },
   {
     name: 'platform',
diff --git a/lib/config/migration.js b/lib/config/migration.js
index e58dcefa19..b7592e27c9 100644
--- a/lib/config/migration.js
+++ b/lib/config/migration.js
@@ -109,6 +109,14 @@ function migrateConfig(config) {
         } else if (val === false) {
           migratedConfig.rangeStrategy = 'replace';
         }
+      } else if (key === 'exposeEnv') {
+        isMigrated = true;
+        delete migratedConfig.exposeEnv;
+        if (val === true) {
+          migratedConfig.trustLevel = 'high';
+        } else if (val === false) {
+          migratedConfig.trustLevel = 'low';
+        }
       } else if (key === 'upgradeInRange') {
         isMigrated = true;
         delete migratedConfig.upgradeInRange;
diff --git a/lib/datasource/npm.js b/lib/datasource/npm.js
index 4979f7940b..0673fcecc0 100644
--- a/lib/datasource/npm.js
+++ b/lib/datasource/npm.js
@@ -56,7 +56,7 @@ function maskToken(token) {
   )}${token.slice(-2)}`;
 }
 
-function setNpmrc(input, exposeEnv = false) {
+function setNpmrc(input, trustLevel = 'low') {
   if (input) {
     if (input === npmrcRaw) {
       return;
@@ -73,7 +73,7 @@ function setNpmrc(input, exposeEnv = false) {
         delete npmrc[key];
       }
     }
-    if (!exposeEnv) {
+    if (trustLevel !== 'high') {
       return;
     }
     for (const key in npmrc) {
@@ -112,8 +112,8 @@ async function getPkgReleases(input, config) {
     return getDependency(depName, retries);
   }
   if (config) {
-    const exposeEnv = config.global ? config.global.exposeEnv : false;
-    setNpmrc(config.npmrc, exposeEnv);
+    const trustLevel = config.global ? config.global.trustLevel : 'low';
+    setNpmrc(config.npmrc, trustLevel);
   }
   const purl = input;
   const res = await getDependency(purl.fullname, retries);
diff --git a/lib/manager/composer/artifacts.js b/lib/manager/composer/artifacts.js
index 2936a58006..9426f015d7 100644
--- a/lib/manager/composer/artifacts.js
+++ b/lib/manager/composer/artifacts.js
@@ -99,7 +99,7 @@ async function getArtifacts(
       await fs.outputFile(localAuthFileName, JSON.stringify(authJson));
     }
     const env =
-      config.global && config.global.exposeEnv
+      config.global && config.global.trustLevel === 'high'
         ? process.env
         : {
             HOME: process.env.HOME,
diff --git a/lib/manager/gomod/artifacts.js b/lib/manager/gomod/artifacts.js
index 74c6948e63..879d5e5de2 100644
--- a/lib/manager/gomod/artifacts.js
+++ b/lib/manager/gomod/artifacts.js
@@ -42,7 +42,7 @@ async function getArtifacts(
       await fs.outputFile(localGoSumFileName, existingGoSumContent);
     }
     const env =
-      config.global && config.global.exposeEnv
+      config.global && config.global.trustLevel === 'high'
         ? process.env
         : {
             HOME: process.env.HOME,
diff --git a/lib/manager/npm/extract/index.js b/lib/manager/npm/extract/index.js
index 847d921b49..6ce36861f8 100644
--- a/lib/manager/npm/extract/index.js
+++ b/lib/manager/npm/extract/index.js
@@ -100,7 +100,7 @@ async function extractPackageFile(content, fileName, config) {
     if (npmrc) {
       if (
         npmrc.includes('=${') &&
-        !(config.global && config.global.exposeEnv)
+        !(config.global && config.global.trustLevel === 'high')
       ) {
         logger.info('Discarding .npmrc file with variables');
         npmrc = undefined;
diff --git a/lib/manager/npm/post-update/index.js b/lib/manager/npm/post-update/index.js
index 0afee7daf1..6d877f018f 100644
--- a/lib/manager/npm/post-update/index.js
+++ b/lib/manager/npm/post-update/index.js
@@ -345,7 +345,7 @@ async function getAdditionalFiles(config, packageFiles) {
   await fs.ensureDir(process.env.YARN_CACHE_FOLDER);
 
   const env =
-    config.global && config.global.exposeEnv
+    config.global && config.global.trustLevel === 'high'
       ? process.env
       : {
           HOME: process.env.HOME,
diff --git a/lib/workers/global/index.js b/lib/workers/global/index.js
index 17d814ba5f..82821326ad 100644
--- a/lib/workers/global/index.js
+++ b/lib/workers/global/index.js
@@ -39,7 +39,7 @@ async function start() {
         'Available now for GitLab: [Renovate Pro](https://renovatebot.com/pro) with real-time webhook handling and priority job queue.';
     }
     // Move global variables that we need to use later
-    const importGlobals = ['exposeEnv', 'prBanner', 'prFooter'];
+    const importGlobals = ['trustLevel', 'prBanner', 'prFooter'];
     config.global = {};
     importGlobals.forEach(key => {
       config.global[key] = config[key];
diff --git a/lib/workers/repository/init/apis.js b/lib/workers/repository/init/apis.js
index d40f1c2bd4..ebef7106c5 100644
--- a/lib/workers/repository/init/apis.js
+++ b/lib/workers/repository/init/apis.js
@@ -22,7 +22,7 @@ async function initApis(input) {
   npmApi.resetMemCache();
   npmApi.setNpmrc(
     config.npmrc,
-    config.global ? config.global.exposeEnv : false
+    config.global ? config.global.trustLevel : 'low'
   );
   delete config.gitPrivateKey;
   return config;
diff --git a/lib/workers/repository/init/config.js b/lib/workers/repository/init/config.js
index 7e1bfec15e..8bc2b5d238 100644
--- a/lib/workers/repository/init/config.js
+++ b/lib/workers/repository/init/config.js
@@ -129,7 +129,7 @@ async function mergeRenovateConfig(config) {
     logger.debug('Found npmrc in decrypted config - setting');
     npmApi.setNpmrc(
       decryptedConfig.npmrc,
-      config.global ? config.global.exposeEnv : false
+      config.global ? config.global.trustLevel : 'low'
     );
   }
   // Decrypt after resolving in case the preset contains npm authentication instead
@@ -146,7 +146,7 @@ async function mergeRenovateConfig(config) {
     );
     npmApi.setNpmrc(
       resolvedConfig.npmrc,
-      config.global ? config.global.exposeEnv : false
+      config.global ? config.global.trustLevel : 'low'
     );
     resolvedConfig.ignoreNpmrcFile = true;
   }
diff --git a/test/config/__snapshots__/migration.spec.js.snap b/test/config/__snapshots__/migration.spec.js.snap
index d71effdf69..85445846bb 100644
--- a/test/config/__snapshots__/migration.spec.js.snap
+++ b/test/config/__snapshots__/migration.spec.js.snap
@@ -32,6 +32,7 @@ Object {
     "automerge": true,
     "gitFs": "https",
     "schedule": "before 5am",
+    "trustLevel": "low",
   },
   "major": Object {
     "automerge": false,
@@ -130,6 +131,7 @@ Object {
   "travis": Object {
     "enabled": true,
   },
+  "trustLevel": "high",
 }
 `;
 
diff --git a/test/config/migration.spec.js b/test/config/migration.spec.js
index d3e695e18d..0f9b16cb84 100644
--- a/test/config/migration.spec.js
+++ b/test/config/migration.spec.js
@@ -57,7 +57,9 @@ describe('config/migration', () => {
             enabled: false,
           },
         ],
+        exposeEnv: true,
         lockFileMaintenance: {
+          exposeEnv: false,
           gitFs: true,
           automerge: 'any',
           schedule: 'before 5am every day',
diff --git a/test/datasource/npm.spec.js b/test/datasource/npm.spec.js
index 8bf3afdcd8..715c4702e6 100644
--- a/test/datasource/npm.spec.js
+++ b/test/datasource/npm.spec.js
@@ -383,7 +383,7 @@ describe('api/npm', () => {
       .reply(200, npmResponse);
     process.env.REGISTRY = 'https://registry.from-env.com';
     // eslint-disable-next-line no-template-curly-in-string
-    npm.setNpmrc('registry=${REGISTRY}', true);
+    npm.setNpmrc('registry=${REGISTRY}', 'high');
     const res = await npm.getPkgReleases('foobar');
     expect(res).toMatchSnapshot();
   });
@@ -391,7 +391,7 @@ describe('api/npm', () => {
     let e;
     try {
       // eslint-disable-next-line no-template-curly-in-string
-      npm.setNpmrc('registry=${REGISTRY_MISSING}', true);
+      npm.setNpmrc('registry=${REGISTRY_MISSING}', 'high');
     } catch (err) {
       e = err;
     }
diff --git a/website/docs/self-hosted-configuration.md b/website/docs/self-hosted-configuration.md
index ef536422bc..54ff2f2fd4 100644
--- a/website/docs/self-hosted-configuration.md
+++ b/website/docs/self-hosted-configuration.md
@@ -19,8 +19,6 @@ Set this to 'global' if you wish Renovate to use globally-installed binaries (`n
 
 ## endpoint
 
-## exposeEnv
-
 ## force
 
 This object is used as a "force override" when you need to make sure certain configuration overrides whatever is configured in the repository. For example, forcing a null (no) schedule to make sure Renovate raises PRs on a run even if the repository itself or its preset defines a schedule that's currently in active.
@@ -80,4 +78,13 @@ By default, Renovate will use the most efficient approach to updating package fi
 
 ## token
 
+## trustLevel
+
+Setting trustLevel to "high" can make sense in may self-hosted cases where the bot operator trusts the content in each repository.
+
+Setting trustLevel=high means:
+
+- Child processes are run with full access to `env`
+- `.npmrc` files can have environment variable substitution performed
+
 ## username
-- 
GitLab