CVE-2020-15095 (published 2020-07-07T19:15Z)

Versions of the npm CLI prior to 6.14.6 are vulnerable to an information exposure vulnerability through log files. The CLI supports URLs like "<protocol>://[<user>[:<password>]@]<hostname>[:<port>][:][/]<path>". The password value is not redacted and is printed to stdout and also to any generated log files.

Severity: LOW
CVSS v2 base score: 1.9 (AV:L/AC:M/Au:N/C:P/I:N/A:N)
CVSS v3 base score: 4.4, MEDIUM (CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-532
cwe_name: Insertion of Sensitive Information into Log File
description: Information written to log files can be of a sensitive nature and give valuable guidance to an attacker or expose sensitive user information.
extended_description: 
            While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.
            Different log files may be produced and stored for:
            
               Server log files (e.g. server.log).  This can give information on whatever application left the file. Usually this can give full path names and system information, and sometimes usernames and passwords.
               log files that are used for debugging
            
         
         

Fix information:
repo_url: https://github.com/npm/cli
commit_details: repo_url: https://github.com/npm/cli
author: claudiahdz
author_date: 2020-06-25 19:34:47-05:00
msg: chore: remove auth info from logs
repository: repo_url: https://github.com/npm/cli
repo_name: npm/cli
description: the package manager for JavaScript
repo_language: JavaScript
owner: npm
file_changes: filename: npm-cli.js
change_type: ModificationType.MODIFY
diff: @@ -28,6 +28,7 @@
   var npm = require('../lib/npm.js')
   var npmconf = require('../lib/config/core.js')
   var errorHandler = require('../lib/utils/error-handler.js')
+  var replaceInfo = require('../lib/utils/replace-info.js')
 
   var configDefs = npmconf.defs
   var shorthands = configDefs.shorthands
@@ -40,7 +41,8 @@
     process.argv.splice(1, 1, 'npm', '-g')
   }
 
-  log.verbose('cli', process.argv)
+  var args = replaceInfo(process.argv)
+  log.verbose('cli', args)
 
   var conf = nopt(types, shorthands)
   npm.argv = conf.argv.remain

programming_language: CoffeeScript
filename: fetch-package-metadata.js
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,7 @@
 const deprCheck = require('./utils/depr-check')
 const path = require('path')
 const log = require('npmlog')
+const pacote = require('pacote')
 const readPackageTree = require('read-package-tree')
 const rimraf = require('rimraf')
 const validate = require('aproba')
@@ -11,15 +12,17 @@ const npm = require('./npm')
 let npmConfig
 const npmlog = require('npmlog')
 const limit = require('call-limit')
-const tempFilename = require('./utils/temp-filename')
-const pacote = require('pacote')
+const tempFilename = require('./utils/temp-filename.js')
+const replaceInfo = require('./utils/replace-info.js')
 const isWindows = require('./utils/is-windows.js')
 
 function andLogAndFinish (spec, tracker, done) {
   validate('SOF|SZF|OOF|OZF', [spec, tracker, done])
   return (er, pkg) => {
     if (er) {
-      log.silly('fetchPackageMetaData', 'error for ' + String(spec), er.message)
+      er.message = replaceInfo(er.message)
+      var spc = replaceInfo(String(spec))
+      log.silly('fetchPackageMetaData', 'error for ' + spc, er.message)
       if (tracker) tracker.finish()
     }
     return done(er, pkg)

programming_language: CoffeeScript
filename: error-handler.js
change_type: ModificationType.MODIFY
diff: @@ -12,6 +12,7 @@ var exitCode = 0
 var rollbacks = npm.rollbacks
 var chain = require('slide').chain
 var errorMessage = require('./error-message.js')
+var replaceInfo = require('./replace-info.js')
 var stopMetrics = require('./metrics.js').stop
 
 const cacheFile = require('./cache-file.js')
@@ -175,14 +176,16 @@ function errorHandler (er) {
   ].forEach(function (k) {
     var v = er[k]
     if (!v) return
+    v = replaceInfo(v)
     log.verbose(k, v)
   })
 
   log.verbose('cwd', process.cwd())
 
   var os = require('os')
+  var args = replaceInfo(process.argv)
   log.verbose('', os.type() + ' ' + os.release())
-  log.verbose('argv', process.argv.map(JSON.stringify).join(' '))
+  log.verbose('argv', args.map(JSON.stringify).join(' '))
   log.verbose('node', process.version)
   log.verbose('npm ', 'v' + npm.version)
 

programming_language: JavaScript
filename: error-message.js
change_type: ModificationType.MODIFY
diff: @@ -3,12 +3,17 @@ var npm = require('../npm.js')
 var util = require('util')
 var nameValidator = require('validate-npm-package-name')
 var npmlog = require('npmlog')
+var replaceInfo = require('./replace-info.js')
 
 module.exports = errorMessage
 
 function errorMessage (er) {
   var short = []
   var detail = []
+
+  er.message = replaceInfo(er.message)
+  er.stack = replaceInfo(er.stack)
+
   switch (er.code) {
     case 'ENOAUDIT':
       short.push(['audit', er.message])

programming_language: CoffeeScript
filename: replace-info.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,22 @@
+const URL = require('url')
+
+// replaces auth info in an array
+//  of arguments or in a strings
+function replaceInfo (arg) {
+  const isArray = Array.isArray(arg)
+  const isString = typeof arg === 'string'
+
+  if (!isArray && !isString) return arg
+
+  const args = isString ? arg.split(' ') : arg
+  const info = args.map(arg => {
+    try {
+      const url = new URL(arg)
+      return url.password === '' ? arg : arg.replace(url.password, '***')
+    } catch (e) { return arg }
+  })
+
+  return isString ? info.join(' ') : info
+}
+
+module.exports = replaceInfo

programming_language: CoffeeScript