{"record":{"id":"d56b2606e1e94d41","repo":"gitbutlerapp/gitbutler","slug":"bug-sensitive-data-cannot-be-serialized-it-need","errorCode":null,"errorMessage":"BUG: Sensitive data cannot be serialized - it needs to be extracted and put into a struct for serialization explicitly","messagePattern":"BUG: Sensitive data cannot be serialized - it needs to be extracted and put into a struct for serialization explicitly","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-secret/src/sensitive.rs","lineNumber":15,"sourceCode":"use std::ops::{Deref, DerefMut};\n\nuse serde::{Deserialize, Deserializer, Serialize, Serializer};\n\nuse crate::Sensitive;\n\nimpl<T> Serialize for Sensitive<T>\nwhere\n    T: Serialize,\n{\n    fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        unreachable!(\n            \"BUG: Sensitive data cannot be serialized - it needs to be extracted and put into a struct for serialization explicitly\"\n        )\n    }\n}\nimpl<'de, T> Deserialize<'de> for Sensitive<T>\nwhere\n    T: Deserialize<'de>,\n{\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        T::deserialize(deserializer).map(Sensitive)\n    }\n}\n\nimpl<T> std::fmt::Debug for Sensitive<T>\nwhere","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-secret/src/sensitive.rs#L1-L33","documentation":"`Sensitive<T>` in but-secret is a secrecy wrapper whose blanket serde `Serialize` impl deliberately panics: serializing secrets accidentally (state files, logs, IPC payloads, API DTOs) is treated as a bug. To persist or transmit data containing a `Sensitive` field you must build an explicit serializable struct and consciously include (or omit) the secret (crates/but-secret/src/sensitive.rs:15).","triggerScenarios":"`#[derive(Serialize)]` on any struct that contains a `Sensitive<T>` field, followed by serde_json/toml serialization or returning it from a Tauri command; recursively serializing a domain type that embeds Sensitive.","commonSituations":"Adding a new config/state/session struct that carries a token or credential; passing internal domain types across an API boundary instead of a DTO; snapshotting or logging app state for debugging.","solutions":["Introduce a dedicated serializable DTO that omits the secret or exposes it explicitly via the wrapper's expose API, and convert before serializing","If the field must stay on the serialized type, exclude it with `#[serde(skip)]` and default-construct it on load","Audit with a grep for `Sensitive<` inside `#[derive(Serialize)]` types as a review check"],"exampleFix":"// before\n#[derive(serde::Serialize)]\nstruct Session {\n    name: String,\n    token: but_secret::Sensitive<String>, // panics at to_string()\n}\nserde_json::to_string(&session)?;\n\n// after - explicit DTO, secret consciously handled\n#[derive(serde::Serialize)]\nstruct SessionDto {\n    name: String,\n    // token intentionally omitted; never serialized\n}\nlet dto = SessionDto { name: session.name.clone() };\nserde_json::to_string(&dto)?;","handlingStrategy":"type-guard","validationCode":"// Keep Sensitive fields out of serialized types by construction:\n// define DTOs explicitly and convert before serde ever sees the domain type\n#[derive(serde::Serialize)]\nstruct AuthDto {\n    username: String,\n    // no token field: Sensitive<String> stays in the domain type only\n}","typeGuard":"// Compile-time guard: registering a domain type for serialization fails if it (still)\n// contains Sensitive, because Sensitive's Serialize impl panics at runtime - so instead\n// whitelist explicit DTOs:\ntrait SafeSerialize: serde::Serialize {}\nimpl SafeSerialize for AuthDto {} // only DTOs opt in; domain types never implement it","tryCatchPattern":"// If a panic escapes third-party code:\nlet json = std::panic::catch_unwind(|| serde_json::to_string(&value))\n    .map_err(|_| anyhow::anyhow!(\"type contained Sensitive data; build an explicit DTO\"))?;","preventionTips":["Never #[derive(Serialize)] on structs containing Sensitive<T>","Convert domain types to DTOs at API/IPC boundaries before serialization","Use #[serde(skip)] if a Sensitive field must ride along on an otherwise-serializable struct","Grep the codebase for 'Sensitive<' inside files with 'derive(Serialize)' as a review gate"],"tags":["rust","serde","secrets","serialization","panic","security","but-secret"],"backgroundTag":"secret-leakage-guard","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}