| Crates.io | serde_mask_derive |
| lib.rs | serde_mask_derive |
| version | 0.1.1 |
| created_at | 2026-01-07 23:30:02.88362+00 |
| updated_at | 2026-01-07 23:44:08.565098+00 |
| description | Mask sensitive data during serde serialization for LLM ingestion |
| homepage | |
| repository | https://github.com/nestyy1337/serde_mask |
| max_upload_size | |
| id | 2029253 |
| size | 13,606 |
Mask sensitive data during serde serialization for LLM ingestion, with secrets attached to the object, not a separate struct like expunge does.
The crate intercepts serde serialization and substitutes sensitive values with placeholders. A deanonymize method on the struct lets you replace placeholders in LLM responses with the original values.
use serde_mask::{anonymize, Anonymize, AnonymizeTrait};
#[anonymize]
#[derive(Debug, Anonymize)]
struct Query {
#[anon]
username: String,
public: String,
}
let q = Query {
username: "my_secret_username".to_string(),
public: "visible".to_string(),
__state: std::sync::OnceLock::new(),
};
// Serializes with secret masked
let json = serde_json::to_string(&q).unwrap();
// {"secret":"ANON_123456","public":"visible"}
// LLM responds with something like "Contact user ANON_123456 immediately."
// then we deanonymize it back to "Contact user my_secret_username immediately."
let response = "Contact user ANON_123456 immediately.";
let restored = q.deanonymize(response.to_string());
// "Contact user my_secret_username immediately."
Implement AnonymizeTrait for your own types:
use serde_mask::AnonymizeTrait;
struct Email(String);
impl AnonymizeTrait for Email {
type State = String;
fn anonymize(&self) -> Self::State {
format!("EMAIL_{}", fastrand::usize(0..1_000_000))
}
fn deanonymize(&self, state: Self::State, serialized: &str) -> String {
serialized.replace(&state, &self.0)
}
}
The state is stored in a OnceLock field on the struct itself. This means the anonymization mapping is computed once on first serialize and reused, so deanonymize always uses the same placeholders that were serialized.
Alternative approaches considered:
Crate is breaking invariant of types, for example:
struct Age {
age: usize
}
age type would get converted to String type at serialization. For JSON serde would wrap the value with double quotes, but I bet LLM wouldn't notice.