# NousResearch/hermes-agent · Restrict writes to sensitive env credential files

## Open now

- Nothing open on GitHub.

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

## TL;DR

- 4 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #29687. Verify it addresses your case.
- **Recommended:** read PR #29687 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-29687`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.50`: the share of member 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.

## Closed / duplicate issues

- #15981: write_file tool bypasses credential protection for global ~/.hermes/.env

## Prior & competing PRs

- **#29687: security(file-safety): also write-deny <root>/.env when running under a profile (#15981)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `agent/file_safety.py`, `tests/tools/test_write_deny.py`
  - fix hunks:
  - `agent/file_safety.py`

    ```diff
    @@ -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 [
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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"]:
    ```
- **#17044: security(file-safety): also write-deny <root>/.env when running under a profile (#15981)**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#29687`
- **#17078: fix(security): deny writes to global ~/.hermes/.env**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#17044`, `#15981`

## Fix status

- Merged to main in PR #29687 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-21`.
