hermes - ✅(Solved) Fix Security: Redaction system fails to obscure passwords in lowercase/dotted config keys (e.g., spring.datasource.password) [1 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#16413Fetched 2026-04-28 06:53:31
View on GitHub
Comments
0
Participants
1
Timeline
5
Reactions
0
Participants
Timeline (top)
labeled ×4cross-referenced ×1

The secret redaction system in agent/redact.py fails to redact passwords and sensitive values when they appear in common configuration file formats, particularly:

  • Lowercase key names: password=secret, secret=xyz
  • Dotted namespaced keys: spring.datasource.password=secret
  • Quoted values: password='secret'
  • YAML style: password: secret

This means when terminal commands output configuration files (like cat /app/application.properties or grep results), passwords remain visible in logs and agent responses.


Root Cause

The _ENV_ASSIGN_RE regex only matches uppercase alphanumeric + underscore characters surrounding secret-indicating keywords. It uses [A-Z0-9_] which explicitly rejects lowercase letters and punctuation (., -).

Current pattern matches: UPPERCASE_KEY_NAME=value where UPPERCASE_KEY_NAME contains a secret keyword.

Failing cases:

  • spring.datasource.password=secret (lowercase + dots)
  • password=secret (lowercase)
  • app.secret.key=xyz (lowercase + dot)
  • PASSWORD=secret (uppercase) — works

Proposed fix pattern:

_ENV_ASSIGN_RE = re.compile(
    r"([A-Za-z0-9_.-](0, 100)(?:API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)[A-Za-z0-9_.-]{0,100})\s*=\s*(['\"]?)(\S+)\2",
    re.IGNORECASE,
)

Changes: Allow [A-Za-z0-9_.-] (lowercase, dots, hyphens), add re.IGNORECASE.


Fix Action

Fixed

PR fix notes

PR #16430: fix(agent): redact lowercase/dotted config keys in terminal output (#16413)

Description (problem / solution / changelog)

What does this PR do?

Fixes #16413 — the secret redaction system fails to obscure passwords in lowercase/dotted config keys.

Problem

_ENV_ASSIGN_RE in agent/redact.py only matched UPPERCASE env-style assignments ([A-Z0-9_]). Passwords in common configuration file formats slipped through unredacted:

  • password=secret
  • spring.datasource.password=secret
  • password: secret (YAML)
  • db.secret=mysecretvalue

This means when terminal commands output configuration files (e.g. cat /app/application.properties), passwords remain visible in logs and agent responses.

Fix

Two new complementary regex passes, applied after the existing uppercase _ENV_ASSIGN_RE:

  1. _CONFIG_ASSIGN_RE — matches lowercase/dotted/hyphenated config keys with no space before = (prevents false-positives on code assignments like token = await getToken()). Handles:

    • Namespaced: spring.datasource.password=…, db.secret=…, service.api-token=…
    • Bare keywords: password=…, secret=…, auth=…
    • Quoted values: password='secret', password="secret"
  2. _YAML_CONFIG_RE — matches YAML-style key: value patterns with colon-space separator, anchored to end-of-line.

Both regexes stop at &, #, and quote boundaries to avoid clobbering URL query parameters or fragments.

False-positive guards

The no-space-before-= constraint prevents matching code assignments that were previously tested as safe in #4367:

  • token = await getToken() — unchanged
  • api_key = config.get('api_key') — unchanged
  • const secret = await fetchSecret() — unchanged
  • before_tokens = response.usage.prompt_tokens — unchanged
  • model: gpt-4 — unchanged (non-secret YAML key)

Tests

  • All 75 existing tests pass without modification
  • 16 new tests added in TestLowercaseDottedConfigKeys covering:
    • All four formats from the issue (bare, dotted, quoted, YAML)
    • Hyphenated namespaces (service.api-token)
    • Full application.properties simulation
    • False-positive guards for code assignments
    • URL query parameter preservation

Labels: type/security, comp/agent, tool/terminal, P1

Changed files

  • agent/redact.py (modified, +43/-3)
  • tests/agent/test_redact.py (modified, +85/-0)

Code Example

_ENV_ASSIGN_RE = re.compile(
    r"([A-Za-z0-9_.-](0, 100)(?:API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)[A-Za-z0-9_.-]{0,100})\s*=\s*(['\"]?)(\S+)\2",
    re.IGNORECASE,
)

---

# From Hermes Agent terminal
ssh -i /tmp/1 admin@10.1.1.5 "grep 'spring.datasource' /app/application-qg.properties"

---

spring.datasource.url=jdbc:mysql://localhost/db
spring.datasource.username=admin
spring.datasource.password=MySecret123  # ← EXPOSED! Not redacted.

---

from agent.redact import redact_sensitive_text
result = redact_sensitive_text('spring.datasource.password=MySecret123')
print(result)  # prints: spring.datasource.password=MySecret123 (LEAKED!)

---

# Replace the current _ENV_ASSIGN_RE definition with:
_ENV_ASSIGN_RE = re.compile(
    r"([A-Za-z0-9_.-](0, 100)(?:API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)[A-Za-z0-9_.-]{0,100})\s*=\s*(['\"]?)(\S+)\2",
    re.IGNORECASE,
)

---

_CONFIG_KV_RE = re.compile(
    r"^\s*([A-Za-z0-9_.-]+?)\s*[=:]\s*(['"]?)(.*?)\2\s*$",
    re.MULTILINE | re.IGNORECASE
)

def _redact_config_kv(text: str) -> str:
    def _sub(m):
        key = m.group(1)
        quote = m.group(2) or ''
        value = m.group(3)
        if any(secret in key.lower() for secret in ['password', 'secret', 'key', 'token', 'passwd', 'credential']):
            return f"{key}={quote}***{quote}"
        return m.group(0)
    return _CONFIG_KV_RE.sub(_sub, text)

---

class TestConfigFileFormats:
    def test_spring_boot_password(self):
        text = "spring.datasource.password=MySecret123"
        result = redact_sensitive_text(text)
        assert "MySecret123" not in result

    def test_lowercase_password(self):
        text = "password=hunter2"
        result = redact_sensitive_text(text)
        assert "hunter2" not in result

    def test_dotted_secret_key(self):
        text = "app.secret.key=xyz"
        result = redact_sensitive_text(text)
        assert "xyz" not in result

    def test_yaml_style_password(self):
        text = "  password: hunter2"
        result = redact_sensitive_text(text)
        assert "hunter2" not in result

    def test_quoted_password(self):
        text = "spring.password='s3cr3t'"
        result = redact_sensitive_text(text)
        assert "s3cr3t" not in result

    def test_mixed_case_dotted(self):
        text = "MyApp.Configuration.Password=pass"
        result = redact_sensitive_text(text)
        assert "pass" not in result

---

# Run the test suite
pytest tests/agent/test_redact.py -v

# Manual check
python3 -c "from agent.redact import redact_sensitive_text; print(redact_sensitive_text('spring.datasource.password=MySecret123'))"
# Expected: something like 'spring.datasource.password=MySec...123' (masked)
RAW_BUFFERClick to expand / collapse

Bug Report: Redaction Bypass for Lowercase/Dotted Configuration Keys

Repository: https://github.com/NousResearch/hermes-agent Issue Type: Security Bug / Privacy Leak Severity: High (sensitive credentials exposed in logs/output) Detected By: Hermes Agent self-audit Date: 2026-04-27 06:28:48


Summary

The secret redaction system in agent/redact.py fails to redact passwords and sensitive values when they appear in common configuration file formats, particularly:

  • Lowercase key names: password=secret, secret=xyz
  • Dotted namespaced keys: spring.datasource.password=secret
  • Quoted values: password='secret'
  • YAML style: password: secret

This means when terminal commands output configuration files (like cat /app/application.properties or grep results), passwords remain visible in logs and agent responses.


Affected Component

File: agent/redact.py Function: redact_sensitive_text() Currently called from: tools/terminal_tool.py (line ~1827) after command execution


Root Cause

The _ENV_ASSIGN_RE regex only matches uppercase alphanumeric + underscore characters surrounding secret-indicating keywords. It uses [A-Z0-9_] which explicitly rejects lowercase letters and punctuation (., -).

Current pattern matches: UPPERCASE_KEY_NAME=value where UPPERCASE_KEY_NAME contains a secret keyword.

Failing cases:

  • spring.datasource.password=secret (lowercase + dots)
  • password=secret (lowercase)
  • app.secret.key=xyz (lowercase + dot)
  • PASSWORD=secret (uppercase) — works

Proposed fix pattern:

_ENV_ASSIGN_RE = re.compile(
    r"([A-Za-z0-9_.-](0, 100)(?:API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)[A-Za-z0-9_.-]{0,100})\s*=\s*(['\"]?)(\S+)\2",
    re.IGNORECASE,
)

Changes: Allow [A-Za-z0-9_.-] (lowercase, dots, hyphens), add re.IGNORECASE.


Reproduction

Command:

# From Hermes Agent terminal
ssh -i /tmp/1 [email protected] "grep 'spring.datasource' /app/application-qg.properties"

Config content:

spring.datasource.url=jdbc:mysql://localhost/db
spring.datasource.username=admin
spring.datasource.password=MySecret123  # ← EXPOSED! Not redacted.

Result: Password MySecret123 appears in full in terminal output.

Python PoC:

from agent.redact import redact_sensitive_text
result = redact_sensitive_text('spring.datasource.password=MySecret123')
print(result)  # prints: spring.datasource.password=MySecret123 (LEAKED!)

Impact

  • Credential Exposure: DB passwords, API keys, and secrets from config files are leaked to:

    • Terminal tool output (visible to LLM, may be echoed back to user)
    • Gateway logs
    • Session transcripts
    • Downstream tools processing terminal output
  • Compliance Risk: Violates security best practices and compliance (PCI-DSS, SOC2) for secret handling.

  • False Sense of Security: Users believe Hermes redacts secrets, but common config formats bypass it entirely.


Affected Formats (Verified)

FormatExampleRedacted?
Spring Boot propertiesspring.datasource.password=secret✗ No
Lowercase env-stylepassword=secret✗ No
Quoted valuepassword='secret'✗ No
YAML stylepassword: secret✗ No
Uppercase envPASSWORD=secret✓ Yes
JSON field{"password": "secret"}✓ Yes
DB URL passwordjdbc://user:pass@host✓ Yes

Proposed Fix

Option 1: Broaden _ENV_ASSIGN_RE (Recommended, minimal change)

Edit agent/redact.py:

# Replace the current _ENV_ASSIGN_RE definition with:
_ENV_ASSIGN_RE = re.compile(
    r"([A-Za-z0-9_.-](0, 100)(?:API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)[A-Za-z0-9_.-]{0,100})\s*=\s*(['\"]?)(\S+)\2",
    re.IGNORECASE,
)

Rationale: One-line fix that broadens key charset and makes matching case-insensitive.

Option 2: Dedicated config-KV pattern

Add a separate regex for line-based config files (properties, YAML, INI):

_CONFIG_KV_RE = re.compile(
    r"^\s*([A-Za-z0-9_.-]+?)\s*[=:]\s*(['"]?)(.*?)\2\s*$",
    re.MULTILINE | re.IGNORECASE
)

def _redact_config_kv(text: str) -> str:
    def _sub(m):
        key = m.group(1)
        quote = m.group(2) or ''
        value = m.group(3)
        if any(secret in key.lower() for secret in ['password', 'secret', 'key', 'token', 'passwd', 'credential']):
            return f"{key}={quote}***{quote}"
        return m.group(0)
    return _CONFIG_KV_RE.sub(_sub, text)

Then add text = _redact_config_kv(text) near the end of redact_sensitive_text().

Option 3: Both (defense-in-depth)

Apply both broadened _ENV_ASSIGN_RE and _redact_config_kv() for maximal coverage with minimal false positives.


Test Cases to Add

Add this to tests/agent/test_redact.py:

class TestConfigFileFormats:
    def test_spring_boot_password(self):
        text = "spring.datasource.password=MySecret123"
        result = redact_sensitive_text(text)
        assert "MySecret123" not in result

    def test_lowercase_password(self):
        text = "password=hunter2"
        result = redact_sensitive_text(text)
        assert "hunter2" not in result

    def test_dotted_secret_key(self):
        text = "app.secret.key=xyz"
        result = redact_sensitive_text(text)
        assert "xyz" not in result

    def test_yaml_style_password(self):
        text = "  password: hunter2"
        result = redact_sensitive_text(text)
        assert "hunter2" not in result

    def test_quoted_password(self):
        text = "spring.password='s3cr3t'"
        result = redact_sensitive_text(text)
        assert "s3cr3t" not in result

    def test_mixed_case_dotted(self):
        text = "MyApp.Configuration.Password=pass"
        result = redact_sensitive_text(text)
        assert "pass" not in result

Important: Must ensure existing regression tests still pass:

  • test_lowercase_python_variable_token_unchanged
  • test_lowercase_python_variable_api_key_unchanged
  • test_typescript_await_token_unchanged

Verification (Post-Fix)

# Run the test suite
pytest tests/agent/test_redact.py -v

# Manual check
python3 -c "from agent.redact import redact_sensitive_text; print(redact_sensitive_text('spring.datasource.password=MySecret123'))"
# Expected: something like 'spring.datasource.password=MySec...123' (masked)

Suggested Labels

  • security
  • bug
  • privacy
  • high-priority
  • redaction
  • credentials

Environment

  • Project: Hermes Agent
  • Version: 0.11.0
  • Affected file: agent/redact.py (340 lines)
  • Usage site: tools/terminal_tool.py:1826

Original Report: User ran SSH command to grep Spring Boot config on remote host. Passwords like spring.datasource.password were returned in full, unredacted, violating the agent's security guarantee.


This bug report was generated by Hermes Agent diagnostic analysis.

extent analysis

TL;DR

The most likely fix is to update the _ENV_ASSIGN_RE regex pattern in agent/redact.py to make it case-insensitive and allow for lowercase letters, dots, and hyphens in key names.

Guidance

  • Verify the issue by running the provided test cases and checking the output of the redact_sensitive_text function.
  • Update the _ENV_ASSIGN_RE pattern to the proposed fix: _ENV_ASSIGN_RE = re.compile(r"([A-Za-z0-9_.-]{0,100}(?:API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)[A-Za-z0-9_.-]{0,100})\s*=\s*(['\"]?)(\S+)\2", re.IGNORECASE).
  • Consider adding a separate regex for line-based config files (properties, YAML, INI) as a defense-in-depth measure.
  • Run the test suite and manual checks to verify the fix.

Example

The proposed fix pattern can be applied as follows:

_ENV_ASSIGN_RE = re.compile(
    r"([A-Za-z0-9_.-]{0,100}(?:API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)[A-Za-z0-9_.-]{0,100})\s*=\s*(['\"]?)(\S+)\2",
    re.IGNORECASE,
)

This pattern allows for lowercase letters, dots, and hyphens in key names and makes the matching case-insensitive.

Notes

The proposed fix assumes that the issue is solely due to the _ENV_ASSIGN_RE pattern being too restrictive. Additional testing and verification may be necessary to ensure that the fix does not introduce any unintended consequences.

Recommendation

Apply the proposed fix by updating the _ENV_ASSIGN_RE pattern, as it is a minimal change that addresses the issue. This fix should

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: Redaction system fails to obscure passwords in lowercase/dotted config keys (e.g., spring.datasource.password) [1 pull requests, 1 participants]