Skip to content

security: leak-proof RedactedSecret newtype for all in-memory secrets#51

Closed
muchiny wants to merge 19 commits into
mainfrom
security/redacted-secret-newtype
Closed

security: leak-proof RedactedSecret newtype for all in-memory secrets#51
muchiny wants to merge 19 commits into
mainfrom
security/redacted-secret-newtype

Conversation

@muchiny

@muchiny muchiny commented May 30, 2026

Copy link
Copy Markdown
Owner

Contexte

Audit du filtrage/protection des mots de passe (demande utilisateur). L'audit a confirmé 4 failles réelles (vérifiées au niveau du crate zeroize-1.8.2) + une 5ᵉ classe identique trouvée à la revue finale.

Findings corrigés

ID Faille Preuve
F1 Fuite via Debug zeroize::Zeroizing dérive/forwarde Debug (lib.rs:622) → tout {:?} sur un struct config imprimait le mot de passe en clair.
F2 Fuite via Serialize Zeroizing impl Serialize en forward (lib.rs:725) → sérialiser un Config émettait les secrets en clair.
F3 Token AWX en clair AwxConfig::token: String — ni zeroizé ni redacté.
F4 Bearer opaque Tokens Authorization: Bearer non-JWT seulement attrapés par l'entropy detector (désactivable).
F1-bis Même classe sur args DB/vault db_password (×3) + vault data en Zeroizing<String> + derive(Debug) (fuite latente).

Solution

Newtype in-crate RedactedSecret (src/config/secret.rs) qui wrappe Zeroizing<String> :

  • Debug / Display / Serialize écrits à la main → "[REDACTED]" (fuite impossible by construction)
  • Deserialize transparent (lit une string en clair — config YAML inchangée)
  • Deref<Target = str> + as_str() = unique frontière auditée où le plaintext est exposé (doc explicite)
  • Pas de PartialEq/Hash (rationale timing side-channel documentée)
  • zeroize-on-drop conservé

Les 10 secrets mémoire migrés vers ce type (6 config + token AWX + 3 db_password + vault data). Pattern Bearer ajouté au sanitizer (ordonné après JWT pour préserver [JWT_TOKEN_REDACTED]).

Rejeté (volontairement)

  • Crate secrecy — nouvelle dépendance sur projet air-gapped, gros blast radius (.expose_secret() partout). Le newtype in-crate fait le même travail sans dette.

Tests / vérif

  • TDD strict, chaque secret a un test "ne fuit pas via Debug/Serialize".
  • Frontière escape-hatch vérifiée : aucun .as_str() ne coule dans un macro de log/format — uniquement vers auth/transport.
  • make ci vert (fmt-check, clippy -D warnings, suite nextest complète, audit, typos).

Plan

docs/superpowers/plans/2026-05-30-redacted-secret-newtype.md

🤖 Generated with Claude Code

muchiny and others added 19 commits May 30, 2026 22:03
cargo update — patch/minor bumps within existing ranges:
tokio 1.52.3, russh 0.60.3, russh-sftp 2.3.0, reqwest 0.13.4,
tower-http 0.6.11, aws-config 1.8.17, aws-sdk-ssm 1.111, clap_complete
4.6.5, const-hex 1.19.1, mimalloc 0.1.52, serde_json 1.0.150,
tokio-socks 0.5.3, uuid 1.23.2, filetime 0.2.29 (dev).
crates.io 1.1.2 ships the reqwest feature fix the local fork carried;
[patch.crates-io] override removed. Resolves cargo-outdated 'Removed' status.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
opentelemetry/_sdk/-otlp 0.32, tracing-opentelemetry 0.33 (must match).
otel feature only; trace/metric/log APIs stable since 0.30.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
No source changes needed — TextDiff::from_lines / ChangeTag API is
stable across the major; the break was MSRV-only. Resolves to
similar v3.1.1.
Direct API (Sha256::new/update/finalize/Digest) unchanged; 0.11 moves
to digest 0.11 / hybrid-array output. sftp checksum + audit-recording
hash tests confirm byte-identical output.

recording.rs: replace format!("{:x}", h.finalize()) with
const_hex::encode(h.finalize().as_slice()) — LowerHex no longer
implemented for hybrid_array::Array in digest 0.11.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OAuth/JWT validator (http feature). v10 introduces a pluggable
CryptoProvider trait system as a breaking change; enable the
rust_crypto + use_pem features to satisfy it with pure-Rust backends.
All Validation/DecodingKey call-sites in oauth.rs are API-compatible
(identical signatures in v10). No changes to oauth.rs required.

explicit set_required_spec_claims + set_issuer/set_audience preserved
(FIND-007). Full oauth negative-case suite verified: 20/20 tests pass,
all rejection cases still rejecting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Core SSH transport. Only Cargo.toml + Cargo.lock required changes —
all adapter code (client/session/connector/sftp/known_hosts) compiled
without modification. Algorithm preferences (kex/cipher/mac constants,
Preferred struct fields, Limits::new) and host-key verification
(check_server_key returns Result<bool>) are unchanged in 0.61 API.
Mock harness green: 3965 passed, 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hardened YAML wrapper (domain/yaml.rs) Budget/Options preserved;
DoS budget-limit tests verified. Pin kept as '=' (0.0.x).

Migration notes:
- Direct struct construction of Budget/Options deprecated since 0.0.23;
  migrated hardened_options() to budget!/options! macros (no behaviour change).
- All five original limits retained at same values: max_reader_input_bytes,
  max_anchors (100), max_aliases (1 000), max_depth (50), max_nodes (10 000).
- Added max_total_scalar_bytes = MAX_YAML_BYTES (1 MiB) to cap the new
  scalar-amplification vector introduced in 0.0.22+.
- New dependency: granit-parser 0.0.3 (pulled by serde-saphyr 0.0.27).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ates

- RUSTSEC-2026-0098/0099/0104 (rustls-webpki) are no longer triggered by
  the updated dep graph; moved from active ignore to historical comment in
  deny.toml. Only RUSTSEC-2023-0071 (Marvin/RSA via russh) remains active.
- CLAUDE.md Known Advisories section updated to reflect the new state (1
  active, 6 historical).
- Fix import order regression in src/security/recording.rs (serde vs
  const_hex alphabetical ordering after sha2 0.11 bump).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fixes F1/F2 leak)

Replace bare Zeroizing<String> secret fields (sudo_password, SOCKS password,
AuthConfig Key passphrase / Password / Ntlm password) with the leak-proof
RedactedSecret newtype so Debug/Display/Serialize emit [REDACTED] instead of
the plaintext (F1/F2). Add a Debug-does-not-leak regression test and update
serialization/deserialization tests to assert redaction. Fix the SOCKS5
connect_with_password call site and all winrm/psrp/cli/ssh_status test
construction sites to build RedactedSecret.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ret + fmt

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (no Debug leak)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation rust security tools domain labels May 30, 2026
@muchiny

muchiny commented May 30, 2026

Copy link
Copy Markdown
Owner Author

Pushé directement sur main (fast-forward) à la demande, pas de merge via PR.

@muchiny muchiny closed this May 30, 2026
@muchiny muchiny deleted the security/redacted-secret-newtype branch May 30, 2026 23:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation domain rust security tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant