Files
hermes-agent/hooks/pre-commit-path-guard.py
Alexander Whitestone 5dcb90531b
Some checks failed
Contributor Attribution Check / check-attribution (pull_request) Failing after 49s
Docker Build and Publish / build-and-push (pull_request) Has been skipped
Nix / nix (ubuntu-latest) (pull_request) Failing after 6s
Supply Chain Audit / Scan PR for supply chain risks (pull_request) Successful in 25s
Tests / e2e (pull_request) Successful in 4m26s
Tests / test (pull_request) Failing after 59m48s
Nix / nix (macos-latest) (pull_request) Has been cancelled
feat: Poka-yoke — prevent hardcoded home-directory paths (closes #921)
Three-layer defense against latent /Users/<name>/ and ~/ path defects:

1. Runtime guard (tools/path_guard.py):
   - validate_path() catches /Users/<name>/, /home/<name>/ in tool args
   - Allows current HOME prefix (expanduser output is safe)
   - Wired into write_file_tool() and execute_code()

2. Pre-commit hook (hooks/pre-commit-path-guard.py):
   - Scans staged .py files for hardcoded path patterns
   - Blocks commit with actionable error message
   - # noqa: hardcoded-path-ok escape hatch for legitimate cases

3. CI lint (scripts/lint_hardcoded_paths.py):
   - Scans directory tree for violations
   - --fix flag shows remediation suggestions
   - Skips test dirs, __pycache__, venv

4. 21 tests (tests/test_path_guard.py):
   - Runtime validation (valid/invalid paths, batch, edge cases)
   - Static scanning (clean files, violations, noqa, comments)
   - Directory scanning (tree traversal, skip rules)

Existing violations annotated with # noqa: hardcoded-path-ok where
legitimate (config defaults, display strings, test fixtures, skills).
2026-04-21 07:37:14 -04:00

69 lines
2.0 KiB
Python

#!/usr/bin/env python3
"""
Pre-commit hook: Reject hardcoded home-directory paths.
Scans staged Python files for patterns like:
- /Users/<name>/...
- /home/<name>/...
- ~/... (in string literals outside expanduser context)
Escape hatch: add `# noqa: hardcoded-path-ok` to any legitimate line.
Install:
cp hooks/pre-commit-path-guard.py .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
"""
import subprocess
import sys
from pathlib import Path
# Add project root to path so we can import path_guard
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from tools.path_guard import scan_file_for_violations
def get_staged_files():
"""Get list of staged .py files."""
result = subprocess.run(
["git", "diff", "--cached", "--name-only", "--diff-filter=ACM"],
capture_output=True, text=True
)
return [f for f in result.stdout.strip().splitlines() if f.endswith(".py")]
def main():
files = get_staged_files()
if not files:
sys.exit(0)
all_violations = []
for filepath in files:
if not Path(filepath).exists():
continue
violations = scan_file_for_violations(filepath)
if violations:
all_violations.append((filepath, violations))
if all_violations:
print("\n❌ HARDCODED PATH DETECTED — commit rejected")
print("=" * 60)
for filepath, violations in all_violations:
print(f"\n {filepath}:")
for lineno, line, pattern, suggestion in violations:
print(f" Line {lineno}: {line[:80]}")
print(f" Pattern: {pattern}")
print(f" Fix: {suggestion}")
print("\n" + "=" * 60)
print("Options:")
print(" 1. Use get_hermes_home(), os.environ['HOME'], or relative paths")
print(" 2. Add # noqa: hardcoded-path-ok to the line for legitimate cases")
print("")
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
main()