From de3e381ab5b32ef18f29a75b129415482fe6db84 Mon Sep 17 00:00:00 2001 From: Gaston Yelmini Date: Mon, 4 Aug 2025 18:25:55 -0300 Subject: [PATCH 1/2] Refact auth headers from error response in alpha --- .../src/api/__tests__/index.test.ts | 28 +++++++++++++++ .../integration-sdk-runtime/src/api/index.ts | 34 +++++++++++++++++++ 2 files changed, 62 insertions(+) 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..260339907 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 From 8dcd84bc69f7196b3e2898ae0a65f4db3bdb199f Mon Sep 17 00:00:00 2001 From: Gaston Yelmini Date: Mon, 4 Aug 2025 18:28:12 -0300 Subject: [PATCH 2/2] Add safety check --- packages/integration-sdk-runtime/src/api/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integration-sdk-runtime/src/api/index.ts b/packages/integration-sdk-runtime/src/api/index.ts index 260339907..c1da355bb 100644 --- a/packages/integration-sdk-runtime/src/api/index.ts +++ b/packages/integration-sdk-runtime/src/api/index.ts @@ -62,7 +62,7 @@ export function createApiClient({ const client = new Alpha(opts) as ApiClient; // Redact Authorization header from error response - client.interceptors.response.use( + client.interceptors?.response?.use( (response) => response, (error: any) => { if (error?.config?.headers) {