#!/usr/bin/env python3 """ Hardcoded Path Guard — Poka-Yoke #921 Detects and blocks hardcoded home-directory paths in tool arguments. These paths work on one machine but break on others, VPS deployments, or when HOME changes. Usage: from tools.hardcoded_path_guard import check_path, validate_tool_args # Check a single path err = check_path("/Users/apayne/.hermes/config.yaml") # Validate all path-like args in a tool call clean_args, warnings = validate_tool_args("read_file", {"path": "/home/user/file.txt"}) """ import os import re import json as _json from typing import Dict, List, Optional, Tuple, Any # Patterns that indicate hardcoded home directories HARDCODED_PATTERNS = [ (r"/Users/[\w.\-]+/", "macOS home directory (/Users/...)"), (r"/home/[\w.\-]+/", "Linux home directory (/home/...)"), (r"(? Optional[str]: if _NOQA_PATTERN.search(text): return None for pattern, desc in _COMPILED_PATTERNS: if pattern.search(text): return desc return None def check_path(path_value: str) -> Optional[str]: if not isinstance(path_value, str): return None match_desc = has_hardcoded_path(path_value) if match_desc: return ( f"Path contains hardcoded home directory ({match_desc}): '{path_value}'. " f"Use $HOME, relative paths, or get_hermes_home(). " f"Add '# noqa: hardcoded-path-ok' if intentional." ) return None def validate_tool_args(tool_name: str, args: Dict[str, Any]) -> Tuple[Dict[str, Any], List[str]]: warnings = [] for key, value in args.items(): if key.lower() not in _PATH_ARG_NAMES: continue if isinstance(value, str): err = check_path(value) if err: warnings.append(err) elif isinstance(value, list): for item in value: if isinstance(item, str): err = check_path(item) if err: warnings.append(err) return args, warnings def scan_source_for_violations(source_code: str, filename: str = "") -> List[Tuple[int, str, str]]: violations = [] lines = source_code.split("\n") for i, line in enumerate(lines, 1): stripped = line.strip() if stripped.startswith("#"): if _NOQA_PATTERN.search(line): continue continue if stripped.startswith("import ") or stripped.startswith("from "): continue for pattern, desc in _COMPILED_PATTERNS: match = pattern.search(line) if match: if _NOQA_PATTERN.search(line): continue violations.append((i, line.strip(), desc)) break return violations def guard_tool_dispatch(tool_name: str, args: Dict[str, Any]) -> Optional[str]: _, warnings = validate_tool_args(tool_name, args) if warnings: return _json.dumps({ "error": "Hardcoded home directory path detected", "details": warnings, "suggestion": "Use $HOME, relative paths, or get_hermes_home() instead of hardcoded paths.", "pokayoke": True, "rule": "hardcoded-path-guard" }) return None