fix: add security.display_redaction_only to keep secrets usable in tools - #16849
fix: add security.display_redaction_only to keep secrets usable in tools#16849HiddenPuppy wants to merge 2 commits into
Conversation
Root cause: redact_sensitive_text() was applied uniformly to 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 Bitwarden CLI workflows (NousResearch#16700) and terminal credential usage (NousResearch#16843). Fix: - Add security.display_redaction_only config flag - redact_sensitive_text() skips patterns when flag is active (tool results) - New redact_for_display() always redacts (chat output, logs) - RedactingFormatter uses redact_for_display (always redact logs) - All gateway/platform/cli entry points bridge the new flag When display_redaction_only=true: ✓ LLM sees real values in tool results ✓ User sees redacted values in chat ✓ Logs are always redacted Closes NousResearch#16843 Closes NousResearch#16700
plcunha
left a comment
There was a problem hiding this comment.
Review — PR #16849: security.display_redaction_only
Author: @HiddenPuppy
Reviewer: PooL / João Vitor Cunha
Verdict: ✅ Approach is correct — the right fix for this class of bugs. Needs rebase + minor cleanup before merge.
✅ What this PR gets right
The display_redaction_only flag is exactly the right architectural fix for the redaction-usability tension:
redact_sensitive_text()— whendisplay_redaction_only=true, becomes a no-op. Tool results (terminal, file reads, execute_code) pass real credential values to the LLM → it can use them in subsequent commands.redact_for_display()— ALWAYS redacts, used in chat messages, logs, context summaries, cron output. User never sees secrets.- Backward compatible — default
falsepreserves existing behavior for users who want aggressive redaction.
This directly fixes:
- #43083 (P1, open) — "Passwords get replaced by *** but model reads back its own conversation history and fails on second tool call"
- #16843 — "Secret redaction breaks functional credential use in terminal commands"
- #16700 — "Bitwarden CLI workflows where Hermes retrieves a secret and passes it to a script"
I hit #43083 in production today (2026-06-22): trying to query a PostgreSQL DB via PGPASSWORD=*** (the real password pool2026db was redacted to *** by the terminal tool output). The agent retried 8+ times in a loop, unable to see that the password was being masked.
🔴 Issues that need fixing
1. Workflow file deletions (903 lines)
The commit e71a2ad1f deletes all .github/workflows/*.yml files:
.github/workflows/contributor-check.yml | 73 ------
.github/workflows/deploy-site.yml | 87 ------
.github/workflows/docker-publish.yml | 99 ------
.github/workflows/docs-site-checks.yml | 48 ------
.github/workflows/nix-lockfile-check.yml | 68 ------
.github/workflows/nix-lockfile-fix.yml | 149 ------
.github/workflows/nix.yml | 33 ------
.github/workflows/skills-index.yml | 101 ------
.github/workflows/supply-chain-audit.yml | 139 ------
.github/workflows/tests.yml | 82 ------
These must be reverted. Removing CI/CD, tests, supply-chain audit, and lockfile checks would break the repo infrastructure. Likely an accident from forking/pushing to a personal fork.
2. Branch is stale — needs rebase onto current main
The PR branch is based on 8081425a1 (from ~April 2026). Since then, the codebase has evolved significantly:
agent/chat_completion_helpers.pynow exists on main (lines 874-1027) and usesredact_sensitive_text()— this is the exact codepath that causes #43083- The PR currently deletes this file (it was not in the old base)
- Multiple redaction-related fixes have landed on main (
1f28b1a9b,6f0ecf37d,3b56d3a29, etc.)
Request: Rebase onto main and verify chat_completion_helpers.py correctly inherits the display_redaction_only behavior. Since redact_sensitive_text() is the function being modified, it should work automatically — but this needs to be tested against the current code.
3. send_message_tool.py aliasing is confusing
from agent.redact import redact_for_display as redact_sensitive_textAliasing redact_for_display as redact_sensitive_text works but is semantically misleading. The call sites in the file use redact_sensitive_text(content) but actually get display-only redaction. This is the correct behavior (send_message output IS display), but the alias hides intent.
Suggestion: Rename the local usage at the call sites instead:
from agent.redact import redact_for_display
# then at call sites:
content = redact_for_display(content)4. Test coverage for #43083 scenario
The existing 75 tests are solid. Consider adding a regression test specifically for the #43083 pattern:
- Redacted tool arguments in conversation history
- Model receives real values via tools despite display redaction
- Verify no
***leakage into tool execution arguments whendisplay_redaction_only=true
🟢 Confirmed working
- Config bridging at all 3 entry points (cli, gateway, hermes_cli) ✅
cron/scheduler.pyswitch toredact_for_display✅context_compressor.pyswitch toredact_for_display✅RedactingFormatterswitch toredact_for_display✅- Default config template updated ✅
📋 Summary
| Item | Status |
|---|---|
Core approach (display_redaction_only flag) |
✅ Approved |
| Workflow file deletions | 🔴 Must revert |
Rebase onto current main |
🔴 Required |
send_message_tool.py aliasing |
🟡 Cleanup suggested |
| Test coverage | 🟡 Regression test for #43083 suggested |
This is the right fix — once rebased and cleaned up, this should land. The production impact is real (89+ repeated failures in our logs from a single session hitting this).
teknium1
left a comment
There was a problem hiding this comment.
Thanks for addressing the credential-usability problem. Current main already fixes the replay corruption that turned later credential-bearing tool calls into ***: agent/chat_completion_helpers.py:1142-1158 preserves tool-call arguments verbatim, from bbe1bf404.
Problems
- The submitted diff deletes ten unrelated CI/CD workflow files, including
.github/workflows/tests.ymlandsupply-chain-audit.yml; these deletions must not be part of a redaction change. - The proposed global
redact_sensitive_text()bypass would need a current-main boundary audit. Current user-facing approval copies still call that helper attools/approval.py:1682-1688and2119-2126, and TUI quick-command output does so attui_gateway/server.py:11830-11839. - No tests are included. The config is read before import-time redaction state is initialized; see the existing fresh-process bridge coverage in
tests/hermes_cli/test_redact_config_bridge.py:23-158.
Suggested changes
- Salvage only the intended redaction work, excluding workflow deletions.
- Rework it around current forced display/log safety boundaries and add end-to-end tests for raw model-visible tool output versus masked chat, logs, and approvals.
Automated hermes-sweeper review.
| @@ -1,82 +0,0 @@ | |||
| name: Tests | |||
There was a problem hiding this comment.
Blocking: this redaction PR must not delete the repository test workflow. Please exclude this unrelated workflow deletion, along with the other CI/CD workflow removals in this commit.
|
Correction to my June 22 review after re-auditing this PR against current The primary replay bug from #43083 is already fixed on The sweeper's remaining concerns are valid:
Recommendation: close this PR as stale/implemented for the original bug. If a display-only mode is still desired, it should be a new current-main design that separates model-visible raw tool data from forced-redaction display/log/approval surfaces, with end-to-end tests for every boundary. Simply rebasing this global bypass would risk exposing credentials to users and logs. Apologies for the overly positive architectural verdict in my earlier review; it predated the merged #43083 fix and did not account for all current display boundaries. |
Summary
security.redact_secretswas 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:htpasswdand similar tools that pass passwords via command-line arguments (Secret redaction breaks functional credential use in terminal commands #16843)Changes
New config:
security.display_redaction_onlyfalse(default)trueHow it works
redact_sensitive_text()— used by tool results (file reads, terminal output, code execution). Respects the new flag: skips patterns whendisplay_redaction_only=trueso 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.Testing
tests/agent/test_redact.py: 75 tests, all passing ✅Closes #16843
Closes #16700