Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions agent/redact.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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.
"""
Expand Down
14 changes: 7 additions & 7 deletions hermes_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions tests/hermes_cli/test_redact_config_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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):
Expand Down