Skip to content

Commit b7aae73

Browse files
prontclaude
andauthored
fix(observability): redact sink-specific API key headers in debug logs (#25516)
* fix(observability): redact sink-specific API key headers in debug logs Expand the sensitive header list in `remove_sensitive()` to cover `DD-API-KEY`, `X-Honeycomb-Team`, `x-api-key`, and `Api-Key`, which are injected by Vector's own sinks and were previously logged in plaintext when debug logging was enabled. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(observability): mark all duplicate sensitive header values in debug logs `HeaderMap::get_mut` only returned the first value for a header name, leaving any duplicates (added via `append` or injected by a proxy) unredacted. Switch to `iter_mut` so every entry matching a sensitive name is marked. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: apply rustfmt to http_client tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(observability): add case-insensitive header name test HeaderName normalizes to lowercase, confirming that mixed-case variants like X-Api-Key are redacted by the same path as x-api-key. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(observability): add Proxy-Authenticate, WWW-Authenticate, and Cookie2 to sensitive headers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: update changelog to include newly redacted headers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ba27e2e commit b7aae73

2 files changed

Lines changed: 90 additions & 4 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Redact sink-specific API key headers (`DD-API-KEY`, `X-Honeycomb-Team`, `x-api-key`, `Api-Key`) in debug-level HTTP request and response logs, alongside the existing standard headers (`Authorization`, `Proxy-Authorization`, `Proxy-Authenticate`, `WWW-Authenticate`, `Cookie`, `Set-Cookie`, `Cookie2`).
2+
3+
authors: pront

src/internal_events/http_client.rs

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::time::Duration;
22

33
use http::{
44
Request, Response,
5-
header::{self, HeaderMap, HeaderValue},
5+
header::{self, HeaderMap, HeaderName, HeaderValue},
66
};
77
use hyper::{Error, body::HttpBody};
88
use vector_lib::{
@@ -17,13 +17,21 @@ pub struct AboutToSendHttpRequest<'a, T> {
1717

1818
fn remove_sensitive(headers: &HeaderMap<HeaderValue>) -> HeaderMap<HeaderValue> {
1919
let mut headers = headers.clone();
20-
for name in &[
20+
let sensitive: &[HeaderName] = &[
2121
header::AUTHORIZATION,
2222
header::PROXY_AUTHORIZATION,
23+
header::PROXY_AUTHENTICATE,
24+
header::WWW_AUTHENTICATE,
2325
header::COOKIE,
2426
header::SET_COOKIE,
25-
] {
26-
if let Some(value) = headers.get_mut(name) {
27+
HeaderName::from_static("cookie2"),
28+
HeaderName::from_static("dd-api-key"),
29+
HeaderName::from_static("x-honeycomb-team"),
30+
HeaderName::from_static("x-api-key"),
31+
HeaderName::from_static("api-key"),
32+
];
33+
for (name, value) in headers.iter_mut() {
34+
if sensitive.contains(name) {
2735
value.set_sensitive(true);
2836
}
2937
}
@@ -114,3 +122,78 @@ impl<B: HttpBody> std::fmt::Display for FormatBody<'_, B> {
114122
}
115123
}
116124
}
125+
126+
#[cfg(test)]
127+
mod tests {
128+
use http::header::{self, HeaderMap, HeaderName, HeaderValue};
129+
130+
use super::remove_sensitive;
131+
132+
fn is_sensitive(map: &HeaderMap, name: &HeaderName) -> Vec<bool> {
133+
map.get_all(name)
134+
.iter()
135+
.map(HeaderValue::is_sensitive)
136+
.collect()
137+
}
138+
139+
#[test]
140+
fn marks_single_sensitive_header() {
141+
let mut headers = HeaderMap::new();
142+
headers.insert(
143+
header::AUTHORIZATION,
144+
HeaderValue::from_static("Bearer token"),
145+
);
146+
let result = remove_sensitive(&headers);
147+
assert!(
148+
is_sensitive(&result, &header::AUTHORIZATION)
149+
.iter()
150+
.all(|&s| s)
151+
);
152+
}
153+
154+
#[test]
155+
fn marks_all_duplicate_sensitive_headers() {
156+
let x_api_key: HeaderName = HeaderName::from_static("x-api-key");
157+
let mut headers = HeaderMap::new();
158+
headers.insert(x_api_key.clone(), HeaderValue::from_static("key-one"));
159+
headers.append(x_api_key.clone(), HeaderValue::from_static("key-two"));
160+
headers.append(x_api_key.clone(), HeaderValue::from_static("key-three"));
161+
162+
let result = remove_sensitive(&headers);
163+
let sensitive_flags = is_sensitive(&result, &x_api_key);
164+
assert_eq!(sensitive_flags.len(), 3);
165+
assert!(
166+
sensitive_flags.iter().all(|&s| s),
167+
"not all duplicate x-api-key values were marked sensitive: {sensitive_flags:?}"
168+
);
169+
}
170+
171+
#[test]
172+
fn header_name_matching_is_case_insensitive() {
173+
// HeaderName normalizes to lowercase, so mixed-case variants are identical.
174+
let mut headers = HeaderMap::new();
175+
headers.insert(
176+
HeaderName::from_static("x-api-key"),
177+
HeaderValue::from_static("secret"),
178+
);
179+
let result = remove_sensitive(&headers);
180+
// Lookup with the mixed-case form resolves to the same normalized name.
181+
let mixed_case = HeaderName::from_bytes(b"X-Api-Key").unwrap();
182+
assert!(is_sensitive(&result, &mixed_case).iter().all(|&s| s));
183+
}
184+
185+
#[test]
186+
fn does_not_mark_non_sensitive_headers() {
187+
let mut headers = HeaderMap::new();
188+
headers.insert(
189+
header::CONTENT_TYPE,
190+
HeaderValue::from_static("application/json"),
191+
);
192+
let result = remove_sensitive(&headers);
193+
assert!(
194+
is_sensitive(&result, &header::CONTENT_TYPE)
195+
.iter()
196+
.all(|&s| !s)
197+
);
198+
}
199+
}

0 commit comments

Comments
 (0)