From c8d5a3aa88c61dd85d6ca66a1e17eda5bf0d28b2 Mon Sep 17 00:00:00 2001 From: liuhao1024 Date: Sat, 2 May 2026 08:46:49 +0800 Subject: [PATCH] fix(security): enable secret redaction by default Change HERMES_REDACT_SECRETS default from OFF to ON to prevent accidental credential leaks in gateway chat output and session logs. Without this fix, a vanilla Hermes deployment echoes back live API key values in Telegram/Discord chat and writes them verbatim into session JSON files. Users who need unredacted output can opt out via security.redact_secrets: false in config.yaml. Closes #17691 --- agent/redact.py | 16 +++++++++------- hermes_cli/config.py | 14 +++++++------- tests/hermes_cli/test_redact_config_bridge.py | 10 +++++----- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/agent/redact.py b/agent/redact.py index 970ad5adfb3..e7c0499426e 100644 --- a/agent/redact.py +++ b/agent/redact.py @@ -56,12 +56,13 @@ }) # Snapshot at import time so runtime env mutations (e.g. LLM-generated -# `export HERMES_REDACT_SECRETS=true`) cannot enable/disable redaction -# mid-session. OFF by default — user must opt in via -# `security.redact_secrets: true` in config.yaml (bridged to this env var -# in hermes_cli/main.py and gateway/run.py) or `HERMES_REDACT_SECRETS=true` -# in ~/.hermes/.env. -_REDACT_ENABLED = os.getenv("HERMES_REDACT_SECRETS", "").lower() in ("1", "true", "yes", "on") +# `export HERMES_REDACT_SECRETS=...`) cannot enable/disable redaction +# mid-session. ON by default to prevent accidental credential leaks in +# gateway chat output and session logs. Users who need raw output can +# opt out via `security.redact_secrets: false` in config.yaml (bridged +# to this env var in hermes_cli/main.py and gateway/run.py) or +# `HERMES_REDACT_SECRETS=false` in ~/.hermes/.env. +_REDACT_ENABLED = os.getenv("HERMES_REDACT_SECRETS", "true").lower() in ("1", "true", "yes", "on") # Known API key prefixes -- match the prefix + contiguous token chars _PREFIX_PATTERNS = [ @@ -309,7 +310,8 @@ def redact_sensitive_text(text: str, *, force: bool = False) -> str: """Apply all redaction patterns to a block of text. Safe to call on any string -- non-matching text passes through unchanged. - Disabled by default — enable via security.redact_secrets: true in config.yaml. + Enabled by default to prevent credential leaks in gateway output and logs. + Disable via security.redact_secrets: false in config.yaml or HERMES_REDACT_SECRETS=false in .env. Set force=True for safety boundaries that must never return raw secrets regardless of the user's global logging redaction preference. """ diff --git a/hermes_cli/config.py b/hermes_cli/config.py index fe989619bb9..7f3c75147e9 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -1132,7 +1132,7 @@ def _ensure_hermes_home_managed(home: Path): # Pre-exec security scanning via tirith "security": { "allow_private_urls": False, # Allow requests to private/internal IPs (for OpenWrt, proxies, VPNs) - "redact_secrets": False, + "redact_secrets": True, # ON by default — prevents credential leaks in gateway output (GH #17691) "tirith_enabled": True, "tirith_path": "tirith", "tirith_timeout": 5, @@ -3895,10 +3895,10 @@ def load_config() -> Dict[str, Any]: _SECURITY_COMMENT = """ # ── Security ────────────────────────────────────────────────────────── -# Secret redaction is OFF by default — tool output (terminal stdout, -# read_file results, web content) passes through unmodified. Set -# redact_secrets to true to mask strings that look like API keys, tokens, -# and passwords before they enter the model context and logs. +# Secret redaction is ON by default — tool output (terminal stdout, +# read_file results, web content) has API keys, tokens, and passwords +# masked before they enter the model context and logs. Set +# redact_secrets to false if you need unredacted output for debugging. # tirith pre-exec scanning is enabled by default when the tirith binary # is available. Configure via security.tirith_* keys or env vars # (TIRITH_ENABLED, TIRITH_BIN, TIRITH_TIMEOUT, TIRITH_FAIL_OPEN). @@ -3937,8 +3937,8 @@ def load_config() -> Dict[str, Any]: _COMMENTED_SECTIONS = """ # ── Security ────────────────────────────────────────────────────────── -# Secret redaction is OFF by default. Set to true to mask strings that -# look like API keys, tokens, and passwords in tool output and logs. +# Secret redaction is ON by default. Set to false to disable masking of +# strings that look like API keys, tokens, and passwords in tool output and logs. # # security: # redact_secrets: true diff --git a/tests/hermes_cli/test_redact_config_bridge.py b/tests/hermes_cli/test_redact_config_bridge.py index cf759e05384..37f7720f45d 100644 --- a/tests/hermes_cli/test_redact_config_bridge.py +++ b/tests/hermes_cli/test_redact_config_bridge.py @@ -72,11 +72,11 @@ def test_redact_secrets_false_in_config_yaml_is_honored(tmp_path): assert "ENV_VAR=false" in result.stdout -def test_redact_secrets_default_false_when_unset(tmp_path): - """Without the config key, redaction stays OFF by default. +def test_redact_secrets_default_true_when_unset(tmp_path): + """Without the config key, redaction is ON by default. - Secret redaction is opt-in — users who want it must set - `security.redact_secrets: true` explicitly (or HERMES_REDACT_SECRETS=true). + Secret redaction is opt-out — users who need raw output must set + `security.redact_secrets: false` explicitly (or HERMES_REDACT_SECRETS=false """ hermes_home = tmp_path / ".hermes" hermes_home.mkdir() @@ -107,7 +107,7 @@ def test_redact_secrets_default_false_when_unset(tmp_path): timeout=30, ) assert result.returncode == 0, f"probe failed: {result.stderr}" - assert "REDACT_ENABLED=False" in result.stdout + assert "REDACT_ENABLED=True" in result.stdout def test_redact_secrets_true_in_config_yaml_is_honored(tmp_path):