fix(security): mask secret values in secret-source status lines - #77012
fix(security): mask secret values in secret-source status lines#77012andrexibiza wants to merge 2 commits into
Conversation
The startup status block in env_loader prints error, remediation-hint, warning, and conflict lines straight to stderr. The merged secret-name suppression (NousResearch#60295/NousResearch#69054) removed the applied-name list, but a source error or warning that echoes a secret VALUE would still print it. This change routes all four status-line types through a value masker: - agent.redact.mask_known_secret_values() masks the exact values of credential-named env vars (opaque tokens with no vendor prefix) - env_loader._mask_secret_text() additionally masks every value applied from external secret sources this process (Bitwarden/1Password), which is the authoritative set for what the status line is about Why this matters to users: secret names already stopped being printed; now secret values cannot leak through error or warning text either. If a backend echoes a key back in an error message, you see the diagnostic without the key. Tests: 67 passed across env_loader + secret-sources suites (2 new regression tests: value in warning masked, value in error masked) plus the existing 76-test redact suite.
|
Thanks for covering the direct stderr status path; the premise holds on current main: Problems
Suggested changes
Automated hermes-sweeper review. |
…lues Addresses hermes-sweeper review on NousResearch#77012: - _mask_secret_text() now takes home_path and reads only that resolved home's snapshot from _SECRET_SOURCE_VALUES_BY_HOME. Status output for one profile no longer depends on another profile's secret values. - Drop the 6-character minimum-length filter on snapshot values: a short external-source *_PASSWORD / *_TOKEN echoed by a backend would have leaked through the status line. Adds two regression tests: a short-value warning echo through the real apply path, and a cross-home isolation test asserting each home's status line masks only its own snapshot values.
|
Fixed in
Regression tests added to
Validation: |
…cription, skill test Addresses teknium1's review on NousResearch#77097: 1. 'Not true on main' — the security contract is now explicitly scoped as implemented by the secrets-exfiltration hardening series (NousResearch#77008/NousResearch#77012/NousResearch#77020/NousResearch#77027/NousResearch#77031/NousResearch#77039). The docs state current main behavior plainly (plaintext bws_cache.json read/written when encryption disabled, default false) and keep the rotation instruction mandatory today, since that exposure already exists on main. The posture framing stays — this eliminates an entire vulnerability class — but the claim is now sequenced truthfully. 2. Skill description shortened to 53 chars, one sentence, ends with a period (AGENTS.md hardline). 3. tests/skills/test_bitwarden_secrets_skill.py added: validates frontmatter, description length, required sections, user-only rotation + clipboard discipline, honest series scoping (no claim the gate test is on main), and docs-page metadata consistency. 4. Clipboard discipline added to rotation instructions (docs + skill): create token, copy to clipboard, paste into terminal, save nowhere in between.
SummaryThree PRs address related secret-value disclosure paths: #77012 masks external-secret-source status errors, hints, warnings, and conflicts with home-scoped snapshots; #77014 combined an earlier status-path implementation with formatter-wide masking; and #77020 independently adds formatter-wide masking for opaque credential environment values while preserving the global opt-out. Related pull requests
Duplicates#77014 substantially overlaps #77012 on status-line masking and #77020 on formatter masking, but #77012 and #77020 cover distinct output paths and are not duplicates of each other. Suggested consolidationKeep #77012 open with the salvage path of its home-scoped status-output masking and short-value/cross-home tests, and keep #77020 open with the salvage path of its opt-out-aware formatter masking and disabled-setting test. Retain already-closed #77014 as the superseded combined precursor; its two substantive parts are covered separately by #77012 and #77020. Cross-PR triage: Reviewed 3 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 28 kB of PR diffs, 7 kB of issue/PR text, 4 kB of discussion (6 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
|
Both sweeper findings were addressed in the current head (35c592a): (1) home-scoped masking — |
Related #60295 #69054
What changed and why
The startup status block in
hermes_cli/env_loader.pyprints secret-source error, remediation-hint, warning, and conflict lines straight to stderr. The merged secret-name suppression (#60295 / #69054) removed the applied-name list from the status line — but a source error or warning that echoes a secret value (e.g. a backend quoting the key back) would still print that value.This PR routes all four status-line types through a value masker:
agent/redact.py::mask_known_secret_values()— masks the exact values of credential-named env vars (*_API_KEY,*_TOKEN,*_SECRET,*_KEY,*_PASSWORD), catching opaque tokens that carry no recognizable vendor prefix and would slip past the shape-based regex passes.hermes_cli/env_loader.py::_mask_secret_text()— additionally masks every value applied from external secret sources this process (Bitwarden / 1Password / command sources), which is the authoritative set for what the status line is about.Why this matters to you as a user
Secret names already stopped being printed at startup; now secret values cannot leak through error or warning text either. If a backend echoes your key back inside an error message, you still see the diagnostic — just without the key in it. This closes the value half of the same disclosure class the name fix addressed.
Reproduction steps (current behavior on
main)BWS_ACCESS_TOKEN.secrets.bitwarden.access_token_envat a token whose value contains a recognizable prefix, or force any source error/warning that quotes a credential value.Current:
Bitwarden Secrets Manager: <error text containing the raw secret value>Expected: same diagnostic with the value replaced by
***.How to test
scripts/run_tests.sh tests/test_env_loader_secret_sources.py→ 20 passed (2 new regression tests: value-in-warning masked, value-in-error masked while the diagnostic is preserved).scripts/run_tests.sh tests/test_env_loader.py tests/test_env_loader_applied_homes.py tests/secret_sources/ tests/agent/test_redact.py tests/hermes_cli/test_redact_config_bridge.py→ 143 passed, 0 failed.Platforms tested
scripts/run_tests.shrunner.git diff --checkclean;check-windows-footguns.pyclean on changed files.Related
Part of #77162
Part of #77165