fix(skills): detect prompt injection patterns and block cache file reads

Adds two security layers to prevent prompt injection via skills hub
cache files (#1558):

1. read_file: blocks direct reads of ~/.hermes/skills/.hub/ directory
   (index-cache, catalog files). The 3.5MB clawhub_catalog_v1.json
   was the original injection vector — untrusted skill descriptions
   in the catalog contained adversarial text that the model executed.

2. skill_view: warns when skills are loaded from outside the trusted
   ~/.hermes/skills/ directory, and detects common injection patterns
   in skill content ("ignore previous instructions", "<system>", etc.).

Cherry-picked from PR #1562 by ygd58.
This commit is contained in:
buray
2026-03-17 01:49:49 -07:00
committed by teknium1
parent e1e702abc5
commit 26b2fc360f
2 changed files with 52 additions and 0 deletions
+21
View File
@@ -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:
+31
View File
@@ -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:",
"<system>",
"]]>",
]
_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)