Skip to content

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

Merged
teknium1 merged 1 commit into
mainfrom
salvage-17044-env-deny
May 21, 2026
Merged

security(file-safety): also write-deny <root>/.env when running under a profile (#15981)#29687
teknium1 merged 1 commit into
mainfrom
salvage-17044-env-deny

Conversation

@teknium1

@teknium1 teknium1 commented May 21, 2026

Copy link
Copy Markdown
Contributor

Salvage of #17044 onto current main, preserving @0xsir0000's authorship.

Note on scope

Per SECURITY.md, the agent's terminal tool can already overwrite <root>/.env directly — so this is not in the project's security scope and does not close a real attack surface. We're landing it anyway as defense-in-depth / footgun reduction for write_file / patch, matching the existing protection for the profile-level .env.

Fix

build_write_denied_paths() resolved the protected .env via get_hermes_home(), which is profile-aware. Under a profile, HERMES_HOME points at <root>/profiles/<name> and only the profile .env got added to the deny list — the global <root>/.env (whose creds every profile inherits) was left writable by write_file/patch.

Adds a parallel _hermes_root_path() helper using the existing hermes_constants.get_default_hermes_root() and includes <root>/.env in the deny list alongside the active profile's .env. Non-profile-mode behavior is unchanged.

Validation

  • 36/36 pass: tests/tools/test_write_deny.py + tests/tools/test_file_write_safety.py
  • Live E2E with HERMES_HOME=<tmp>/profiles/coder: write_file(<tmp>/.env) → denied, write_file(<tmp>/profiles/coder/.env) → denied, write_file(<tmp>/notes.txt) → succeeds. Original credential preserved.
  • get_default_hermes_root() handles Docker / custom layouts (returns HERMES_HOME when it's outside ~/.hermes).

Closes #15981.
Closes #17044.

Infographic

pr-29687-env-deny-profile

… a profile (#15981)

build_write_denied_paths() resolved the protected ``.env`` via
get_hermes_home(), which is profile-aware. When a profile is active
HERMES_HOME points at ``<root>/profiles/<name>`` and ``hermes_home / ".env"``
expands to the *profile* env file only — the global ``<root>/.env`` is left
off the deny list and a write_file call against it succeeds. Since the
top-level .env supplies credentials inherited by every profile, this is a
P0 credential-exfiltration / overwrite path.

Add a parallel ``_hermes_root_path()`` helper that returns the Hermes root
(via the existing ``get_default_hermes_root()`` constant) and include
``<root>/.env`` in the deny list alongside ``<active_profile>/.env``. Both
paths now refuse write_file/patch regardless of profile state. The active
HERMES_HOME .env entry is preserved so the protection in non-profile mode
is unchanged.

A regression test exercises the profile-active scenario by pointing
HERMES_HOME at ``<tmp>/profiles/coder`` and asserting that ``<tmp>/.env``
is denied.

Fixes #15981
@teknium1 teknium1 merged commit 5edb346 into main May 21, 2026
16 of 17 checks passed
@teknium1 teknium1 deleted the salvage-17044-env-deny branch May 21, 2026 06:37
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: salvage-17044-env-deny vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 8992 on HEAD, 8992 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 4743 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

write_file tool bypasses credential protection for global ~/.hermes/.env

2 participants