Skip to content

fix(security): salvage #25726 + #20778 .env protections#32011

Merged
teknium1 merged 4 commits into
mainfrom
hermes/hermes-f9dd4507
May 25, 2026
Merged

fix(security): salvage #25726 + #20778 .env protections#32011
teknium1 merged 4 commits into
mainfrom
hermes/hermes-f9dd4507

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Salvage of two stale .env-related PRs that conflicted with current main.

Commits (rebased, contributor authorship preserved)

  • @dusterbloom — fix(security): tighten .env file permissions to 0600 at all creation sites (fix(security): tighten .env file permissions to 0600 at all creation sites #25726)
    Covers docker/stage2-hook.sh (post-s6-overlay refactor), hermes_cli/doctor.py (--fix empty .env), hermes_cli/profiles.py (profile create --clone copy), and setup-hermes.sh. scripts/install.sh already chmods 600 unconditionally (line 1442) — no change needed there.

  • @liuhao1024 — fix(security): block read_file on project-local .env files (fix(security): block read_file on project-local .env files #20778)
    Adds project-local .env / .env.local / .env.development / .env.production / .env.test / .env.staging / .envrc to the read-deny list in agent/file_safety.py. Hermes-own .env was already read-blocked; project-local .env was not. Per the module docstring this is defense-in-depth, not a boundary (terminal can still cat).

Salvage tweaks

  • fix(security): tighten .env file permissions to 0600 at all creation sites #25726 docker/entrypoint.sh portion dropped — main switched to an s6-overlay shim and .env creation moved to stage2-hook.sh. Added chmod 600 there instead.
  • fix(security): block read_file on project-local .env files #20778 conflict in get_read_block_error docstring: kept the current rich docstring + extended it to describe the new third category. Updated stale test test_identically_named_files_outside_hermes_home_not_blocked which asserted project-local .env should be readable — that's exactly the assumption this PR inverts. Renamed to test_identically_named_hermes_files_outside_home_not_blocked and narrowed its assertions to auth.json / google_oauth.json / mcp-tokens/ which remain per-location gated.

Test plan

pytest tests/agent/test_file_safety.py tests/agent/test_file_safety_credentials.py  # 38 passed
pytest tests/hermes_cli/ -k 'profile or doctor or env'                              # 705 passed

Co-authored-by: dusterbloom 32869278+dusterbloom@users.noreply.github.com
Co-authored-by: liuhao1024 liuhao1024@users.noreply.github.com

Closes #25726
Closes #20778

dusterbloom and others added 4 commits May 25, 2026 03:38
…sites

.env holds API keys and secrets. Multiple creation sites used `cp` /
`touch` / `shutil.copy2` which obey the process umask — commonly
0o022, leaving the file at 0o644 (world-readable). Apply chmod 0o600
explicitly at every site that creates or copies .env.

Sites covered:
- docker/stage2-hook.sh: after the seed_one '.env' call, applied
  unconditionally (not just on first-seed) so a host-mounted .env with
  loose perms gets tightened on every container restart
- hermes_cli/doctor.py: 'hermes doctor --fix' touches an empty .env
  when missing
- hermes_cli/profiles.py: 'hermes profile create --clone' copies .env
  from the source profile; shutil.copy2 preserves source mode, so a
  source .env at 0o644 was being cloned into 0o644
- setup-hermes.sh: in-tree setup script's cp .env.example .env path,
  plus the already-exists branch (mirror of install.sh which already
  chmods 600 unconditionally on line 1442)

scripts/install.sh was NOT changed — it already chmod 600's the .env
unconditionally after the create/already-exists branches (line 1442).

Salvaged from PR #25726 by @dusterbloom. The docker/entrypoint.sh
portion of the original PR was dropped because main switched to an
s6-overlay shim — the .env creation logic moved to stage2-hook.sh,
which is where the chmod now lives.

Closes #25497 (subset — install.sh + setup-hermes.sh) and #8448
(subset — install.sh only) as superseded.

Co-authored-by: teknium1 <127238744+teknium1@users.noreply.github.com>
get_read_block_error() only blocked internal Hermes cache files but
allowed reading project-local secret-bearing environment files (.env,
.env.production, .env.local, etc.) through both read_file and ACP
fs/read_text_file paths.

Add a basename deny set for common secret-bearing .env variants.
.env.example remains readable as documentation.

Fixes #20734
@teknium1 teknium1 merged commit 4c64638 into main May 25, 2026
22 of 24 checks passed
@teknium1 teknium1 deleted the hermes/hermes-f9dd4507 branch May 25, 2026 10:40
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-f9dd4507 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: 9256 on HEAD, 9255 on base (🆕 +1)

🆕 New issues (1):

Rule Count
unresolved-import 1
First entries
tests/agent/test_file_safety.py:11: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`

✅ Fixed issues: none

Unchanged: 4908 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.

3 participants