Skip to content

Commit

Permalink
fix(config): use redact on config output (#7521)
Browse files Browse the repository at this point in the history
Fixes #3867
  • Loading branch information
lukekarrys committed May 14, 2024
1 parent aa5d7b1 commit badeac2
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 21 deletions.
50 changes: 29 additions & 21 deletions lib/commands/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const pkgJson = require('@npmcli/package-json')
const { defaults, definitions } = require('@npmcli/config/lib/definitions')
const { log, output } = require('proc-log')
const BaseCommand = require('../base-cmd.js')
const { redact } = require('@npmcli/redact')

// These are the configs that we can nerf-dart. Not all of them currently even
// *have* config definitions so we have to explicitly validate them here.
Expand Down Expand Up @@ -53,29 +54,35 @@ const keyValues = args => {
return kv
}

const publicVar = k => {
const isProtected = (k) => {
// _password
if (k.startsWith('_')) {
return false
return true
}
if (protected.includes(k)) {
return false
return true
}
// //localhost:8080/:_password
if (k.startsWith('//')) {
if (k.includes(':_')) {
return false
return true
}
// //registry:_authToken or //registry:authToken
for (const p of protected) {
if (k.endsWith(`:${p}`) || k.endsWith(`:_${p}`)) {
return false
return true
}
}
}
return true
return false
}

// Private fields are either protected or they can redacted info
const isPrivate = (k, v) => isProtected(k) || redact(v) !== v

const displayVar = (k, v) =>
`${k} = ${isProtected(k, v) ? '(protected)' : JSON.stringify(redact(v))}`

class Config extends BaseCommand {
static description = 'Manage the npm configuration files'
static name = 'config'
Expand Down Expand Up @@ -206,12 +213,13 @@ class Config extends BaseCommand {

const out = []
for (const key of keys) {
if (!publicVar(key)) {
const val = this.npm.config.get(key)
if (isPrivate(key, val)) {
throw new Error(`The ${key} option is protected, and can not be retrieved in this way`)
}

const pref = keys.length > 1 ? `${key}=` : ''
out.push(pref + this.npm.config.get(key))
out.push(pref + val)
}
output.standard(out.join('\n'))
}
Expand Down Expand Up @@ -338,18 +346,17 @@ ${defData}
continue
}

const keys = Object.keys(data).sort(localeCompare)
if (!keys.length) {
const entries = Object.entries(data).sort(([a], [b]) => localeCompare(a, b))
if (!entries.length) {
continue
}

msg.push(`; "${where}" config from ${source}`, '')
for (const k of keys) {
const v = publicVar(k) ? JSON.stringify(data[k]) : '(protected)'
for (const [k, v] of entries) {
const display = displayVar(k, v)
const src = this.npm.config.find(k)
const overridden = src !== where
msg.push((overridden ? '; ' : '') +
`${k} = ${v}${overridden ? ` ; overridden by ${src}` : ''}`)
msg.push(src === where ? display : `; ${display} ; overridden by ${src}`)
msg.push()
}
msg.push('')
}
Expand All @@ -374,10 +381,10 @@ ${defData}
const pkgPath = resolve(this.npm.prefix, 'package.json')
msg.push(`; "publishConfig" from ${pkgPath}`)
msg.push('; This set of config values will be used at publish-time.', '')
const pkgKeys = Object.keys(content.publishConfig).sort(localeCompare)
for (const k of pkgKeys) {
const v = publicVar(k) ? JSON.stringify(content.publishConfig[k]) : '(protected)'
msg.push(`${k} = ${v}`)
const entries = Object.entries(content.publishConfig)
.sort(([a], [b]) => localeCompare(a, b))
for (const [k, value] of entries) {
msg.push(displayVar(k, value))
}
msg.push('')
}
Expand All @@ -389,11 +396,12 @@ ${defData}
async listJson () {
const publicConf = {}
for (const key in this.npm.config.list[0]) {
if (!publicVar(key)) {
const value = this.npm.config.get(key)
if (isPrivate(key, value)) {
continue
}

publicConf[key] = this.npm.config.get(key)
publicConf[key] = value
}
output.standard(JSON.stringify(publicConf, null, 2))
}
Expand Down
19 changes: 19 additions & 0 deletions test/lib/commands/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,25 @@ t.test('config get private key', async t => {
)
})

t.test('config redacted values', async t => {
const { npm, joinedOutput, clearOutput } = await loadMockNpm(t)

await npm.exec('config', ['set', 'proxy', 'https://proxy.npmjs.org/'])
await npm.exec('config', ['get', 'proxy'])

t.equal(joinedOutput(), 'https://proxy.npmjs.org/')
clearOutput()

await npm.exec('config', ['set', 'proxy', 'https://u:[email protected]/'])

await t.rejects(npm.exec('config', ['get', 'proxy']), /proxy option is protected/)

await npm.exec('config', ['ls'])

t.match(joinedOutput(), 'proxy = "https://u:***@proxy.npmjs.org/"')
clearOutput()
})

t.test('config edit', async t => {
const EDITOR = 'vim'
const editor = spawk.spawn(EDITOR).exit(0)
Expand Down

0 comments on commit badeac2

Please sign in to comment.