From 2319f7ba3eb700d309ce7ba76c76fca225652f47 Mon Sep 17 00:00:00 2001 From: Radim Hopp Date: Fri, 27 Feb 2026 08:29:04 +0100 Subject: [PATCH 1/2] Use JSON.stringify for object interpolation in log statements to avoid [object Object] Co-Authored-By: Claude Opus 4.6 --- src/api/azure/http/azure-http.client.ts | 4 ++-- src/api/azure/services/azure-variable-group.service.ts | 2 +- src/api/rhdh/developerhub.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/azure/http/azure-http.client.ts b/src/api/azure/http/azure-http.client.ts index 304be6c6..0a315156 100644 --- a/src/api/azure/http/azure-http.client.ts +++ b/src/api/azure/http/azure-http.client.ts @@ -45,9 +45,9 @@ export class AzureHttpClient extends BaseHttpClient { response => response, (error: AxiosError) => { if (error.response) { - this.logger.error(`Azure DevOps API Error: ${error.response.status} ${error.response.statusText} - ${error.response.data}`); + this.logger.error(`Azure DevOps API Error: ${error.response.status} ${error.response.statusText} - ${JSON.stringify(error.response.data)}`); } else if (error.request) { - this.logger.error(`Azure DevOps API Error: No response received - ${error.request}`); + this.logger.error(`Azure DevOps API Error: No response received - ${JSON.stringify(error.request)}`); } else { this.logger.error(`Azure DevOps API Error: Request setup failed - ${error}`); } diff --git a/src/api/azure/services/azure-variable-group.service.ts b/src/api/azure/services/azure-variable-group.service.ts index 2979da66..a7ab1ed2 100644 --- a/src/api/azure/services/azure-variable-group.service.ts +++ b/src/api/azure/services/azure-variable-group.service.ts @@ -41,7 +41,7 @@ export class AzureVariableGroupService { `${this.project}/_apis/distributedtask/variablegroups?${this.getApiVersionParam()}`, payload ); - this.logger.info(`AzureCI group creation response: ${response}`); + this.logger.info(`AzureCI group creation response: ${JSON.stringify(response)}`); } catch (error) { this.logger.error(`Failed to create variable group '${groupName}': ${error}`); throw error; diff --git a/src/api/rhdh/developerhub.ts b/src/api/rhdh/developerhub.ts index 32b0632e..dad4febc 100644 --- a/src/api/rhdh/developerhub.ts +++ b/src/api/rhdh/developerhub.ts @@ -43,7 +43,7 @@ export class DeveloperHub { componentScaffoldOptions: ScaffolderScaffoldOptions ): Promise { try { - this.logger.info(`Creating component with options: ${componentScaffoldOptions}`); + this.logger.info(`Creating component with options: ${JSON.stringify(componentScaffoldOptions)}`); const response: AxiosResponse = await this.axios.post( `${this.url}/api/scaffolder/v2/tasks`, componentScaffoldOptions From 79c8d0da1d5264975cb92c06f8e9837dbffbe04a Mon Sep 17 00:00:00 2001 From: Radim Hopp Date: Fri, 27 Feb 2026 09:13:09 +0100 Subject: [PATCH 2/2] Sanitize log output: avoid leaking sensitive data and fix error handling in Azure HTTP client Co-Authored-By: Claude Opus 4.6 --- src/api/azure/http/azure-http.client.ts | 57 ++++++++++++++----- .../services/azure-variable-group.service.ts | 3 +- src/api/rhdh/developerhub.ts | 3 +- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/api/azure/http/azure-http.client.ts b/src/api/azure/http/azure-http.client.ts index 0a315156..1e7216b0 100644 --- a/src/api/azure/http/azure-http.client.ts +++ b/src/api/azure/http/azure-http.client.ts @@ -1,8 +1,24 @@ import { BaseHttpClient } from '../../common/http/base-http.client'; -import { AxiosError } from 'axios'; +import { ApiError } from '../../common/errors/api.errors'; import { AzureApiError } from '../errors/azure.errors'; import { LoggerFactory, Logger } from '../../../logger/logger'; +function sanitizeErrorData(data: unknown): string { + if (data == null) return 'no data'; + if (typeof data === 'string') return data.slice(0, 2000); + if (typeof data === 'object') { + const { message, typeKey, errorCode, statusCode } = data as Record; + const parts = [ + message != null ? `message=${message}` : null, + typeKey != null ? `typeKey=${typeKey}` : null, + errorCode != null ? `errorCode=${errorCode}` : null, + statusCode != null ? `statusCode=${statusCode}` : null, + ].filter(Boolean); + return parts.length > 0 ? parts.join(', ') : '[response data omitted]'; + } + return String(data); +} + export interface AzureHttpClientConfig { organization: string; host: string; @@ -43,21 +59,36 @@ export class AzureHttpClient extends BaseHttpClient { this.client.interceptors.response.use( response => response, - (error: AxiosError) => { - if (error.response) { - this.logger.error(`Azure DevOps API Error: ${error.response.status} ${error.response.statusText} - ${JSON.stringify(error.response.data)}`); + (error) => { + // Build the AzureApiError first, so wrapping always succeeds regardless of logging + let azureError: AzureApiError; + + // BaseHttpClient already transforms AxiosError → ApiError + // Map ApiError to Azure-specific errors first + if (error instanceof ApiError) { + azureError = new AzureApiError(error.message, error.status, error.data, error); + try { + this.logger.error(`Azure DevOps API Error: ${error.status} - ${sanitizeErrorData(error.data)}`); + } catch { /* logging must not break error propagation */ } + } else if (error.response) { + // Fallback: Handle raw AxiosError if BaseHttpClient interceptor didn't catch it + azureError = new AzureApiError(error.message, error.response.status, error.response.data, error); + try { + this.logger.error(`Azure DevOps API Error: ${error.response.status} ${error.response.statusText} - ${sanitizeErrorData(error.response.data)}`); + } catch { /* logging must not break error propagation */ } } else if (error.request) { - this.logger.error(`Azure DevOps API Error: No response received - ${JSON.stringify(error.request)}`); + azureError = new AzureApiError('No response received from Azure DevOps', undefined, undefined, error); + try { + const { method, baseURL, url, timeout } = error.request?.config ?? error.config ?? {}; + this.logger.error(`Azure DevOps API Error: No response received - ${method?.toUpperCase()} ${baseURL ?? ''}${url ?? ''} (timeout: ${timeout})`); + } catch { /* logging must not break error propagation */ } } else { - this.logger.error(`Azure DevOps API Error: Request setup failed - ${error}`); + azureError = new AzureApiError('Request setup failed', undefined, undefined, error); + try { + this.logger.error(`Azure DevOps API Error: Request setup failed - ${error}`); + } catch { /* logging must not break error propagation */ } } - // Wrap AxiosError in a custom AzureApiError - const azureError = new AzureApiError( - error.message, - error.response?.status, - error.response?.data, - error - ); + return Promise.reject(azureError); } ); diff --git a/src/api/azure/services/azure-variable-group.service.ts b/src/api/azure/services/azure-variable-group.service.ts index a7ab1ed2..17b84e3e 100644 --- a/src/api/azure/services/azure-variable-group.service.ts +++ b/src/api/azure/services/azure-variable-group.service.ts @@ -41,7 +41,8 @@ export class AzureVariableGroupService { `${this.project}/_apis/distributedtask/variablegroups?${this.getApiVersionParam()}`, payload ); - this.logger.info(`AzureCI group creation response: ${JSON.stringify(response)}`); + const { id, name } = (response as any) ?? {}; + this.logger.info(`Variable group created: id=${id}, name=${name}`); } catch (error) { this.logger.error(`Failed to create variable group '${groupName}': ${error}`); throw error; diff --git a/src/api/rhdh/developerhub.ts b/src/api/rhdh/developerhub.ts index dad4febc..a5325724 100644 --- a/src/api/rhdh/developerhub.ts +++ b/src/api/rhdh/developerhub.ts @@ -43,7 +43,8 @@ export class DeveloperHub { componentScaffoldOptions: ScaffolderScaffoldOptions ): Promise { try { - this.logger.info(`Creating component with options: ${JSON.stringify(componentScaffoldOptions)}`); + const { templateRef, values: { name, repoName, namespace, ciType, hostType } = {} } = componentScaffoldOptions ?? {}; + this.logger.info(`Creating component: template=${templateRef}, name=${name}, repo=${repoName}, namespace=${namespace}, ci=${ciType}, host=${hostType}`); const response: AxiosResponse = await this.axios.post( `${this.url}/api/scaffolder/v2/tasks`, componentScaffoldOptions