hermes - ✅(Solved) Fix security.redact_secrets redacts values before passing to tools, breaking Bitwarden CLI workflows [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#16700Fetched 2026-04-28 06:51:28
View on GitHub
Comments
0
Participants
1
Timeline
9
Reactions
0
Participants
Timeline (top)
labeled ×5cross-referenced ×3unlabeled ×1

Fix Action

Workaround

hermes config set security.redact_secrets false — but this removes all redaction protection.

PR fix notes

PR #16849: fix: add security.display_redaction_only to keep secrets usable in tools

Description (problem / solution / changelog)

Summary

security.redact_secrets was redacting credential values in **both** tool results (file reads, terminal output) and display/logging paths. When the LLM received partially-masked values (sk-a***c12) from tool results, it could not use them in subsequent commands — breaking:

  • htpasswd and similar tools that pass passwords via command-line arguments (#16843)
  • Bitwarden CLI workflows where Hermes retrieves a secret and passes it to a script (#16700)

Changes

New config: security.display_redaction_only

SettingEffect
false (default)Current behavior — redact everywhere (backward compatible)
trueRedact only in chat display + logs; real values pass through to tools
security:
  redact_secrets: true
  display_redaction_only: true

How it works

  • redact_sensitive_text() — used by tool results (file reads, terminal output, code execution). Respects the new flag: skips patterns when display_redaction_only=true so the LLM gets real values.
  • redact_for_display() — new function that **always** redacts, used by chat output (send_message), logs (RedactingFormatter), and context summaries. Never skips even when the flag is active.
  • All entry points — CLI, gateway, and hermes_cli all bridge the new config to the env var.

Testing

  • tests/agent/test_redact.py: 75 tests, all passing ✅

Closes #16843 Closes #16700

Changed files

  • .github/workflows/contributor-check.yml (removed, +0/-73)
  • .github/workflows/deploy-site.yml (removed, +0/-87)
  • .github/workflows/docker-publish.yml (removed, +0/-99)
  • .github/workflows/docs-site-checks.yml (removed, +0/-48)
  • .github/workflows/nix-lockfile-check.yml (removed, +0/-68)
  • .github/workflows/nix-lockfile-fix.yml (removed, +0/-149)
  • .github/workflows/nix.yml (removed, +0/-33)
  • .github/workflows/skills-index.yml (removed, +0/-101)
  • .github/workflows/supply-chain-audit.yml (removed, +0/-139)
  • .github/workflows/tests.yml (removed, +0/-82)
  • agent/context_compressor.py (modified, +4/-4)
  • agent/redact.py (modified, +55/-16)
  • cli.py (modified, +3/-0)
  • cron/scheduler.py (modified, +3/-3)
  • gateway/run.py (modified, +3/-0)
  • hermes_cli/config.py (modified, +3/-0)
  • hermes_cli/main.py (modified, +3/-0)
  • tools/send_message_tool.py (modified, +1/-1)

PR #16851: fix(file-safety): refuse read_file on credential paths (SSH keys, .env, …) (#16809)

Description (problem / solution / changelog)

Summary

  • Add a read-side counterpart to build_write_denied_paths() in agent/file_safety.py and have read_file_tool consult it before performing the read.
  • Blocks SSH private keys, the active HERMES_HOME .env, shell history, and credential directories (.ssh, .aws, .gnupg, .kube, .docker, .azure, .config/gh) from being read by the model.

The bug

read_file had no sensitive-path deny list. The reproduction in #16809 — and on a fresh checkout of main — succeeds today:

read_file("~/.ssh/id_ed25519")  # → full SSH private key
read_file("~/.hermes/.env")     # → full API keys
read_file("~/.zsh_history")     # → shell history

The only defense was pattern-based output redaction via redact_sensitive_text(), and that path was made off by default in 73753417 ("feat(security): make secret redaction off by default", #16794) and uses a module-level _REDACT_ENABLED snapshotted at import time — so config-driven opt-in is fragile, and well-known credential prefixes are the only thing recognised even when redaction is on.

Write-side protection exists (build_write_denied_paths + _check_sensitive_path) but has no read-side counterpart.

The fix

Add an access-control floor for high-value credential files where leakage is unrecoverable.

agent/file_safety.py gains three new helpers:

  • build_read_denied_paths(home) — exact paths: SSH keys (id_rsa/id_ed25519/id_ecdsa/id_dsa/authorized_keys/config), .netrc, .pgpass, .npmrc, .pypirc, the active HERMES_HOME / .env (resolved through get_hermes_home() so profile-aware), and shell history (.bash_history, .zsh_history, .psql_history).
  • build_read_denied_prefixes(home) — directory prefixes: ~/.ssh, ~/.aws, ~/.gnupg, ~/.kube, ~/.docker, ~/.azure, ~/.config/gh.
  • is_read_denied(path) — symmetric helper. Resolves through os.path.realpath before checking, so symlink shims are caught.

tools/file_tools.pyread_file_tool calls is_read_denied(path) after the existing device / binary / Hermes-internal guards (so denied paths can't return cached content from the dedup tracker either) and returns a structured error message pointing the model at the terminal tool when a credential read is genuinely intentional.

Intentional scope decisions

  • /etc/passwd is not blocked — world-readable user metadata, not a secret. /etc/shadow is already unreadable to non-root callers, so blocking it would be cosmetic.
  • Shell config (.bashrc, .zshrc, .profile, …) is write-denied but read-allowed: debugging PATH issues / alias setup is a legitimate case, and inline export FOO=key strings remain covered by redact_sensitive_text when security.redact_secrets is on.
  • The escape hatch for genuinely intentional reads (e.g. an agent that wants to inspect its own SSH config) is the existing terminal tool, which gates on user approval — exactly the layer this guard is designed to defer to.

Test plan

  • Focusedtests/tools/test_read_deny.py (new): 26 tests covering exact paths, prefixes, allowed paths, and end-to-end read_file_tool denial. All pass.
  • Adjacenttests/tools/test_write_deny.py, tests/tools/test_file_read_guards.py, tests/tools/test_file_operations.py, tests/tools/test_read_loop_detection.py, tests/tools/test_file_write_safety.py, tests/agent/test_copilot_acp_client.py. The 6 test_file_read_guards.py::TestFileDedup/TestWriteInvalidatesDedup failures and the 1 test_copilot_acp_client.py::test_read_text_file_redacts_sensitive_content failure reproduce on clean origin/main (8081425a1) — they're pre-existing macOS /private/var write-deny collision and the redaction-off-by-default regression respectively, unrelated to this change.
  • Regression guard — verified directly:
    Without guard, contents readable: True
    With guard, error returned: True
    With guard, secret leaked: False

Related

  • Fixes #16809.
  • Companion to the read-side concerns surfaced in #7826 (v0.8.0 audit) and adjacent to the redaction work in #16794 / #16700 / #16843 / #16849 — those address output-side redaction; this is the access-control floor underneath.

Changed files

  • agent/file_safety.py (modified, +69/-0)
  • tests/tools/test_read_deny.py (added, +163/-0)
  • tools/file_tools.py (modified, +17/-0)
RAW_BUFFERClick to expand / collapse

Bug

When security.redact_secrets is enabled, Hermes redacts secret-looking values not just in UI output but before passing them to tools (e.g. the terminal tool). This breaks workflows where Hermes retrieves a secret from Bitwarden CLI and passes it directly to a script or environment variable.

Expected behaviour

security.redact_secrets should only redact values in the displayed response — what the user sees in the TUI, WhatsApp, etc. The actual value should be passed through unmodified when used internally (tool calls, env vars, script arguments).

Actual behaviour

Hermes retrieves a secret from Bitwarden (e.g. an API key stored as a plain text field, type 0), but the redacted display value (AIzaSy...TcVk) is what gets passed to terminal commands, making the secret unusable.

Workaround

hermes config set security.redact_secrets false — but this removes all redaction protection.

Steps to reproduce

  1. Store an API key in Bitwarden as a custom field (type 0 / plain text)
  2. Enable security.redact_secrets (default)
  3. Ask Hermes to retrieve the key from Bitwarden and pass it to a script
  4. The script receives the truncated/redacted string instead of the real value

Impact

Makes Bitwarden-based secret management (the recommended alternative to .env files) non-functional when redaction is enabled. Users are forced to disable redaction entirely to use secrets in automations.

extent analysis

TL;DR

Disable security.redact_secrets or modify the code to only redact values in displayed responses, not internal tool calls.

Guidance

  • Review the security.redact_secrets implementation to ensure it only redacts values in UI output, not when passing them to tools or setting environment variables.
  • Consider adding a new configuration option to control redaction behavior for internal tool calls separately from UI output.
  • Verify that the terminal tool and other internal tools receive the unredacted secret values when security.redact_secrets is enabled.
  • Test the workaround hermes config set security.redact_secrets false to confirm it resolves the issue, but note that it removes all redaction protection.

Example

No code snippet is provided as the issue does not include specific code details.

Notes

The current implementation of security.redact_secrets may not be suitable for all use cases, and a more fine-grained control over redaction behavior may be necessary.

Recommendation

Apply workaround: hermes config set security.redact_secrets false, as it is the only provided solution that resolves the issue, although it removes all redaction protection. A better solution would be to modify the code to only redact values in displayed responses.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix security.redact_secrets redacts values before passing to tools, breaking Bitwarden CLI workflows [2 pull requests, 1 participants]