Skip to main content

ccs_proxy/capture/
redact.rs

1//! Default redaction of secret-bearing headers and JSON body keys.
2
3use serde_json::Value;
4use std::collections::BTreeMap;
5
6const SECRET_HEADER_NAMES: &[&str] = &[
7    "authorization",
8    "x-api-key",
9    "anthropic-api-key",
10    "cookie",
11    "set-cookie",
12];
13
14const SECRET_BODY_KEYS: &[&str] = &[
15    "api_key",
16    "apikey",
17    "auth_token",
18    "authtoken",
19    "access_token",
20    "accesstoken",
21    "token",
22    "secret",
23    "password",
24];
25
26const PLACEHOLDER: &str = "<redacted>";
27
28pub fn redact_headers(headers: &mut BTreeMap<String, String>) {
29    for (k, v) in headers.iter_mut() {
30        if SECRET_HEADER_NAMES
31            .iter()
32            .any(|name| k.eq_ignore_ascii_case(name))
33        {
34            *v = PLACEHOLDER.to_string();
35        }
36    }
37}
38
39pub fn redact_body(body: &mut Value) {
40    if let Value::Object(map) = body {
41        for (k, v) in map.iter_mut() {
42            if SECRET_BODY_KEYS
43                .iter()
44                .any(|secret| k.eq_ignore_ascii_case(secret))
45            {
46                *v = Value::String(PLACEHOLDER.to_string());
47            }
48        }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn redacts_known_headers_case_insensitive() {
58        let mut h: BTreeMap<String, String> = [
59            ("Authorization".into(), "Bearer sk-abc".into()),
60            ("X-Api-Key".into(), "sk-xyz".into()),
61            ("content-type".into(), "application/json".into()),
62        ]
63        .into();
64        redact_headers(&mut h);
65        assert_eq!(h["Authorization"], "<redacted>");
66        assert_eq!(h["X-Api-Key"], "<redacted>");
67        assert_eq!(h["content-type"], "application/json");
68    }
69
70    #[test]
71    fn redacts_top_level_body_keys() {
72        let mut body: Value = serde_json::json!({
73            "api_key": "secret",
74            "model": "claude-sonnet-4-6",
75            "nested": { "api_key": "should_not_be_touched" }
76        });
77        redact_body(&mut body);
78        assert_eq!(body["api_key"], "<redacted>");
79        assert_eq!(body["model"], "claude-sonnet-4-6");
80        assert_eq!(body["nested"]["api_key"], "should_not_be_touched");
81    }
82}