| name | handle-secrets |
| description | Guide for handling sensitive data (secrets) in this Rust project. NEVER use plain String for API tokens, passwords, or other credentials. Use the current stable secrecy crate's direct secret types to prevent accidental exposure through Debug output, logs, and error messages. Call .expose_secret() only when the actual value is needed. Use when working with credentials, API keys, tokens, passwords, or any sensitive configuration. Triggers on "secret", "API token", "password", "credential", "sensitive data", "secrecy", or "expose secret". |
| metadata | {"author":"torrust","version":"1.2"} |
Handling Sensitive Data (Secrets)
Core Rule
NEVER use plain String for sensitive data. Use the current stable
secrecy::SecretString type for string secrets to prevent accidental exposure.
pub struct ApiConfig {
pub token: String,
}
println!("{config:?}");
use secrecy::SecretString;
pub struct ApiConfig {
pub token: SecretString,
}
println!("{config:?}");
Using the secrecy Crate
Add the dependency:
[dependencies]
secrecy = { version = "0.10", features = [ "serde" ] }
Enable serde only when a secret must be read from or written to a serialized
configuration format. This is an intentional opt-in: configuration-file syntax remains
unchanged while the Rust type becomes SecretString.
Basic usage:
use secrecy::{ExposeSecret, SecretString};
let token = SecretString::from("my-api-token");
let token_str: &str = token.expose_secret();
What to Protect
Wrap with SecretString (or another appropriate direct secrecy type) when the value is:
- API tokens (REST API admin token, external service tokens)
- Passwords (database credentials, service accounts)
- Private keys or certificates
Rules for .expose_secret()
- Call as late as possible โ only at the point where the value is required
- Never call in
log!, debug!, info!, warn!, error! macros
- Never call in
Display or Debug implementations
- Never include in error messages that may be logged or shown to users
let response = client
.get(url)
.header("Authorization", format!("Bearer {}", token.expose_secret()))
.send()
.await?;
tracing::debug!("Using token: {}", token.expose_secret());
Serialization and Test Expectations
- Keep existing configuration-file syntax for secret values unless a deliberate schema change
is required.
secrecy with its serde feature supports deserializing a TOML string directly
into SecretString.
SecretString deliberately does not serialize automatically. Separate serialization format
from disclosure intent: generic serialization and diagnostic output redact for every format;
an explicitly named, authorized persistence boundary may call .expose_secret() only to emit
a runnable configuration artifact. Do not infer disclosure intent from TOML, JSON, or another
format alone.
- Test redaction without exposing the secret. For
SecretString, assert that Debug output
contains the exact literal SecretBox<str>([REDACTED]) and does not contain the unique test
value.
- Do not write assertions, snapshots, test failures, or diagnostics that call
.expose_secret() merely to inspect a value. Restrict exposure tests to the runtime boundary
that genuinely consumes the secret.
- Do not remove unrelated legacy redaction solely because a new secret field is type-protected;
credential-bearing strings continue to require their existing masking until migrated.
Checklist