diff --git a/packages/integration-sdk-runtime/src/api/__tests__/index.test.ts b/packages/integration-sdk-runtime/src/api/__tests__/index.test.ts index fdfc82516..cdd1d706c 100644 --- a/packages/integration-sdk-runtime/src/api/__tests__/index.test.ts +++ b/packages/integration-sdk-runtime/src/api/__tests__/index.test.ts @@ -143,3 +143,31 @@ describe('compressRequest', () => { expect(config.data).toEqual({ some: 'data' }); }); }); + +describe('real Alpha request with fake API key', () => { + test('should not expose API key in error', async () => { + jest.resetModules(); + jest.unmock('@lifeomic/alpha'); + + const { createApiClient, getApiBaseUrl } = require('../index'); + + const apiBaseUrl = getApiBaseUrl(); + + const client = createApiClient({ + apiBaseUrl, + account: 'test-account', + accessToken: 'test-key', + retryOptions: { + maxTimeout: 20000, + }, + }); + + try { + await client.post('/persister/synchronization/jobs/', { some: 'data' }); + } catch (err: any) { + const errorString = JSON.stringify(err); + + expect(errorString).not.toContain('test-key'); + } + }); +}); diff --git a/packages/integration-sdk-runtime/src/api/index.ts b/packages/integration-sdk-runtime/src/api/index.ts index 40a1eaa15..c1da355bb 100644 --- a/packages/integration-sdk-runtime/src/api/index.ts +++ b/packages/integration-sdk-runtime/src/api/index.ts @@ -60,6 +60,40 @@ export function createApiClient({ }; const client = new Alpha(opts) as ApiClient; + + // Redact Authorization header from error response + client.interceptors?.response?.use( + (response) => response, + (error: any) => { + if (error?.config?.headers) { + error.config.headers = '[REDACTED]'; + } + + if (error?.response?.config?.headers) { + error.response.config.headers = '[REDACTED]'; + } + + if (typeof error?.request?._header === 'string') { + error.request._header = error.request._header.replace( + /Authorization: Bearer\s[^\r\n]+/i, + 'Authorization: [REDACTED]', + ); + } + + const outHeadersSym = Object.getOwnPropertySymbols( + error.request || {}, + ).find((sym) => String(sym).includes('kOutHeaders')); + if (outHeadersSym) { + const outHeaders = (error.request as any)[outHeadersSym]; + if (outHeaders?.authorization) { + outHeaders.authorization = '[REDACTED]'; + } + } + + return Promise.reject(error); + }, + ); + if (compressUploads) { // interceptors is incorrectly typed even without the case to ApiClient. // an AxiosInterceptor doesn't work here. You must use the AlphaInterceptor