Project-Rivet / v2 /tools /guard.py
HumboldtJoker's picture
Upload folder using huggingface_hub
4554903 verified
Raw
History Blame Contribute Delete
2.72 kB
"""SecurityMonitor for the tool harness (Kintsugi §5c).
Every subprocess the tool layer spawns goes through run_checked().
The check is code, not model judgment — it cannot be reasoned away.
Commands are argv lists (never shell=True), checked against blocked
patterns, allowlisted binaries, and a path jail before execution.
"""
import re
import subprocess
from dataclasses import dataclass
from pathlib import Path
# Kintsugi SecurityMonitor patterns + Rivet additions.
SUSPICIOUS_PATTERNS = [
r"base64\s+--decode",
r"curl.*\|.*sh",
r"\benv\b", r"\bprintenv\b",
r">\s*/dev/tcp",
r"nc\s+-e",
r"chmod\s+777",
r"\.ssh/authorized_keys",
r"rm\s+-rf\s+/",
r"\bsudo\b",
r"DROP\s+DATABASE",
]
# Binaries the tool harness is allowed to spawn. Nothing else runs.
ALLOWED_BINARIES = {
"git", "npm", "npx", "tsc", "node", "pg_dump", "psql",
}
class ToolSecurityError(Exception):
pass
@dataclass
class ToolResult:
ok: bool
stdout: str = ""
stderr: str = ""
returncode: int = -1
blocked_reason: str = ""
def check_command(argv: list) -> str:
"""Return a block reason, or '' if the command is clean."""
if not argv:
return "empty command"
binary = Path(argv[0]).name
if binary not in ALLOWED_BINARIES:
return f"binary '{binary}' not in tool allowlist"
joined = " ".join(str(a) for a in argv)
for pattern in SUSPICIOUS_PATTERNS:
if re.search(pattern, joined, re.IGNORECASE):
return f"matches blocked pattern: {pattern}"
return ""
def check_path(path: Path, roots: list) -> str:
"""Return a block reason unless path resolves inside an allowed root."""
resolved = Path(path).resolve()
for root in roots:
try:
resolved.relative_to(Path(root).resolve())
return ""
except ValueError:
continue
return f"path {resolved} escapes allowed roots {roots}"
def run_checked(argv: list, cwd: str | None = None,
timeout: int = 60) -> ToolResult:
reason = check_command(argv)
if reason:
return ToolResult(ok=False, blocked_reason=reason)
try:
proc = subprocess.run(
argv, cwd=cwd, capture_output=True, text=True, timeout=timeout,
)
except subprocess.TimeoutExpired:
return ToolResult(ok=False, stderr=f"timed out after {timeout}s")
except FileNotFoundError as exc:
return ToolResult(ok=False, stderr=str(exc))
except OSError as exc:
return ToolResult(ok=False, stderr=str(exc))
return ToolResult(
ok=proc.returncode == 0,
stdout=proc.stdout,
stderr=proc.stderr,
returncode=proc.returncode,
)