From eae289fc9389efa5d58ac0e50b4f3748f7e88807 Mon Sep 17 00:00:00 2001 From: Rory Nickolls Date: Sun, 19 Apr 2026 10:46:49 +0100 Subject: [PATCH 1/2] fix: redact sensitive headers in tonic debug logs Adds function that ensures headers that look sensitive are redacted, to be used inside debug logs. Attempted a parameterised test but `run_env_test` only accepts static strings for env vars. --- opentelemetry-otlp/src/exporter/tonic/mod.rs | 43 +++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/opentelemetry-otlp/src/exporter/tonic/mod.rs b/opentelemetry-otlp/src/exporter/tonic/mod.rs index 06c74c39f9..21957214a8 100644 --- a/opentelemetry-otlp/src/exporter/tonic/mod.rs +++ b/opentelemetry-otlp/src/exporter/tonic/mod.rs @@ -231,7 +231,9 @@ impl TonicExporterBuilder { let compression = self.resolve_compression(signal_compression_var)?; - let (headers_from_env, headers_for_logging) = parse_headers_from_env(signal_headers_var); + let (headers_from_env, all_headers) = parse_headers_from_env(signal_headers_var); + let headers_for_logging = redact_sensitive_headers(all_headers); + let metadata = merge_metadata_with_headers_from_env( self.tonic_config.metadata.unwrap_or_default(), headers_from_env, @@ -608,6 +610,25 @@ fn parse_headers_from_env(signal_headers_var: &str) -> (HeaderMap, Vec<(String, ) } +fn redact_sensitive_headers(headers: Vec<(String, String)>) -> Vec<(String, String)> { + headers + .iter() + .map(|(k, v)| { + let header_name = k.to_lowercase(); + let value = v.to_lowercase(); + if header_name.contains("auth") + || header_name.contains("api-key") + || header_name.contains("token") + || value.contains("bearer") + { + (k.clone(), "[REDACTED]".to_string()) + } else { + (k.clone(), v.clone()) + } + }) + .collect() +} + /// Expose interface for modifying [TonicConfig] fields within the exporter builders. pub(crate) trait HasTonicConfig { /// Return a mutable reference to the export config within the exporter builders. @@ -807,7 +828,7 @@ impl WithTonicConfig for B { #[cfg(test)] mod tests { use crate::exporter::tests::run_env_test; - use crate::exporter::tonic::WithTonicConfig; + use crate::exporter::tonic::{redact_sensitive_headers, WithTonicConfig}; #[cfg(feature = "grpc-tonic")] use crate::exporter::Compression; use crate::{TonicExporterBuilder, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT}; @@ -981,6 +1002,24 @@ mod tests { ); } + #[test] + fn test_sensitive_headers_redacted() { + run_env_test( + vec![( + OTEL_EXPORTER_OTLP_HEADERS, + "authorization=Bearer my-secret-token", + )], + || { + let headers_from_env = super::parse_headers_from_env(OTEL_EXPORTER_OTLP_HEADERS); + let redacted = redact_sensitive_headers(headers_from_env.1); + + let (_, header_value) = + redacted.iter().find(|(k, _)| k == "authorization").unwrap(); + assert_eq!(*header_value, "[REDACTED]".to_string()) + }, + ); + } + #[test] fn test_priority_of_signal_env_over_generic_env_for_endpoint() { run_env_test( From b66b3bc0f60f71fc853f786c0133955a4a9387ba Mon Sep 17 00:00:00 2001 From: Rory Nickolls Date: Sun, 3 May 2026 14:54:24 +0100 Subject: [PATCH 2/2] refactor: remove header values from `headers_for_logging` entirely --- opentelemetry-otlp/CHANGELOG.md | 1 + opentelemetry-otlp/src/exporter/tonic/mod.rs | 46 ++------------------ 2 files changed, 5 insertions(+), 42 deletions(-) diff --git a/opentelemetry-otlp/CHANGELOG.md b/opentelemetry-otlp/CHANGELOG.md index dbd798630c..0f6c44d666 100644 --- a/opentelemetry-otlp/CHANGELOG.md +++ b/opentelemetry-otlp/CHANGELOG.md @@ -51,6 +51,7 @@ - Fixed [#2777](https://github.com/open-telemetry/opentelemetry rust/issues/2777) to properly handle `shutdown_with_timeout()` when using `grpc-tonic`. - Deprecate `tls` feature in favor of explicit `tls-ring` and `tls-aws-lc` features. **Migration**: Replace `tls` with `tls-ring` (or `tls-aws-lc`). Users of `tls-roots` or `tls-webpki-roots` must now also enable one of these. +- Prevent logging of header values in OTLP tonic exporter [#3465](https://github.com/open-telemetry/opentelemetry-rust/pull/3465) ## 0.31.0 diff --git a/opentelemetry-otlp/src/exporter/tonic/mod.rs b/opentelemetry-otlp/src/exporter/tonic/mod.rs index 21957214a8..a5e4277847 100644 --- a/opentelemetry-otlp/src/exporter/tonic/mod.rs +++ b/opentelemetry-otlp/src/exporter/tonic/mod.rs @@ -231,8 +231,7 @@ impl TonicExporterBuilder { let compression = self.resolve_compression(signal_compression_var)?; - let (headers_from_env, all_headers) = parse_headers_from_env(signal_headers_var); - let headers_for_logging = redact_sensitive_headers(all_headers); + let (headers_from_env, headers_for_logging) = parse_headers_from_env(signal_headers_var); let metadata = merge_metadata_with_headers_from_env( self.tonic_config.metadata.unwrap_or_default(), @@ -588,7 +587,7 @@ fn merge_metadata_with_headers_from_env( } } -fn parse_headers_from_env(signal_headers_var: &str) -> (HeaderMap, Vec<(String, String)>) { +fn parse_headers_from_env(signal_headers_var: &str) -> (HeaderMap, Vec) { let mut headers = Vec::new(); ( @@ -597,7 +596,7 @@ fn parse_headers_from_env(signal_headers_var: &str) -> (HeaderMap, Vec<(String, .map(|input| { parse_header_string(&input) .filter_map(|(key, value)| { - headers.push((key.to_owned(), value.clone())); + headers.push(key.to_owned()); Some(( HeaderName::from_str(key).ok()?, HeaderValue::from_str(&value).ok()?, @@ -610,25 +609,6 @@ fn parse_headers_from_env(signal_headers_var: &str) -> (HeaderMap, Vec<(String, ) } -fn redact_sensitive_headers(headers: Vec<(String, String)>) -> Vec<(String, String)> { - headers - .iter() - .map(|(k, v)| { - let header_name = k.to_lowercase(); - let value = v.to_lowercase(); - if header_name.contains("auth") - || header_name.contains("api-key") - || header_name.contains("token") - || value.contains("bearer") - { - (k.clone(), "[REDACTED]".to_string()) - } else { - (k.clone(), v.clone()) - } - }) - .collect() -} - /// Expose interface for modifying [TonicConfig] fields within the exporter builders. pub(crate) trait HasTonicConfig { /// Return a mutable reference to the export config within the exporter builders. @@ -828,7 +808,7 @@ impl WithTonicConfig for B { #[cfg(test)] mod tests { use crate::exporter::tests::run_env_test; - use crate::exporter::tonic::{redact_sensitive_headers, WithTonicConfig}; + use crate::exporter::tonic::WithTonicConfig; #[cfg(feature = "grpc-tonic")] use crate::exporter::Compression; use crate::{TonicExporterBuilder, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT}; @@ -1002,24 +982,6 @@ mod tests { ); } - #[test] - fn test_sensitive_headers_redacted() { - run_env_test( - vec![( - OTEL_EXPORTER_OTLP_HEADERS, - "authorization=Bearer my-secret-token", - )], - || { - let headers_from_env = super::parse_headers_from_env(OTEL_EXPORTER_OTLP_HEADERS); - let redacted = redact_sensitive_headers(headers_from_env.1); - - let (_, header_value) = - redacted.iter().find(|(k, _)| k == "authorization").unwrap(); - assert_eq!(*header_value, "[REDACTED]".to_string()) - }, - ); - } - #[test] fn test_priority_of_signal_env_over_generic_env_for_endpoint() { run_env_test(