mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix: make tirith block verdicts approvable instead of hard-blocking (#3428)
Previously, tirith exit code 1 (block) immediately rejected the command with no approval prompt — users saw 'BLOCKED: Command blocked by security scan' and the agent moved on. This prevented gateway/CLI users from approving pipe-to-shell installs like 'curl ... | sh' even when they understood the risk. Changes: - Tirith 'block' and 'warn' now both go through the approval flow. Users see the full tirith findings (severity, title, description, safer alternatives) and can choose to approve or deny. - New _format_tirith_description() builds rich descriptions from tirith findings JSON so the approval prompt is informative. - CLI startup now warns when tirith is enabled but not available, so users know command scanning is degraded to pattern matching only. The default approval choice is still deny, so the security posture is unchanged for unattended/timeout scenarios. Reported via Discord by pistrie — 'curl -fsSL https://mandex.dev/install.sh | sh' was hard-blocked with no way to approve.
This commit is contained in:
parent
6f11ff53ad
commit
e4e04c2005
3 changed files with 75 additions and 18 deletions
|
|
@ -456,6 +456,33 @@ def check_dangerous_command(command: str, env_type: str,
|
|||
# Combined pre-exec guard (tirith + dangerous command detection)
|
||||
# =========================================================================
|
||||
|
||||
def _format_tirith_description(tirith_result: dict) -> str:
|
||||
"""Build a human-readable description from tirith findings.
|
||||
|
||||
Includes severity, title, and description for each finding so users
|
||||
can make an informed approval decision.
|
||||
"""
|
||||
findings = tirith_result.get("findings") or []
|
||||
if not findings:
|
||||
summary = tirith_result.get("summary") or "security issue detected"
|
||||
return f"Security scan: {summary}"
|
||||
|
||||
parts = []
|
||||
for f in findings:
|
||||
severity = f.get("severity", "")
|
||||
title = f.get("title", "")
|
||||
desc = f.get("description", "")
|
||||
if title and desc:
|
||||
parts.append(f"[{severity}] {title}: {desc}" if severity else f"{title}: {desc}")
|
||||
elif title:
|
||||
parts.append(f"[{severity}] {title}" if severity else title)
|
||||
if not parts:
|
||||
summary = tirith_result.get("summary") or "security issue detected"
|
||||
return f"Security scan: {summary}"
|
||||
|
||||
return "Security scan — " + "; ".join(parts)
|
||||
|
||||
|
||||
def check_all_command_guards(command: str, env_type: str,
|
||||
approval_callback=None) -> dict:
|
||||
"""Run all pre-exec security checks and return a single approval decision.
|
||||
|
|
@ -499,24 +526,20 @@ def check_all_command_guards(command: str, env_type: str,
|
|||
|
||||
# --- Phase 2: Decide ---
|
||||
|
||||
# If tirith blocks, block immediately (no approval possible)
|
||||
if tirith_result["action"] == "block":
|
||||
summary = tirith_result.get("summary") or "security issue detected"
|
||||
return {
|
||||
"approved": False,
|
||||
"message": f"BLOCKED: Command blocked by security scan ({summary}). Do NOT retry.",
|
||||
}
|
||||
|
||||
# Collect warnings that need approval
|
||||
warnings = [] # list of (pattern_key, description, is_tirith)
|
||||
|
||||
session_key = os.getenv("HERMES_SESSION_KEY", "default")
|
||||
|
||||
if tirith_result["action"] == "warn":
|
||||
# Tirith block/warn → approvable warning with rich findings.
|
||||
# Previously, tirith "block" was a hard block with no approval prompt.
|
||||
# Now both block and warn go through the approval flow so users can
|
||||
# inspect the explanation and approve if they understand the risk.
|
||||
if tirith_result["action"] in ("block", "warn"):
|
||||
findings = tirith_result.get("findings") or []
|
||||
rule_id = findings[0].get("rule_id", "unknown") if findings else "unknown"
|
||||
tirith_key = f"tirith:{rule_id}"
|
||||
tirith_desc = f"Security scan: {tirith_result.get('summary') or 'security warning detected'}"
|
||||
tirith_desc = _format_tirith_description(tirith_result)
|
||||
if not is_approved(session_key, tirith_key):
|
||||
warnings.append((tirith_key, tirith_desc, True))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue