1use crate::sec::secret;
12
13pub fn resolve_headers(templates: &[(String, String)]) -> Result<Vec<(String, String)>, String> {
20 let env = |k: &str| std::env::var(k).ok();
21 templates
22 .iter()
23 .map(|(name, value)| Ok((name.clone(), secret::resolve(value, &env)?)))
24 .collect()
25}
26
27pub fn headers_resolvable(templates: &[(String, String)]) -> Result<(), String> {
30 resolve_headers(templates).map(|_| ())
31}
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36 use std::io::Write;
37
38 #[test]
39 fn resolves_env_secret_in_header_value() {
40 unsafe { std::env::set_var("MCP_AUTH_TEST_TOKEN", "s3cr3t") };
42 let headers = resolve_headers(&[(
43 "Authorization".into(),
44 "Bearer {{secret:MCP_AUTH_TEST_TOKEN}}".into(),
45 )])
46 .unwrap();
47 assert_eq!(headers[0].0, "Authorization");
48 assert_eq!(headers[0].1, "Bearer s3cr3t");
49 unsafe { std::env::remove_var("MCP_AUTH_TEST_TOKEN") };
50 }
51
52 #[test]
53 fn resolves_secret_file() {
54 let mut f = tempfile::NamedTempFile::new().unwrap();
55 writeln!(f, "file-token").unwrap();
56 let tmpl = format!("Bearer {{{{secret-file:{}}}}}", f.path().to_str().unwrap());
57 let headers = resolve_headers(&[("x-api-key".into(), tmpl)]).unwrap();
58 assert_eq!(headers[0].1, "Bearer file-token");
59 }
60
61 #[test]
62 fn plain_header_passes_through() {
63 let headers = resolve_headers(&[("Accept".into(), "application/json".into())]).unwrap();
64 assert_eq!(headers[0].1, "application/json");
65 }
66
67 #[test]
68 fn missing_secret_is_an_error_without_the_value() {
69 let err = resolve_headers(&[(
70 "Authorization".into(),
71 "Bearer {{secret:DEFINITELY_UNSET_MCP_VAR}}".into(),
72 )])
73 .unwrap_err();
74 assert!(err.contains("DEFINITELY_UNSET_MCP_VAR"));
75 assert!(!err.contains("Bearer"), "the value must not leak: {err}");
76 }
77}