From 3a7e0e7db4233e92298bcef9caa216d82af43aab Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Wed, 22 Apr 2026 02:45:05 -0400 Subject: [PATCH] fix: migrate hardcoded ~/.hermes paths to HERMES_HOME resolution (#835) - tools/session_templates.py: use get_hermes_home() for template dir and state.db - tools/credential_redact.py: use get_hermes_home() for HERMES_HOME base - agent/context_budget.py: use get_hermes_home() for checkpoints dir - tools/crisis_tool.py: use HERMES_HOME env var with fallback for crisis log path - tools/hardcoded_path_guard.py: add noqa to example docstring lines - scripts/lint_hardcoded_paths.py: exclude lines already referencing HERMES_HOME Also fixes a pre-existing SyntaxError in credential_redact.py caused by raw strings with escaped quotes inside double-quoted literals. --- agent/context_budget.py | 4 +++- scripts/lint_hardcoded_paths.py | 2 +- tools/credential_redact.py | 14 ++++++++------ tools/crisis_tool.py | 3 ++- tools/hardcoded_path_guard.py | 4 ++-- tools/session_templates.py | 6 ++++-- 6 files changed, 20 insertions(+), 13 deletions(-) diff --git a/agent/context_budget.py b/agent/context_budget.py index 0ab88e117..2a4a149f0 100644 --- a/agent/context_budget.py +++ b/agent/context_budget.py @@ -13,9 +13,11 @@ import time from pathlib import Path from typing import Any, Dict, List, Optional, Tuple +from hermes_constants import get_hermes_home + logger = logging.getLogger(__name__) -HERMES_HOME = Path.home() / ".hermes" +HERMES_HOME = get_hermes_home() CHECKPOINT_DIR = HERMES_HOME / "checkpoints" CHARS_PER_TOKEN = 4 diff --git a/scripts/lint_hardcoded_paths.py b/scripts/lint_hardcoded_paths.py index 74629bc80..7abd52c1d 100644 --- a/scripts/lint_hardcoded_paths.py +++ b/scripts/lint_hardcoded_paths.py @@ -56,7 +56,7 @@ VIOLATIONS = [ "id": "expanduser-hermes", "name": "os.path.expanduser ~/.hermes (non-fallback)", "pattern": r'os\.path\.expanduser\(["\']~/.hermes', - "exclude_with": r'#', + "exclude_with": r'#|HERMES_HOME', "message": "Use `os.environ.get('HERMES_HOME', os.path.expanduser('~/.hermes'))` instead", }, ] diff --git a/tools/credential_redact.py b/tools/credential_redact.py index c2d63fcbe..d58bb4523 100644 --- a/tools/credential_redact.py +++ b/tools/credential_redact.py @@ -13,9 +13,11 @@ from datetime import datetime, timezone from pathlib import Path from typing import Any, Dict, List, Tuple +from hermes_constants import get_hermes_home + logger = logging.getLogger(__name__) -HERMES_HOME = Path.home() / ".hermes" +HERMES_HOME = get_hermes_home() AUDIT_DIR = HERMES_HOME / "audit" # Credential patterns to detect and redact @@ -32,14 +34,14 @@ CREDENTIAL_PATTERNS = [ (r"bearer\s+[a-zA-Z0-9._-]{20,}", "[REDACTED: Bearer token]"), # Generic tokens/passwords - (r"(?:token|TOKEN|Token)[:=]\s*["']?[a-zA-Z0-9._-]{20,}["']?", "[REDACTED: Token]"), - (r"(?:password|PASSWORD|Password)[:=]\s*["']?[^\s"']{8,}["']?", "[REDACTED: Password]"), - (r"(?:secret|SECRET|Secret)[:=]\s*["']?[a-zA-Z0-9._-]{20,}["']?", "[REDACTED: Secret]"), - (r"(?:api_key|API_KEY|apiKey|ApiKey)[:=]\s*["']?[a-zA-Z0-9._-]{20,}["']?", "[REDACTED: API key]"), + ("(?:token|TOKEN|Token)[:=]\\s*['\"]?[a-zA-Z0-9._-]{20,}['\"]?", "[REDACTED: Token]"), + ("(?:password|PASSWORD|Password)[:=]\\s*['\"]?[^\\s\"']{8,}['\"]?", "[REDACTED: Password]"), + ("(?:secret|SECRET|Secret)[:=]\\s*['\"]?[a-zA-Z0-9._-]{20,}['\"]?", "[REDACTED: Secret]"), + ("(?:api_key|API_KEY|apiKey|ApiKey)[:=]\\s*['\"]?[a-zA-Z0-9._-]{20,}['\"]?", "[REDACTED: API key]"), # AWS keys (r"AKIA[0-9A-Z]{16}", "[REDACTED: AWS access key]"), - (r"(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)[:=]\s*["']?[a-zA-Z0-9/+=]{40}["']?", "[REDACTED: AWS secret]"), + ("(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)[:=]\\s*['\"]?[a-zA-Z0-9/+=]{40}['\"]?", "[REDACTED: AWS secret]"), # Private keys (r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----", "[REDACTED: Private key header]"), diff --git a/tools/crisis_tool.py b/tools/crisis_tool.py index c4d4354a8..92b59cf97 100644 --- a/tools/crisis_tool.py +++ b/tools/crisis_tool.py @@ -249,7 +249,8 @@ def detect_crisis(text: str) -> CrisisDetectionResult: # ── Escalation Logging ──────────────────────────────────────────────────── BRIDGE_URL = os.environ.get("CRISIS_BRIDGE_URL", "") -LOG_PATH = os.path.expanduser("~/.hermes/crisis_escalations.jsonl") +_HERMES_HOME = os.environ.get("HERMES_HOME") +LOG_PATH = os.path.join(_HERMES_HOME or os.path.expanduser("~/.hermes"), "crisis_escalations.jsonl") def _log_escalation(result: CrisisDetectionResult, text_preview: str = ""): diff --git a/tools/hardcoded_path_guard.py b/tools/hardcoded_path_guard.py index 0795370e3..780b3bf3f 100644 --- a/tools/hardcoded_path_guard.py +++ b/tools/hardcoded_path_guard.py @@ -10,10 +10,10 @@ Usage: from tools.hardcoded_path_guard import check_path, validate_tool_args # Check a single path - err = check_path("/Users/apayne/.hermes/config.yaml") + err = check_path("/Users/apayne/.hermes/config.yaml") # noqa: hardcoded-path-ok # Validate all path-like args in a tool call - clean_args, warnings = validate_tool_args("read_file", {"path": "/home/user/file.txt"}) + clean_args, warnings = validate_tool_args("read_file", {"path": "/home/user/file.txt"}) # noqa: hardcoded-path-ok """ import os diff --git a/tools/session_templates.py b/tools/session_templates.py index 8929a15a3..f952fc9e0 100644 --- a/tools/session_templates.py +++ b/tools/session_templates.py @@ -14,9 +14,11 @@ from typing import Dict, List, Optional, Any from dataclasses import dataclass, asdict, field from enum import Enum +from hermes_constants import get_hermes_home + logger = logging.getLogger(__name__) -TEMPLATE_DIR = Path.home() / ".hermes" / "session-templates" +TEMPLATE_DIR = get_hermes_home() / "session-templates" class TaskType(Enum): @@ -106,7 +108,7 @@ class Templates: return TaskType.MIXED def extract(self, session_id, max_n=10): - db = Path.home() / ".hermes" / "state.db" + db = get_hermes_home() / "state.db" if not db.exists(): return [] try: