diff --git a/tools/file_tools.py b/tools/file_tools.py index ddcfcd567a..03470c3758 100644 --- a/tools/file_tools.py +++ b/tools/file_tools.py @@ -169,6 +169,27 @@ def clear_file_ops_cache(task_id: str = None): def read_file_tool(path: str, offset: int = 1, limit: int = 500, task_id: str = "default") -> str: """Read a file with pagination and line numbers.""" try: + # Security: block direct reads of internal Hermes cache/index files + # to prevent prompt injection via catalog or hub metadata files. + import pathlib as _pathlib + _resolved = _pathlib.Path(path).expanduser().resolve() + _hermes_home = _pathlib.Path("~/.hermes").expanduser().resolve() + _blocked_dirs = [ + _hermes_home / "skills" / ".hub" / "index-cache", + _hermes_home / "skills" / ".hub", + ] + for _blocked in _blocked_dirs: + try: + _resolved.relative_to(_blocked) + return json.dumps({ + "error": ( + f"Access denied: {path} is an internal Hermes cache file " + "and cannot be read directly to prevent prompt injection. " + "Use the skills_list or skill_view tools instead." + ) + }) + except ValueError: + pass file_ops = _get_file_ops(task_id) result = file_ops.read_file(path, offset, limit) if result.content: diff --git a/tools/skills_tool.py b/tools/skills_tool.py index bcde5d538b..771d7684f1 100644 --- a/tools/skills_tool.py +++ b/tools/skills_tool.py @@ -873,6 +873,37 @@ def skill_view(name: str, file_path: str = None, task_id: str = None) -> str: ensure_ascii=False, ) + # Security: warn if skill is loaded from outside the trusted skills directory + try: + skill_md.resolve().relative_to(SKILLS_DIR.resolve()) + _outside_skills_dir = False + except ValueError: + _outside_skills_dir = True + + # Security: detect common prompt injection patterns + _INJECTION_PATTERNS = [ + "ignore previous instructions", + "ignore all previous", + "you are now", + "disregard your", + "forget your instructions", + "new instructions:", + "system prompt:", + "", + "]]>", + ] + _content_lower = content.lower() + _injection_detected = any(p in _content_lower for p in _INJECTION_PATTERNS) + + if _outside_skills_dir or _injection_detected: + _warnings = [] + if _outside_skills_dir: + _warnings.append(f"skill file is outside the trusted skills directory (~/.hermes/skills/): {skill_md}") + if _injection_detected: + _warnings.append("skill content contains patterns that may indicate prompt injection") + import logging as _logging + _logging.getLogger(__name__).warning("Skill security warning for '%s': %s", name, "; ".join(_warnings)) + parsed_frontmatter: Dict[str, Any] = {} try: parsed_frontmatter, _ = _parse_frontmatter(content)