NousResearch/hermes-agent

Restrict writes to sensitive env credential files

Cluster 15981. Data as of

Nothing open on GitHub. Fix merged in #29687 on 21 May 2026. Check it covers your case.

The short answer

0open issues
0open PRs
4items grouped
Known?
Yes. 4 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #29687. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #29687. That is separate from the fix event.
Still open
Nothing open on GitHub.

These items concern identifying and enforcing write-deny protections on sensitive environment files to prevent accidental or unauthorized credential exposure.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #29687 security(file-safety): also write-deny <root>/.env when running under a profile (#15981)

    merged to main

    files: agent/file_safety.py, tests/tools/test_write_deny.py

    Show the diff
    • agent/file_safety.py
      @@ -16,9 +16,19 @@ def _hermes_home_path() -> Path:
               return Path(os.path.expanduser("~/.hermes"))
       
       
      +def _hermes_root_path() -> Path:
      +    """Resolve the Hermes root dir (always the parent of any profile, never per-profile)."""
      +    try:
      +        from hermes_constants import get_default_hermes_root  # local import to avoid cycles
      +        return get_default_hermes_root()
      +    except Exception:
      +        return Path(os.path.expanduser("~/.hermes"))
      +
      +
       def build_write_denied_paths(home: str) -> set[str]:
           """Return exact sensitive paths that must never be written."""
           hermes_home = _hermes_home_path()
      +    hermes_root = _hermes_root_path()
           return {
               os.path.realpath(p)
               for p in [
      @@ -26,7 +36,11 @@ def build_write_denied_paths(home: str) -> set[str]:
                   os.path.join(home, ".ssh", "id_rsa"),
                   os.path.join(home, ".ssh", "id_ed25519"),
                   os.path.join(home, ".ssh", "config"),
      +            # Active profile .env (or top-level .env when not in profile mode).
                   str(hermes_home / ".env"),
      +            # Top-level .env, even when running under a profile — overwriting it
      +            # leaks credentials across every profile that inherits from root (#15981).
      +            str(hermes_root / ".env"),
                   os.path.join(home, ".bashrc"),
                   os.path.join(home, ".zshrc"),
                   os.path.join(home, ".profile"),
    • tests/tools/test_write_deny.py
      @@ -41,6 +41,31 @@ def test_hermes_env(self):
               path = str(get_hermes_home() / ".env")
               assert _is_write_denied(path) is True
       
      +    def test_hermes_root_env_when_running_under_profile(self, tmp_path, monkeypatch):
      +        """Top-level ``<root>/.env`` stays write-denied even when running under
      +        a profile (#15981).
      +
      +        Before the fix, ``build_write_denied_paths`` only added
      +        ``<active_profile>/.env`` to the deny list, so the global
      +        ``~/.hermes/.env`` (whose credentials are inherited by every profile)
      +        could be silently overwritten by ``write_file`` while a profile was
      +        active.
      +        """
      +        root = tmp_path / "hermes_root"
      +        profile_home = root / "profiles" / "coder"
      +        profile_home.mkdir(parents=True)
      +        global_env = root / ".env"
      +        global_env.write_text("OPENAI_API_KEY=[redacted:secret-assignment]")
      +
      +        monkeypatch.setenv("HERMES_HOME", str(profile_home))
      +
      +        # Sanity check: HERMES_HOME does point to the profile dir, not the root.
      +        from hermes_constants import get_hermes_home, get_default_hermes_root
      +        assert get_hermes_home() == profile_home
      +        assert get_default_hermes_root() == root
      +
      +        assert _is_write_denied(str(global_env)) is True
      +
           def test_shell_profiles(self):
               home = str(Path.home())
               for name in [".bashrc", ".zshrc", ".profile", ".bash_profile", ".zprofile"]:

2 PRs closed without merging

From the close comments: duplicate 1 · merged via another change 1

  • #17044 security(file-safety): also write-deny <root>/.env when running under a profile (#15981) · merged via another change → #29687
  • #17078 fix(security): deny writes to global ~/.hermes/.env · duplicate → #17044, #15981
Closed issues · 1
  • Closed#15981write_file tool bypasses credential protection for global ~/.hermes/.env
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.50: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Merged PRs
#29687
Closed issues
1
Attempts
2 PRs closed without merging
Fix link checked
21 May 2026

For your agent

The same facts, typed.