Skip to content

Commit badeac2

Browse files
authored
fix(config): use redact on config output (#7521)
Fixes #3867
1 parent aa5d7b1 commit badeac2

File tree

2 files changed

+48
-21
lines changed

2 files changed

+48
-21
lines changed

lib/commands/config.js

+29-21
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const pkgJson = require('@npmcli/package-json')
77
const { defaults, definitions } = require('@npmcli/config/lib/definitions')
88
const { log, output } = require('proc-log')
99
const BaseCommand = require('../base-cmd.js')
10+
const { redact } = require('@npmcli/redact')
1011

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

56-
const publicVar = k => {
57+
const isProtected = (k) => {
5758
// _password
5859
if (k.startsWith('_')) {
59-
return false
60+
return true
6061
}
6162
if (protected.includes(k)) {
62-
return false
63+
return true
6364
}
6465
// //localhost:8080/:_password
6566
if (k.startsWith('//')) {
6667
if (k.includes(':_')) {
67-
return false
68+
return true
6869
}
6970
// //registry:_authToken or //registry:authToken
7071
for (const p of protected) {
7172
if (k.endsWith(`:${p}`) || k.endsWith(`:_${p}`)) {
72-
return false
73+
return true
7374
}
7475
}
7576
}
76-
return true
77+
return false
7778
}
7879

80+
// Private fields are either protected or they can redacted info
81+
const isPrivate = (k, v) => isProtected(k) || redact(v) !== v
82+
83+
const displayVar = (k, v) =>
84+
`${k} = ${isProtected(k, v) ? '(protected)' : JSON.stringify(redact(v))}`
85+
7986
class Config extends BaseCommand {
8087
static description = 'Manage the npm configuration files'
8188
static name = 'config'
@@ -206,12 +213,13 @@ class Config extends BaseCommand {
206213

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

213221
const pref = keys.length > 1 ? `${key}=` : ''
214-
out.push(pref + this.npm.config.get(key))
222+
out.push(pref + val)
215223
}
216224
output.standard(out.join('\n'))
217225
}
@@ -338,18 +346,17 @@ ${defData}
338346
continue
339347
}
340348

341-
const keys = Object.keys(data).sort(localeCompare)
342-
if (!keys.length) {
349+
const entries = Object.entries(data).sort(([a], [b]) => localeCompare(a, b))
350+
if (!entries.length) {
343351
continue
344352
}
345353

346354
msg.push(`; "${where}" config from ${source}`, '')
347-
for (const k of keys) {
348-
const v = publicVar(k) ? JSON.stringify(data[k]) : '(protected)'
355+
for (const [k, v] of entries) {
356+
const display = displayVar(k, v)
349357
const src = this.npm.config.find(k)
350-
const overridden = src !== where
351-
msg.push((overridden ? '; ' : '') +
352-
`${k} = ${v}${overridden ? ` ; overridden by ${src}` : ''}`)
358+
msg.push(src === where ? display : `; ${display} ; overridden by ${src}`)
359+
msg.push()
353360
}
354361
msg.push('')
355362
}
@@ -374,10 +381,10 @@ ${defData}
374381
const pkgPath = resolve(this.npm.prefix, 'package.json')
375382
msg.push(`; "publishConfig" from ${pkgPath}`)
376383
msg.push('; This set of config values will be used at publish-time.', '')
377-
const pkgKeys = Object.keys(content.publishConfig).sort(localeCompare)
378-
for (const k of pkgKeys) {
379-
const v = publicVar(k) ? JSON.stringify(content.publishConfig[k]) : '(protected)'
380-
msg.push(`${k} = ${v}`)
384+
const entries = Object.entries(content.publishConfig)
385+
.sort(([a], [b]) => localeCompare(a, b))
386+
for (const [k, value] of entries) {
387+
msg.push(displayVar(k, value))
381388
}
382389
msg.push('')
383390
}
@@ -389,11 +396,12 @@ ${defData}
389396
async listJson () {
390397
const publicConf = {}
391398
for (const key in this.npm.config.list[0]) {
392-
if (!publicVar(key)) {
399+
const value = this.npm.config.get(key)
400+
if (isPrivate(key, value)) {
393401
continue
394402
}
395403

396-
publicConf[key] = this.npm.config.get(key)
404+
publicConf[key] = value
397405
}
398406
output.standard(JSON.stringify(publicConf, null, 2))
399407
}

test/lib/commands/config.js

+19
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,25 @@ t.test('config get private key', async t => {
505505
)
506506
})
507507

508+
t.test('config redacted values', async t => {
509+
const { npm, joinedOutput, clearOutput } = await loadMockNpm(t)
510+
511+
await npm.exec('config', ['set', 'proxy', 'https://proxy.npmjs.org/'])
512+
await npm.exec('config', ['get', 'proxy'])
513+
514+
t.equal(joinedOutput(), 'https://proxy.npmjs.org/')
515+
clearOutput()
516+
517+
await npm.exec('config', ['set', 'proxy', 'https://u:password@proxy.npmjs.org/'])
518+
519+
await t.rejects(npm.exec('config', ['get', 'proxy']), /proxy option is protected/)
520+
521+
await npm.exec('config', ['ls'])
522+
523+
t.match(joinedOutput(), 'proxy = "https://u:***@proxy.npmjs.org/"')
524+
clearOutput()
525+
})
526+
508527
t.test('config edit', async t => {
509528
const EDITOR = 'vim'
510529
const editor = spawk.spawn(EDITOR).exit(0)

0 commit comments

Comments
 (0)