CVE-2020-26226: Improper Encoding or Escaping of Output (CWE-116)
value: In the npm package semantic-release before version 17.2.3, secrets that would normally be masked by `semantic-release` can be accidentally disclosed if they contain characters that become encoded when included in a URL. Secrets that do not contain characters that become encoded when included in a URL are already masked properly. The issue is fixed in version 17.2.3.
CWE description: The product prepares a structured message for communication with another component, but encoding or escaping of the data is either missing or done incorrectly. As a result, the intended structure of the message is not preserved.
CVSS v3 base score: 8.1
Published: 2020-11-18T22:15Z
Repository: https://github.com/semantic-release/semantic-release
Language: JavaScript
Files changed: lib/hide-sensitive.js, test/hide-sensitive.test.js
Commit date: 2020-11-16 10:36:40 -0600
Commit message: fix: mask secrets when characters get uri encoded

Diff: diff --git a/lib/hide-sensitive.js b/lib/hide-sensitive.js
index 6098496..d612068 100644
--- a/lib/hide-sensitive.js
+++ b/lib/hide-sensitive.js
@@ -9,9 +9,14 @@ module.exports = (env) => {
     }
 
     return /token|password|credential|secret|private/i.test(envVar) && size(env[envVar].trim()) >= SECRET_MIN_SIZE;
   });
 
-  const regexp = new RegExp(toReplace.map((envVar) => escapeRegExp(env[envVar])).join('|'), 'g');
+  const regexp = new RegExp(
+    toReplace
+      .map((envVar) => `${escapeRegExp(env[envVar])}|${encodeURI(escapeRegExp(env[envVar]))}`)
+      .join('|'),
+    'g'
+  );
   return (output) =>
     output && isString(output) && toReplace.length > 0 ? output.toString().replace(regexp, SECRET_REPLACEMENT) : output;
 };
diff --git a/test/hide-sensitive.test.js b/test/hide-sensitive.test.js
index 2b319c4..4987d31 100644
--- a/test/hide-sensitive.test.js
+++ b/test/hide-sensitive.test.js
@@ -22,10 +22,18 @@ test('Replace multiple occurences of sensitive environment variable values', (t)
 test('Replace sensitive environment variable matching specific regex for "private"', (t) => {
   const env = {privateKey: 'secret', GOPRIVATE: 'host.com'};
   t.is(hideSensitive(env)(`https://host.com?token=${env.privateKey}`), `https://host.com?token=${SECRET_REPLACEMENT}`);
 });
 
+test('Replace url-encoded environment variable', (t) => {
+  const env = {privateKey: 'secret '};
+  t.is(
+    hideSensitive(env)(`https://host.com?token=${encodeURI(env.privateKey)}`),
+    `https://host.com?token=${SECRET_REPLACEMENT}`
+  );
+});
+
 test('Escape regexp special characters', (t) => {
   const env = {SOME_CREDENTIALS: 'p$^{.+}\\w[a-z]o.*rd'};
   t.is(
     hideSensitive(env)(`https://user:${env.SOME_CREDENTIALS}@host.com`),
     `https://user:${SECRET_REPLACEMENT}@host.com`

Vulnerable code:   const regexp = new RegExp(toReplace.map((envVar) => escapeRegExp(env[envVar])).join('|'), 'g');
Fixed code:   const regexp = new RegExp(
    toReplace
      .map((envVar) => `${escapeRegExp(env[envVar])}|${encodeURI(escapeRegExp(env[envVar]))}`)
      .join('|'),
    'g'
  );
test('Replace url-encoded environment variable', (t) => {
  const env = {privateKey: 'secret '};
  t.is(
    hideSensitive(env)(`https://host.com?token=${encodeURI(env.privateKey)}`),
    `https://host.com?token=${SECRET_REPLACEMENT}`
  );
});