Skip to content

fix(secret): Add custom derive Debug for SecretResolver to prevent secret leakage with {:?}#1322

Open
alangou wants to merge 1 commit into
NVIDIA:mainfrom
alangou:alangou/os-72-fsr-020-prevent-secret-leakage-via-debug-derive-on
Open

fix(secret): Add custom derive Debug for SecretResolver to prevent secret leakage with {:?}#1322
alangou wants to merge 1 commit into
NVIDIA:mainfrom
alangou:alangou/os-72-fsr-020-prevent-secret-leakage-via-debug-derive-on

Conversation

@alangou
Copy link
Copy Markdown

@alangou alangou commented May 12, 2026

Summary

SecretResolver previously used #[derive(Debug, ...)], which expands the internal HashMap<String, String> and prints both the placeholder keys (openshell:resolve:env:<ENV_VAR>:<revision>) and the resolved secret values. Any accidental {:?} in a tracing call — or a derived Debug on a struct that owns a SecretResolver — would write provider credentials to logs. This PR replaces the auto-derived impl with a hand-rolled one that exposes only the count of registered placeholders.

Related Issue

No public GitHub issue — internal finding.

Changes

  • Drop Debug from the #[derive(...)] on SecretResolver in crates/openshell-sandbox/src/secrets.rs.
  • Add a manual fmt::Debug impl that prints SecretResolver { placeholders: <n>, .. } via finish_non_exhaustive() — no keys, no values, ever.
  • Add unit tests:
    • debug_format_does_not_leak_secret_values checks both {:?} and {:#?} of a populated resolver and asserts that secret values, placeholder keys (provider env-var names), and the openshell:resolve:env: token are all absent — while the placeholder count is still present.
    • debug_format_of_empty_resolver_is_safe covers the empty case.

Design Note: Why Only the Count

I deliberately picked the most defensive Debug shape — just the number of registered placeholders, nothing else.

OpenShell logs may be shipped through channels I do not control end-to-end: unencrypted transports, third-party aggregators, long-term storage on operator machines. Any Debug output has to be treated as potentially externalized. Under that threat model:

  • Leaking the secret values is obviously unacceptable.
  • Leaking the placeholder keys (e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY, DB_PASSWORD) is still a real information disclosure — it tells an attacker which providers are configured and which credentials are worth phishing or stealing elsewhere.
  • Leaking only the count reveals essentially nothing about the nature of the secrets stored or in use, while keeping Debug useful enough to identify the struct in traces.

Alternatives considered and rejected:

  • Printing redacted keys (ANTHROPIC_API_KEY=***) — still leaks which providers are in play.
  • Hashing keys — same disclosure surface in practice (small, enumerable set of provider env-var names).
  • Removing Debug entirely — would break derived Debug on every struct that owns a SecretResolver, which is the path of least resistance for the very leakage I am trying to prevent.

Testing

  • mise run pre-commit passes (run inside the dev VM — NixOS host can't run the mise toolchain directly)
  • Unit tests added (debug_format_does_not_leak_secret_values, debug_format_of_empty_resolver_is_safe)
  • E2E tests added/updated (not applicable — formatting-only change, no behavioral surface)

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (not applicable — internal struct formatting)

…cret leakage with {:?}

Signed-off-by: Adrien Langou <alangou@nvidia.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 12, 2026

All contributors have signed the DCO ✍️ ✅
Posted by the DCO Assistant Lite bot.

@alangou
Copy link
Copy Markdown
Author

alangou commented May 12, 2026

I have read the DCO document and I hereby sign the DCO.

@alangou
Copy link
Copy Markdown
Author

alangou commented May 12, 2026

recheck

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant