Skip to main content

microsandbox_types/
secret.rs

1//! Redacting, zeroizing wrapper for secret material.
2
3use std::fmt;
4
5use serde::{Deserialize, Serialize};
6use zeroize::{Zeroize, ZeroizeOnDrop};
7
8//--------------------------------------------------------------------------------------------------
9// Types
10//--------------------------------------------------------------------------------------------------
11
12/// Secret material that is redacted in diagnostics and cleared on drop.
13///
14/// Wrapped so any `Debug`-formatted request or error path shows `[redacted]`
15/// instead of the value, and zeroized on drop so the plaintext does not linger
16/// in freed memory.
17#[derive(Clone, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
18#[serde(transparent)]
19pub struct SecretValue(pub String);
20
21//--------------------------------------------------------------------------------------------------
22// Trait Implementations
23//--------------------------------------------------------------------------------------------------
24
25impl fmt::Debug for SecretValue {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        f.write_str("[redacted]")
28    }
29}
30
31//--------------------------------------------------------------------------------------------------
32// Tests
33//--------------------------------------------------------------------------------------------------
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38
39    #[test]
40    fn debug_is_redacted() {
41        let value = SecretValue("sentinel-secret-value".into());
42
43        let debug = format!("{value:?}");
44
45        assert_eq!(debug, "[redacted]");
46        assert!(!debug.contains("sentinel-secret-value"));
47    }
48
49    #[test]
50    fn serde_form_is_a_plain_json_string() {
51        let value = SecretValue("x".into());
52
53        let json = serde_json::to_string(&value).unwrap();
54        let parsed: SecretValue = serde_json::from_str(&json).unwrap();
55
56        assert_eq!(json, "\"x\"");
57        assert_eq!(parsed.0, "x");
58    }
59
60    #[test]
61    fn zeroize_clears_the_value() {
62        fn requires_zeroize_on_drop<T: ZeroizeOnDrop>() {}
63        requires_zeroize_on_drop::<SecretValue>();
64
65        let mut value = SecretValue("sentinel-secret-value".into());
66
67        value.zeroize();
68
69        assert!(value.0.is_empty());
70    }
71}