cloud_sdk_sanitization/
lib.rs1#![no_std]
2#![doc = include_str!("../README.md")]
3
4#[cfg(feature = "std")]
5extern crate std;
6
7#[cfg(feature = "alloc")]
8pub use sanitization::SecretString;
9
10#[inline]
15pub fn sanitize_bytes(bytes: &mut [u8]) {
16 sanitization::sanitize_bytes(bytes);
17}
18
19pub struct SecretBuffer<'a> {
25 bytes: &'a mut [u8],
26}
27
28impl<'a> SecretBuffer<'a> {
29 #[must_use]
31 pub const fn new(bytes: &'a mut [u8]) -> Self {
32 Self { bytes }
33 }
34
35 #[must_use]
37 pub fn as_mut_slice(&mut self) -> &mut [u8] {
38 self.bytes
39 }
40
41 #[must_use]
43 pub fn as_slice(&self) -> &[u8] {
44 self.bytes
45 }
46}
47
48impl Drop for SecretBuffer<'_> {
49 fn drop(&mut self) {
50 sanitize_bytes(self.bytes);
51 }
52}
53
54impl core::fmt::Debug for SecretBuffer<'_> {
55 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
56 formatter.write_str("SecretBuffer([redacted])")
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 #[cfg(feature = "alloc")]
63 extern crate alloc;
64
65 #[cfg(feature = "alloc")]
66 use super::SecretString;
67 use super::{SecretBuffer, sanitize_bytes};
68
69 #[test]
70 fn explicit_sanitization_clears_every_byte() {
71 let mut bytes = [0xa5_u8; 8];
72 sanitize_bytes(&mut bytes);
73 assert_eq!(bytes, [0; 8]);
74 }
75
76 #[test]
77 fn guard_clears_its_full_buffer_on_drop() {
78 let mut bytes = [0xa5_u8; 8];
79 {
80 let mut guarded = SecretBuffer::new(&mut bytes);
81 if let Some(first) = guarded.as_mut_slice().first_mut() {
82 *first = 0x42;
83 }
84 assert_eq!(guarded.as_slice().first(), Some(&0x42));
85 }
86 assert_eq!(bytes, [0; 8]);
87 }
88
89 #[test]
90 fn guard_clears_after_an_early_error() {
91 fn write_then_fail(output: &mut [u8]) -> Result<(), ()> {
92 let mut guarded = SecretBuffer::new(output);
93 if let Some(first) = guarded.as_mut_slice().first_mut() {
94 *first = 0x42;
95 }
96 Err(())
97 }
98
99 let mut bytes = [0xa5_u8; 8];
100 assert_eq!(write_then_fail(&mut bytes), Err(()));
101 assert_eq!(bytes, [0; 8]);
102 }
103
104 #[cfg(feature = "alloc")]
105 #[test]
106 fn reexported_secret_string_uses_scoped_access_and_redacted_debug() {
107 let secret = SecretString::from_string(alloc::string::String::from("temporary secret"));
108
109 assert_eq!(
110 secret.try_with_secret(|value| value == "temporary secret"),
111 Ok(true)
112 );
113 assert!(!alloc::format!("{secret:?}").contains("temporary secret"));
114 }
115}