fix(error): hide auth token in FetchError (#8069)

This commit is contained in:
Zoltan Kochan
2024-05-10 15:05:02 +02:00
committed by GitHub
parent 5dec45196d
commit a7aef51c66
5 changed files with 55 additions and 8 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/error": patch
---
Hide auth token in FetchError object [#8069](https://github.com/pnpm/pnpm/pull/8069).

View File

@@ -0,0 +1,3 @@
const config = require('../../jest.config.js');
module.exports = Object.assign({}, config, {});

View File

@@ -12,8 +12,9 @@
"node": ">=18.12"
},
"scripts": {
"lint": "eslint \"src/**/*.ts\"",
"test": "pnpm run compile",
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
"_test": "jest",
"test": "pnpm run compile && pnpm run _test",
"prepublishOnly": "pnpm run compile",
"compile": "tsc --build && pnpm run lint --fix"
},

View File

@@ -34,28 +34,35 @@ export class FetchError extends PnpmError {
response: FetchErrorResponse,
hint?: string
) {
const _request: FetchErrorRequest = {
url: request.url,
}
if (request.authHeaderValue) {
_request.authHeaderValue = hideAuthInformation(request.authHeaderValue)
}
const message = `GET ${request.url}: ${response.statusText} - ${response.status}`
const authHeaderValue = request.authHeaderValue
? hideAuthInformation(request.authHeaderValue)
: undefined
// NOTE: For security reasons, some registries respond with 404 on authentication errors as well.
// So we print authorization info on 404 errors as well.
if (response.status === 401 || response.status === 403 || response.status === 404) {
hint = hint ? `${hint}\n\n` : ''
if (authHeaderValue) {
hint += `An authorization header was used: ${authHeaderValue}`
if (_request.authHeaderValue) {
hint += `An authorization header was used: ${_request.authHeaderValue}`
} else {
hint += 'No authorization header was set for the request.'
}
}
super(`FETCH_${response.status}`, message, { hint })
this.request = request
this.request = _request
this.response = response
}
}
function hideAuthInformation (authHeaderValue: string): string {
const [authType, token] = authHeaderValue.split(' ')
if (token == null) return '[hidden]'
if (token.length < 20) {
return `${authType} [hidden]`
}
return `${authType} ${token.substring(0, 4)}[hidden]`
}

View File

@@ -0,0 +1,31 @@
import { FetchError } from '@pnpm/error'
test('FetchError escapes auth tokens', () => {
const error = new FetchError(
{ url: 'https://foo.com', authHeaderValue: 'Bearer 00000000000000000000' },
{ status: 401, statusText: 'Unauthorized' }
)
expect(error.message).toBe('GET https://foo.com: Unauthorized - 401')
expect(error.hint).toBe('An authorization header was used: Bearer 0000[hidden]')
expect(error.request.authHeaderValue).toBe('Bearer 0000[hidden]')
})
test('FetchError escapes short auth tokens', () => {
const error = new FetchError(
{ url: 'https://foo.com', authHeaderValue: 'Bearer 0000000000' },
{ status: 401, statusText: 'Unauthorized' }
)
expect(error.message).toBe('GET https://foo.com: Unauthorized - 401')
expect(error.hint).toBe('An authorization header was used: Bearer [hidden]')
expect(error.request.authHeaderValue).toBe('Bearer [hidden]')
})
test('FetchError escapes non-standard auth header', () => {
const error = new FetchError(
{ url: 'https://foo.com', authHeaderValue: '0000000000' },
{ status: 401, statusText: 'Unauthorized' }
)
expect(error.message).toBe('GET https://foo.com: Unauthorized - 401')
expect(error.hint).toBe('An authorization header was used: [hidden]')
expect(error.request.authHeaderValue).toBe('[hidden]')
})