From 989491178115ad3bb898b7c681ab6cad4293e8f2 Mon Sep 17 00:00:00 2001 From: Luke Karrys Date: Tue, 22 Mar 2022 09:25:14 -0700 Subject: [PATCH] feat: clean token from logged urls (#107) * chore: remove unused log file * feat: clean token from logged urls and export helper --- lib/check-response.js | 15 ++------------- lib/clean-url.js | 24 ++++++++++++++++++++++++ lib/index.js | 2 ++ lib/silentlog.js | 14 -------------- test/check-response.js | 28 ++++++++++++++++++++++++++++ 5 files changed, 56 insertions(+), 27 deletions(-) create mode 100644 lib/clean-url.js delete mode 100644 lib/silentlog.js diff --git a/lib/check-response.js b/lib/check-response.js index 872ec8a..7145139 100644 --- a/lib/check-response.js +++ b/lib/check-response.js @@ -4,6 +4,7 @@ const errors = require('./errors.js') const { Response } = require('minipass-fetch') const defaultOpts = require('./default-opts.js') const log = require('proc-log') +const cleanUrl = require('./clean-url.js') /* eslint-disable-next-line max-len */ const moreInfoUrl = 'https://github.com/npm/cli/wiki/No-auth-for-URI,-but-auth-present-for-scoped-registry' @@ -45,19 +46,7 @@ function logRequest (method, res, startTime) { const attemptStr = attempt && attempt > 1 ? ` attempt #${attempt}` : '' const cacheStatus = res.headers.get('x-local-cache-status') const cacheStr = cacheStatus ? ` (cache ${cacheStatus})` : '' - - let urlStr - try { - const { URL } = require('url') - const url = new URL(res.url) - if (url.password) { - url.password = '***' - } - - urlStr = url.toString() - } catch (er) { - urlStr = res.url - } + const urlStr = cleanUrl(res.url) log.http( 'fetch', diff --git a/lib/clean-url.js b/lib/clean-url.js new file mode 100644 index 0000000..ba31dc4 --- /dev/null +++ b/lib/clean-url.js @@ -0,0 +1,24 @@ +const { URL } = require('url') + +const replace = '***' +const tokenRegex = /\bnpm_[a-zA-Z0-9]{36}\b/g +const guidRegex = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g + +const cleanUrl = (str) => { + if (typeof str !== 'string' || !str) { + return str + } + + try { + const url = new URL(str) + if (url.password) { + str = str.replace(url.password, replace) + } + } catch {} + + return str + .replace(tokenRegex, `npm_${replace}`) + .replace(guidRegex, `npm_${replace}`) +} + +module.exports = cleanUrl diff --git a/lib/index.js b/lib/index.js index 19c9214..a0fc280 100644 --- a/lib/index.js +++ b/lib/index.js @@ -239,3 +239,5 @@ function getHeaders (uri, auth, opts) { return headers } + +module.exports.cleanUrl = require('./clean-url.js') diff --git a/lib/silentlog.js b/lib/silentlog.js deleted file mode 100644 index 483bd44..0000000 --- a/lib/silentlog.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict' - -const noop = Function.prototype -module.exports = { - error: noop, - warn: noop, - notice: noop, - info: noop, - verbose: noop, - silly: noop, - http: noop, - pause: noop, - resume: noop, -} diff --git a/test/check-response.js b/test/check-response.js index d005e3f..93e6918 100644 --- a/test/check-response.js +++ b/test/check-response.js @@ -135,6 +135,34 @@ t.test('redact password from log', t => { t.match(msg, /^GET 200 http:\/\/username:\*\*\*@example.com\/foo\/bar\/baz [0-9]+m?s/) }) +t.test('redact well known token from log', t => { + const headers = new Headers() + const EE = require('events') + headers.get = header => undefined + const res = Object.assign({}, mockFetchRes, { + headers, + status: 200, + url: `http://example.com/foo/bar/baz/npm_${'a'.repeat(36)}`, + body: new EE(), + }) + t.plan(2) + let header, msg + process.on('log', (level, ...args) => { + if (level === 'http') { + ;[header, msg] = args + } + }) + checkResponse({ + method: 'get', + res, + registry, + startTime, + }) + res.body.emit('end') + t.equal(header, 'fetch') + t.match(msg, /^GET 200 http:\/\/example.com\/foo\/bar\/baz\/npm_\*\*\* [0-9]+m?s/) +}) + /* eslint-disable-next-line max-len */ const moreInfoUrl = 'https://github.com/npm/cli/wiki/No-auth-for-URI,-but-auth-present-for-scoped-registry'