From 7485fe0605a54eb148caf6eb7cf16fc23f18e6b5 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Fri, 3 Jul 2026 03:06:55 -0700 Subject: [PATCH] fix(dashboard): make .env sensitive-file guard case-insensitive Follow-up to #57507: .ENV / .Env.local on case-insensitive filesystem mounts slipped past the guard. Lowercase the name before matching and add a regression test. Addresses egilewski's open review note. --- hermes_cli/web_server.py | 9 +++++++-- tests/hermes_cli/test_web_server_files.py | 12 ++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 217e7723d..f81353f6a 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -1192,8 +1192,13 @@ _FS_READDIR_HIDDEN = { # and exposing them through the dashboard file browser is a security leak — # see issue #57505. def _is_sensitive_filename(name: str) -> bool: - """Return True for ``.env`` and any ``.env.`` variant.""" - return name == ".env" or name.startswith(".env.") + """Return True for ``.env`` and any ``.env.`` variant. + + Case-insensitive so ``.ENV`` / ``.Env.local`` on case-insensitive + filesystems (macOS/Windows mounts) can't slip past the guard. + """ + lowered = name.lower() + return lowered == ".env" or lowered.startswith(".env.") _FS_DATA_URL_MAX_BYTES = 16 * 1024 * 1024 _FS_TEXT_SOURCE_MAX_BYTES = 64 * 1024 * 1024 _FS_TEXT_PREVIEW_MAX_BYTES = 512 * 1024 diff --git a/tests/hermes_cli/test_web_server_files.py b/tests/hermes_cli/test_web_server_files.py index 8bc40680f..63a8b39ff 100644 --- a/tests/hermes_cli/test_web_server_files.py +++ b/tests/hermes_cli/test_web_server_files.py @@ -548,3 +548,15 @@ def test_sensitive_env_suffix_variants_blocked(forced_files_client): p.write_text(f"SECRET_{suffix}=abc123") assert client.get("/api/files/read", params={"path": str(p)}).status_code == 403 assert client.get("/api/files/download", params={"path": str(p)}).status_code == 403 + + +def test_sensitive_env_case_insensitive_blocked(forced_files_client): + """Regression: .ENV / .Env.local casings must be blocked too (case-insensitive FS mounts).""" + client, root = forced_files_client + + root.mkdir(parents=True, exist_ok=True) + for name in (".ENV", ".Env.local", ".eNv.PROD"): + p = root / name + p.write_text("SECRET=abc123") + assert client.get("/api/files/read", params={"path": str(p)}).status_code == 403 + assert client.get("/api/files/download", params={"path": str(p)}).status_code == 403