mirror of
https://github.com/Strategic-Automation/violin.git
synced 2026-08-14 12:33:37 +02:00
fix(guard): scope online-research gate to the exploited hypothesis
- check_command now accepts hypothesis_id (schema + handler + args) - Exploit-phase research gate checks only the named hypothesis when provided; otherwise all candidates (previous behavior) - Error message names the remediation tool: violin_record_hypothesis id=H-00N cve_research=... exploit_research=... - Fixes silent bypass: normalized id comparison (002 vs 2) so an unresearched hypothesis is actually blocked - New test: named-hypothesis passes, unresearched H-002 blocked, all-candidates mode still enforces full rows
This commit is contained in:
@@ -58,6 +58,7 @@ class CheckCommandArgs:
|
||||
target: str | None = None
|
||||
session_id: str | None = None
|
||||
account_sync: bool = True
|
||||
hypothesis_id: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -530,10 +531,26 @@ def check_hypothesis_freshness(
|
||||
Phase.PRIVESC,
|
||||
Phase.FLAGS,
|
||||
}:
|
||||
researched = [h for h in relevant if h.cve_research.strip() and h.exploit_research.strip()]
|
||||
if not researched:
|
||||
# Online research is required before exploit execution: the engagement
|
||||
# brief may have no exploit path, so the first action for any real
|
||||
# exploit is to look for prior work. When the command names a specific
|
||||
# hypothesis, only that hypothesis must carry research rows; otherwise
|
||||
# every candidate hypothesis must. 'no results' / 'not applicable' /
|
||||
# 'source unavailable' are valid truthful outcomes.
|
||||
if norm_hyp_id is not None:
|
||||
research_targets = [
|
||||
h
|
||||
for h in relevant
|
||||
if (h.id.strip().upper().removeprefix("H-").lstrip("0") or "0") == norm_hyp_id
|
||||
]
|
||||
else:
|
||||
research_targets = relevant
|
||||
researched = [h for h in research_targets if h.cve_research.strip() and h.exploit_research.strip()]
|
||||
if len(researched) < len(research_targets):
|
||||
missing = []
|
||||
for h in relevant:
|
||||
for h in research_targets:
|
||||
if h.cve_research.strip() and h.exploit_research.strip():
|
||||
continue
|
||||
fields = []
|
||||
if not h.cve_research.strip():
|
||||
fields.append("CVE Research")
|
||||
@@ -543,8 +560,10 @@ def check_hypothesis_freshness(
|
||||
result.add_error(
|
||||
"online research must be attempted and recorded before exploit execution; "
|
||||
+ "; ".join(missing)
|
||||
+ ". Record each query/source and outcome; 'no results', 'not applicable', "
|
||||
"or 'source unavailable' are valid outcomes when truthful."
|
||||
+ ". Record each query/source/outcome via violin_record_hypothesis "
|
||||
"id=H-00N cve_research='...' exploit_research='...' — 'no results', "
|
||||
"'not applicable', or 'source unavailable' are valid outcomes when "
|
||||
"truthful."
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -746,7 +765,7 @@ def check_command(args: CheckCommandArgs) -> CheckResult:
|
||||
phase,
|
||||
args.command,
|
||||
args.target,
|
||||
hypothesis_id=active_task_hyp_id,
|
||||
hypothesis_id=args.hypothesis_id or active_task_hyp_id,
|
||||
match_command_target=not research_primary,
|
||||
)
|
||||
result.errors.extend(hyp_result.errors)
|
||||
|
||||
@@ -92,6 +92,7 @@ def _check_command_internal(a) -> cmd_module.CheckResult:
|
||||
scope=a.get("scope", ""),
|
||||
target=a.get("target"),
|
||||
session_id=a.get("session_id"),
|
||||
hypothesis_id=a.get("hypothesis_id"),
|
||||
)
|
||||
)
|
||||
try:
|
||||
|
||||
@@ -30,6 +30,13 @@ class CheckCommandArgsModel(BaseModel):
|
||||
),
|
||||
)
|
||||
session_id: str = ""
|
||||
hypothesis_id: str = Field(
|
||||
"",
|
||||
description=(
|
||||
"Optional hypothesis the command targets (H-00N). When provided, exploit-phase "
|
||||
"guards (e.g. online-research requirement) check only this hypothesis."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class RecordPttArgsModel(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user