VYPR
Moderate severityNVD Advisory· Published Jul 7, 2020· Updated Aug 4, 2024

Sensitive information exposure through logs in npm cli

CVE-2020-15095

Description

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 "://[[:]@][:][:][/]". The password value is not redacted and is printed to stdout and also to any generated log files.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

npm CLI before 6.14.6 logs authentication passwords in plaintext to stdout and log files, exposing credentials.

Vulnerability

Description The npm CLI prior to version 6.14.6 supports URLs with embedded credentials in the format ://[[:]@]. When such URLs are processed, the password value is printed to stdout and written to log files without redaction, leading to an information exposure vulnerability [1][4]. This occurs because the CLI did not sanitize authentication information before logging.

Exploitation

Conditions An attacker who gains access to log files—for example, from CI/CD pipelines, shared development environments, or system logs—can retrieve plaintext passwords. No authentication or special privileges are required beyond the ability to read the logs [2]. The vulnerability is present in all npm CLI versions before 6.14.6.

Impact

Successful exploitation exposes authentication credentials for npm registries or any service accessed via such URLs. This could allow an attacker to impersonate the victim, publish malicious packages, or access private repositories [4]. The severity is elevated because logs are often retained and shared across teams.

Mitigation

The issue is fixed in npm CLI version 6.14.6, released on 2020-07-07 [1]. The fix introduces a replace-info utility that redacts sensitive information from log output [3]. Users should update to the latest version and review existing logs for exposed credentials. No workaround is available for older versions.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
npmnpm
< 6.14.66.14.6

Affected products

131

Patches

1
a9857b8f6869

chore: remove auth info from logs

https://github.com/npm/cliclaudiahdzJun 26, 2020via ghsa
5 files changed · +40 5
  • bin/npm-cli.js+3 1 modified
    @@ -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
    
  • lib/fetch-package-metadata.js+6 3 modified
    @@ -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)
    
  • lib/utils/error-handler.js+4 1 modified
    @@ -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)
     
    
  • lib/utils/error-message.js+5 0 modified
    @@ -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])
    
  • lib/utils/replace-info.js+22 0 added
    @@ -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
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

11

News mentions

0

No linked articles in our index yet.