Skip to main content

handle-secrets

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".

소스 정보

저장소
torrust/torrust-tracker
최근 소스 활동
2026년 9월 4일 16:42
감지된 SKILL.md 언어
영어
스타
532
포크
56

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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. ```rust // ❌ WRONG: secret leaked in Debug output pub struct ApiConfig { pub token: String, } println!("{config:?}"); // → ApiConfig { token: "secret_abc123" } — LEAKED! ``` ```rust // ✅ CORRECT: secret redacted in Debug use secrecy::SecretString; pub struct ApiConfig { pub token: SecretString, } println!("{config:?}"); // → ApiConfig { token: SecretBox<str>([REDACTED]) } ``` ## Using the `secrecy` Crate Add the dependency: ```toml [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: ```rust use secrecy::{ExposeSecret, SecretString}; // Wrap the secret let token = SecretString::from("my-api-token"); // Access the value only when truly needed (e.g., making the actual API call) 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 ```rust // ✅ Correct: called at last moment for HTTP header let response = client .get(url) .header("Authorization", format!("Bearer {}", token.expose_secret())) .send() .await?; // ❌ Wrong: exposed in log tracing::debug!("Using token: {}", token.expose_secret()); ``` ## Comparing Secrets **Never compare a secret with `==`, `!=`, `eq()`, or `Iterator::any` over `==`.** Those may exit at the first differing byte and leak, through timing, how many leading bytes were correct. Use `subtle::ConstantTimeEq` on the byte slices and, when checking against several candidates, evaluate **all** of them and OR the `Choice` results instead of stopping at the first match: ```rust use subtle::{Choice, ConstantTimeEq}; let provided = provided.as_bytes(); let matched = configured.iter().fold(Choice::from(0), |acc, secret| { acc | secret.expose_secret().as_bytes().ct_eq(provided) }); bool::from(matched) ``` `subtle` is already a workspace dependency (transitively via `sqlx`); adding it directly to a crate pulls in no new code. Length still short-circuits in `ct_eq`; if token length must be hidden, that is a separate design decision (fixed-length tokens or a keyed hash), not a reason to fall back to `==`. See `docs/security/analysis/reports/` for disclosed, handled-report examples. ## 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 - [ ] No plain `String` fields for tokens, passwords, or private keys - [ ] `SecretString` (or an equivalent direct `secrecy` type) used for string secrets - [ ] `.expose_secret()` called only at the last moment - [ ] Secrets compared with `subtle::ConstantTimeEq`, never `==`; multiple candidates all evaluated - [ ] No `.expose_secret()` in log statements or error messages - [ ] No sensitive values in `Display` or `Debug` output - [ ] Serialized configuration tests preserve the existing secret syntax - [ ] Redaction tests assert `SecretBox<str>([REDACTED])` and never print test secret values
GitHub에서 보기