Skip to content

Commit 48dddc2

Browse files
authored
fix(trace): handle all header flag formats and missing secret headers in redaction (#1009)
## Summary - Handle `--user`/`-u` flags (redact next argument) - Handle `--header=value` concatenated form - Handle `-Hvalue` concatenated form - Add missing secret headers: `cookie`, `proxy-authorization`, `set-cookie`, `x-csrf-token` ## What & Why `redact_argv` only handled `-H`/`--header` as standalone flags. Credentials passed via `--user admin:pass`, `-HAuthorization: Bearer token`, or `--header=Authorization: Bearer token` were not redacted, leaking to trace output. ## Tests Added - `test_redact_user_flag` / `test_redact_short_user_flag` - `test_redact_header_equals_form` - `test_redact_concatenated_h_flag` - `test_redact_cookie_header` - `test_redact_proxy_authorization` Closes #1002
1 parent 49f64af commit 48dddc2

File tree

2 files changed

+120
-9
lines changed

2 files changed

+120
-9
lines changed

crates/bashkit/src/trace.rs

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,15 @@ const SECRET_SUFFIXES: &[&str] = &[
224224
"_PASS",
225225
"_CREDENTIAL",
226226
];
227-
const SECRET_HEADERS: &[&str] = &["authorization", "x-api-key", "x-auth-token"];
227+
const SECRET_HEADERS: &[&str] = &[
228+
"authorization",
229+
"x-api-key",
230+
"x-auth-token",
231+
"cookie",
232+
"proxy-authorization",
233+
"set-cookie",
234+
"x-csrf-token",
235+
];
228236

229237
/// Redact secret patterns from trace event details.
230238
fn redact_details(details: TraceEventDetails) -> TraceEventDetails {
@@ -239,6 +247,7 @@ fn redact_details(details: TraceEventDetails) -> TraceEventDetails {
239247
}
240248

241249
/// Redact secret values in command arguments.
250+
// THREAT[TM-LOG-001]: Redact credentials from trace output in all flag formats
242251
fn redact_argv(argv: &[String]) -> Vec<String> {
243252
let mut result = Vec::with_capacity(argv.len());
244253
let mut redact_next = false;
@@ -252,14 +261,52 @@ fn redact_argv(argv: &[String]) -> Vec<String> {
252261

253262
let lower = arg.to_lowercase();
254263

255-
// Check for -H "Authorization: Bearer xxx" style headers
256-
if lower == "-h" || lower == "--header" {
264+
// --header "value" or -H "value" (standalone flags — redact next arg)
265+
if lower == "-h" || lower == "--header" || lower == "--user" || lower == "-u" {
257266
result.push(arg.clone());
258267
redact_next = true;
259268
continue;
260269
}
261270

262-
// Check for "Authorization: xxx" or "Bearer xxx" inline
271+
// --header=Authorization: Bearer xxx (= concatenated form)
272+
if let Some(eq_pos) = arg
273+
.find('=')
274+
.filter(|_| lower.starts_with("--header=") || lower.starts_with("--user="))
275+
{
276+
let header_val = &arg[eq_pos + 1..];
277+
let header_lower = header_val.to_lowercase();
278+
if SECRET_HEADERS
279+
.iter()
280+
.any(|h| header_lower.starts_with(&format!("{h}:")))
281+
|| lower.starts_with("--user=")
282+
{
283+
result.push(format!("{}=[REDACTED]", &arg[..eq_pos]));
284+
} else {
285+
result.push(arg.clone());
286+
}
287+
continue;
288+
}
289+
290+
// -HAuthorization: Bearer xxx (concatenated -H form)
291+
if (lower.starts_with("-h") && lower.len() > 2 && !lower.starts_with("-h="))
292+
|| (lower.starts_with("-u") && lower.len() > 2 && !lower.starts_with("-u="))
293+
{
294+
let prefix = &arg[..2]; // -H or -u
295+
let val = &arg[2..];
296+
let val_lower = val.to_lowercase();
297+
if lower.starts_with("-u")
298+
|| SECRET_HEADERS
299+
.iter()
300+
.any(|h| val_lower.starts_with(&format!("{h}:")))
301+
{
302+
result.push(format!("{prefix}[REDACTED]"));
303+
} else {
304+
result.push(arg.clone());
305+
}
306+
continue;
307+
}
308+
309+
// Check for "Authorization: xxx" or "Cookie: xxx" inline
263310
if SECRET_HEADERS
264311
.iter()
265312
.any(|h| lower.starts_with(&format!("{h}:")))
@@ -409,4 +456,68 @@ mod tests {
409456
panic!("wrong event type");
410457
}
411458
}
459+
460+
#[test]
461+
fn test_redact_user_flag() {
462+
let argv = vec![
463+
"curl".into(),
464+
"--user".into(),
465+
"admin:password123".into(),
466+
"https://api.example.com".into(),
467+
];
468+
let redacted = redact_argv(&argv);
469+
assert_eq!(redacted[2], "[REDACTED]");
470+
}
471+
472+
#[test]
473+
fn test_redact_short_user_flag() {
474+
let argv = vec![
475+
"curl".into(),
476+
"-u".into(),
477+
"admin:password123".into(),
478+
"https://api.example.com".into(),
479+
];
480+
let redacted = redact_argv(&argv);
481+
assert_eq!(redacted[2], "[REDACTED]");
482+
}
483+
484+
#[test]
485+
fn test_redact_header_equals_form() {
486+
let argv = vec![
487+
"curl".into(),
488+
"--header=Authorization: Bearer token".into(),
489+
"https://api.example.com".into(),
490+
];
491+
let redacted = redact_argv(&argv);
492+
assert_eq!(redacted[1], "--header=[REDACTED]");
493+
}
494+
495+
#[test]
496+
fn test_redact_concatenated_h_flag() {
497+
let argv = vec![
498+
"curl".into(),
499+
"-HAuthorization: Bearer secret".into(),
500+
"https://api.example.com".into(),
501+
];
502+
let redacted = redact_argv(&argv);
503+
assert_eq!(redacted[1], "-H[REDACTED]");
504+
}
505+
506+
#[test]
507+
fn test_redact_cookie_header() {
508+
let argv = vec!["curl".into(), "cookie: session=abc123".into()];
509+
let redacted = redact_argv(&argv);
510+
assert_eq!(redacted[1], "cookie: [REDACTED]");
511+
}
512+
513+
#[test]
514+
fn test_redact_proxy_authorization() {
515+
let argv = vec![
516+
"curl".into(),
517+
"-H".into(),
518+
"Proxy-Authorization: Basic abc".into(),
519+
];
520+
let redacted = redact_argv(&argv);
521+
assert_eq!(redacted[2], "[REDACTED]");
522+
}
412523
}

supply-chain/config.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -991,27 +991,27 @@ version = "1.11.0"
991991
criteria = "safe-to-run"
992992

993993
[[exemptions.pyo3]]
994-
version = "0.28.2"
994+
version = "0.28.3"
995995
criteria = "safe-to-deploy"
996996

997997
[[exemptions.pyo3-async-runtimes]]
998998
version = "0.28.0"
999999
criteria = "safe-to-deploy"
10001000

10011001
[[exemptions.pyo3-build-config]]
1002-
version = "0.28.2"
1002+
version = "0.28.3"
10031003
criteria = "safe-to-deploy"
10041004

10051005
[[exemptions.pyo3-ffi]]
1006-
version = "0.28.2"
1006+
version = "0.28.3"
10071007
criteria = "safe-to-deploy"
10081008

10091009
[[exemptions.pyo3-macros]]
1010-
version = "0.28.2"
1010+
version = "0.28.3"
10111011
criteria = "safe-to-deploy"
10121012

10131013
[[exemptions.pyo3-macros-backend]]
1014-
version = "0.28.2"
1014+
version = "0.28.3"
10151015
criteria = "safe-to-deploy"
10161016

10171017
[[exemptions.python3-dll-a]]

0 commit comments

Comments
 (0)