Skip to content

read_file has no sensitive-path deny list — SSH keys, .env, shell history readable without restriction #16809

@MeasyZero

Description

@MeasyZero

Summary

read_file has no sensitive-path deny list. SSH private keys, .env credential files, and shell history are all freely readable. The only line of defense — output redaction via redact_sensitive_text() — was just made off by default (commit 73753417, Apr 27 2026) and uses a module-level _REDACT_ENABLED flag captured at import time, making config-driven opt-in fragile.

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


How to Reproduce

# Terminal:
read_file("~/.ssh/id_ed25519")      # → full SSH private key
read_file("~/.hermes/.env")          # → full API keys
read_file("~/.zsh_history")          # → shell history
read_file("/etc/passwd")             # → user database (readable on macOS)

All return unredacted content by default as of commit 73753417.


Existing defenses that don't cover this

Defense Status for read
build_write_denied_paths() in agent/file_safety.py — blocks writes to SSH keys, .env, .bashrc, etc. Write only — no read counterpart
_check_sensitive_path() in tools/file_tools.py — blocks writes to /etc/, /boot/, etc. Only called by write_file_tool() and patch_tool(), NOT by read_file_tool()
redact_sensitive_text() — output masking ⚠️ Off by default since 73753417; module-level flag captured at import time so config bridge timing is fragile
get_read_block_error() in agent/file_safety.py ❌ Only blocks Hermes internal cache files (skills/.hub/index-cache), not sensitive user files

Related but distinct issues


Suggested Fixes

Short-term: Add read deny list alongside the write deny list

# In agent/file_safety.py, add:
def build_read_denied_paths(home: str) -> set[str]:
    """Return exact sensitive paths that must never be read."""
    hermes_home = _hermes_home_path()
    return {
        os.path.join(home, ".ssh", "id_rsa"),
        os.path.join(home, ".ssh", "id_ed25519"),
        str(hermes_home / ".env"),
        os.path.join(home, ".ssh", "authorized_keys"),
        # ... same as write denied paths
    }

# In tools/file_tools.py, apply it in read_file_tool() before performing the read,
# similar to how write_file_tool() calls _check_sensitive_path().

Medium-term: Fix _REDACT_ENABLED module-level caching

The module-level variable in agent/redact.py:60 is evaluated at import time:

_REDACT_ENABLED = os.getenv("HERMES_REDACT_SECRETS", "").lower() not in ("0", "false", "no", "off")

If the config bridge (in hermes_cli/main.py / gateway/run.py) sets the env var after the module is imported, this check is stale. Change to a runtime function call.

Long-term: Unify read/write deny lists

build_write_denied_paths() and _check_sensitive_path() are two parallel systems (one in agent/file_safety.py, one in tools/file_tools.py). Consolidate into a single source of truth covering both read and write operations.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High — major feature broken, no workaroundtool/fileFile tools (read, write, patch, search)type/securitySecurity vulnerability or hardening

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions