Daedalus, secrets sanitizer
How Daedalus prevents secrets leaking through the terminal display
Many Daedalus commands print secrets to stdout: gh auth status, doctl auth init, cat /opt/olympus/.env.prod. Without sanitization, these would:
- Show on the operator's screen (visible to anyone with shoulder access).
- Get captured in MCP screenshot tool output (which Claude / other agents see).
- Get logged to Daedalus's optional session log file.
The sanitizer redacts known-secret patterns before display.
Where it runs
In daedalus/src-tauri/src/commands/sanitize.rs, called from the PTY reader task on every output chunk:
fn sanitize_secrets(input: &str) -> String {
let mut result = input.to_string();
for pat in SECRET_PATTERNS.iter() {
result = pat.regex.replace_all(&result, pat.replacement).into_owned();
}
result
}Patterns
static SECRET_PATTERNS: Lazy<Vec<Pattern>> = Lazy::new(|| vec![
// Bearer / Authorization tokens
Pattern::new(r"(?i)bearer\s+[A-Za-z0-9._-]{20,}", "Bearer [REDACTED]"),
Pattern::new(r"(?i)authorization:\s*[^\r\n]+", "Authorization: [REDACTED]"),
// GitHub tokens
Pattern::new(r"ghp_[A-Za-z0-9]{36,}", "ghp_[REDACTED]"),
Pattern::new(r"gho_[A-Za-z0-9]{36,}", "gho_[REDACTED]"),
Pattern::new(r"ghu_[A-Za-z0-9]{36,}", "ghu_[REDACTED]"),
Pattern::new(r"ghr_[A-Za-z0-9]{36,}", "ghr_[REDACTED]"),
Pattern::new(r"ghs_[A-Za-z0-9]{36,}", "ghs_[REDACTED]"),
// DigitalOcean
Pattern::new(r"dop_v1_[a-f0-9]{60,}", "dop_v1_[REDACTED]"),
// Resend
Pattern::new(r"re_[A-Za-z0-9]{20,}", "re_[REDACTED]"),
// Postmark server tokens
Pattern::new(r"[A-Za-z0-9-]{36}", |m| {
// UUID-shape, could be a Postmark token. Conservative.
if m.contains("-") && m.len() == 36 { "[REDACTED-UUID]".to_string() } else { m.to_string() }
}),
// PostgreSQL DSN passwords
Pattern::new(r"postgres(?:ql)?://[^:]+:([^@]+)@", "postgres://[REDACTED-USER]:[REDACTED]@"),
// JWT-like strings
Pattern::new(r"eyJ[A-Za-z0-9._-]+\.eyJ[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+", "[REDACTED-JWT]"),
// Generic high-entropy strings (last resort)
// ... (omitted; high false-positive rate)
]);The list grows as we discover new secret formats.
False positives
Some non-secret strings match. Examples:
- A UUID that's actually a database row ID gets
[REDACTED-UUID]. - A base64-encoded image data URL might trigger a JWT-like pattern.
Trade-off: we err on the side of over-redaction. An operator who needs to see a redacted value can re-run the command outside Daedalus to get the raw output.
False negatives
Some secret formats we don't catch:
- Custom API keys without a recognizable prefix.
- Secrets embedded in JSON values that don't look like tokens at the regex level.
For these, the operator's responsibility increases. The wizard's design tries not to dump raw config files into the terminal, most secret reads happen via Daedalus's UI fields, not via terminal commands.
What's NOT sanitized
- The Daedalus UI itself. The Secrets wizard page shows secrets as the operator enters them, by design.
- Daedalus's persistent state file
daedalus.json. Stored unredacted.
The sanitizer is only for the terminal pipeline + log + screenshot path.
Disabling
For debugging the sanitizer itself: DAEDALUS_SANITIZER_DISABLE=true env var. Strongly recommend not using this except in development.
Testing the sanitizer
cd daedalus/src-tauri
cargo test sanitizeTests verify:
- Known patterns are redacted.
- Non-patterns are preserved.
- Adjacent text isn't munged.