From 4a373625afed1553e80af294e1cd91d09f4fd5fb Mon Sep 17 00:00:00 2001 From: Lucinda Zhou Date: Fri, 15 May 2026 15:18:59 -0400 Subject: [PATCH] Redact Authorization (Bearer) header in sanitize_request_header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit api_client.rb's debug log path calls sanitize_request_header before @config.logger.debug to mask DD-API-KEY and DD-APPLICATION-KEY values. The Authorization header set from Bearer-token auth (delegated tokens, PATs) is missing from the keys_to_redact list and gets logged verbatim — any caller running with debug logging and access-token auth leaks the bearer to the global logger. Surfaced cross-language by terraform-provider-datadog#3757, which is the first Terraform code path to set the equivalent ContextAccessToken in the Go SDK. The same gap exists here in Ruby. Add "Authorization" as a third entry in keys_to_redact. Apply the change to both the .j2 template and the generated api_client.rb so the file matches what the next regen produces. Test (spec/api_client_spec.rb #sanitize_request_header) drives the method directly with all three credential headers plus a non-credential header set, asserts each credential becomes "REDACTED", the non- credential header passes through unchanged, and the input hash is not mutated. Refs: CRED-2625, terraform-provider-datadog#3757 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/generator/templates/api_client.j2 | 4 +-- lib/datadog_api_client/api_client.rb | 4 +-- spec/api_client_spec.rb | 27 +++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/.generator/src/generator/templates/api_client.j2 b/.generator/src/generator/templates/api_client.j2 index 61d6ee4152fb..9ac71ff36324 100644 --- a/.generator/src/generator/templates/api_client.j2 +++ b/.generator/src/generator/templates/api_client.j2 @@ -140,10 +140,10 @@ module {{ module_name }} sleep_time end - #Redact api and app key in the request header + #Redact api key, app key, and Authorization (Bearer) header in the request log def sanitize_request_header(request_header) sanitized_headers= request_header.dup - keys_to_redact = ["DD-API-KEY", "DD-APPLICATION-KEY"] + keys_to_redact = ["DD-API-KEY", "DD-APPLICATION-KEY", "Authorization"] keys_to_redact.each do |key_to_redact| if sanitized_headers.key?(key_to_redact) sanitized_headers[key_to_redact] = "REDACTED" diff --git a/lib/datadog_api_client/api_client.rb b/lib/datadog_api_client/api_client.rb index 86b4f6dad628..c8b7c698f6d1 100644 --- a/lib/datadog_api_client/api_client.rb +++ b/lib/datadog_api_client/api_client.rb @@ -151,10 +151,10 @@ def calculate_retry_interval(response, backoff_base, backoff_multiplier, attempt sleep_time end - #Redact api and app key in the request header + #Redact api key, app key, and Authorization (Bearer) header in the request log def sanitize_request_header(request_header) sanitized_headers= request_header.dup - keys_to_redact = ["DD-API-KEY", "DD-APPLICATION-KEY"] + keys_to_redact = ["DD-API-KEY", "DD-APPLICATION-KEY", "Authorization"] keys_to_redact.each do |key_to_redact| if sanitized_headers.key?(key_to_redact) sanitized_headers[key_to_redact] = "REDACTED" diff --git a/spec/api_client_spec.rb b/spec/api_client_spec.rb index 2231a991b3c8..de7c20b03911 100644 --- a/spec/api_client_spec.rb +++ b/spec/api_client_spec.rb @@ -191,6 +191,33 @@ end end + describe '#sanitize_request_header' do + let(:api_client) { DatadogAPIClient::APIClient.new } + + # Verifies the debug log path redacts api key, app key, and Authorization + # (Bearer) header before the @config.logger.debug call. Without redacting + # Authorization, callers running with debug logging and a PAT or delegated + # token would leak the bearer to stderr / CI artifacts / log shippers. + # See CRED-2625. + it 'redacts DD-API-KEY, DD-APPLICATION-KEY, and Authorization' do + headers = { + 'DD-API-KEY' => 'api-key-secret-value', + 'DD-APPLICATION-KEY' => 'app-key-secret-value', + 'Authorization' => 'Bearer ddpat_supersecret_should_not_leak', + 'Content-Type' => 'application/json', + } + sanitized = api_client.sanitize_request_header(headers) + + expect(sanitized['DD-API-KEY']).to eq('REDACTED') + expect(sanitized['DD-APPLICATION-KEY']).to eq('REDACTED') + expect(sanitized['Authorization']).to eq('REDACTED') + # Non-credential headers pass through unchanged. + expect(sanitized['Content-Type']).to eq('application/json') + # Original hash unchanged (defensive dup). + expect(headers['Authorization']).to eq('Bearer ddpat_supersecret_should_not_leak') + end + end + describe '#sanitize_filename' do let(:api_client) { DatadogAPIClient::APIClient.new }