更新: 21 个文件 - 2026-03-17 00:00:00
这个提交包含在:
@@ -35,6 +35,32 @@ def _compose_run_id(advisory: Dict[str, Any]) -> str:
|
||||
return f"{advisory['system_id']}-{advisory['canonical_id']}-{now_utc().strftime('%Y%m%d%H%M%S')}"
|
||||
|
||||
|
||||
def _existing_refs(*paths: Path) -> List[str]:
|
||||
return [str(path) for path in paths if path.exists()]
|
||||
|
||||
|
||||
def _timeline_event(timeline: List[Dict[str, Any]], step: str, status: str, detail: str = "") -> None:
|
||||
timeline.append(
|
||||
{
|
||||
"at": isoformat(now_utc()),
|
||||
"step": step,
|
||||
"status": status,
|
||||
"detail": detail,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _sync_registry_outputs() -> None:
|
||||
from intel.config import GENERATED_DIR, load_source_map # noqa: E402
|
||||
from intel.main import _load_existing_advisories, _load_existing_triage, _write_outputs # noqa: E402
|
||||
|
||||
source_map = load_source_map()
|
||||
advisories = _load_existing_advisories()
|
||||
triage = _load_existing_triage()
|
||||
summary = read_json(GENERATED_DIR / "run-summary.json", default={}) or {}
|
||||
_write_outputs(source_map, advisories, triage, summary.get("failures", []), summary)
|
||||
|
||||
|
||||
def _resolve_profile(advisory: Dict[str, Any]) -> Dict[str, Any]:
|
||||
profile = repro.resolve_profile(advisory["canonical_id"], advisory)
|
||||
current_profile = read_yaml(ENV_PROFILES_DIR / "core" / advisory["system_id"] / "current.yaml", default={}) or {}
|
||||
@@ -70,6 +96,11 @@ def _build_run_bundle(
|
||||
browser_refs: List[str],
|
||||
container_log_refs: List[str],
|
||||
request_log_refs: List[str],
|
||||
compose_refs: List[str],
|
||||
browser_evidence: Dict[str, Any],
|
||||
timeline: List[Dict[str, Any]],
|
||||
started_at: str,
|
||||
finished_at: str,
|
||||
blocked_reason: str | None,
|
||||
) -> Dict[str, Any]:
|
||||
return {
|
||||
@@ -85,15 +116,174 @@ def _build_run_bundle(
|
||||
"baseline_refs": baseline_refs,
|
||||
"attack_steps": attack_steps,
|
||||
"browser_refs": browser_refs,
|
||||
"browser_evidence": browser_evidence,
|
||||
"container_log_refs": container_log_refs,
|
||||
"request_log_refs": request_log_refs,
|
||||
"timeline": [],
|
||||
"started_at": isoformat(now_utc()),
|
||||
"finished_at": isoformat(now_utc()),
|
||||
"compose_refs": compose_refs,
|
||||
"timeline": timeline,
|
||||
"started_at": started_at,
|
||||
"finished_at": finished_at,
|
||||
"blocked_reason": blocked_reason,
|
||||
}
|
||||
|
||||
|
||||
def _dry_run_case_plan(advisory: Dict[str, Any], profile: Dict[str, Any], run_id: str) -> Dict[str, Any]:
|
||||
provision_result = provision.prepare(profile, CASE_RUNS_DIR / run_id, dry_run=True)
|
||||
return {
|
||||
"run_id": run_id,
|
||||
"system_id": advisory["system_id"],
|
||||
"advisory_id": advisory["canonical_id"],
|
||||
"repro_profile_id": profile["profile_id"],
|
||||
"verification_mode": profile.get("verification_mode", "synthetic"),
|
||||
"artifact_mode": profile.get("artifact_mode", profile.get("provisioning_mode", "synthetic")),
|
||||
"browser_required": bool(profile.get("browser_assertions", {}).get("required")),
|
||||
"baseline_urls": profile.get("baseline_urls", []),
|
||||
"compose_services": sorted(profile.get("services", {}).keys()),
|
||||
"seed_actions": profile.get("seed_actions", []),
|
||||
"attack_actions": profile.get("attack_actions", []),
|
||||
"compose_preview": provision_result.get("compose_preview", {}),
|
||||
"note": "dry-run only; no bundle, report, compose file, or registry update was written",
|
||||
}
|
||||
|
||||
|
||||
def _execute_case(canonical_id: str, run_id: str | None = None, dry_run: bool = False, sync_outputs: bool = True) -> Dict[str, Any]:
|
||||
advisory = _load_advisory(canonical_id)
|
||||
profile = _resolve_profile(advisory)
|
||||
resolved_run_id = run_id or _compose_run_id(advisory)
|
||||
if dry_run:
|
||||
return _dry_run_case_plan(advisory, profile, resolved_run_id)
|
||||
|
||||
started_at = isoformat(now_utc())
|
||||
timeline: List[Dict[str, Any]] = []
|
||||
_timeline_event(timeline, "select-advisory", "completed", advisory["canonical_id"])
|
||||
_timeline_event(timeline, "resolve-repro-profile", "completed", profile["profile_id"])
|
||||
|
||||
run_dir = _run_dir(resolved_run_id)
|
||||
provision_result = provision.prepare(profile, run_dir, dry_run=False)
|
||||
_timeline_event(
|
||||
timeline,
|
||||
"provision-compose-environment",
|
||||
provision_result.get("status", "unknown"),
|
||||
provision_result.get("blocked_reason", ""),
|
||||
)
|
||||
allow_runtime_steps = provision_result.get("status") not in {"blocked-artifact"}
|
||||
browser_required = bool(profile.get("browser_assertions", {}).get("required"))
|
||||
|
||||
baseline_payload = {"observations": []}
|
||||
if profile.get("baseline_urls") and allow_runtime_steps:
|
||||
baseline_payload = baseline.collect(profile, run_dir)
|
||||
_timeline_event(timeline, "baseline-snapshot", "completed", f"urls={len(profile.get('baseline_urls', []))}")
|
||||
else:
|
||||
_timeline_event(timeline, "baseline-snapshot", "skipped", "no baseline urls or provisioning blocked")
|
||||
|
||||
baseline_browser = {"required": browser_required, "present": False, "refs": []}
|
||||
if browser_required and allow_runtime_steps and profile.get("baseline_urls"):
|
||||
baseline_browser = browser.capture(profile["baseline_urls"][0], run_dir, prefix="baseline")
|
||||
_timeline_event(
|
||||
timeline,
|
||||
"browser-replay-before-attack",
|
||||
"completed" if baseline_browser.get("present") else "failed",
|
||||
baseline_browser.get("reason", ""),
|
||||
)
|
||||
elif browser_required:
|
||||
_timeline_event(timeline, "browser-replay-before-attack", "skipped", "baseline browser capture unavailable")
|
||||
|
||||
attack_payload = {"steps": []}
|
||||
if allow_runtime_steps:
|
||||
attack_payload = attack.run_attack(profile, advisory, run_dir, dry_run=False)
|
||||
attack_failed = any(step.get("status") == "failed" for step in attack_payload.get("steps", []))
|
||||
_timeline_event(
|
||||
timeline,
|
||||
"controlled-attack-chain",
|
||||
"failed" if attack_failed else "completed",
|
||||
f"steps={len(attack_payload.get('steps', []))}",
|
||||
)
|
||||
else:
|
||||
_timeline_event(timeline, "controlled-attack-chain", "skipped", "provisioning blocked")
|
||||
|
||||
proof_browser = {"required": browser_required, "present": False, "refs": []}
|
||||
if browser_required and allow_runtime_steps and profile.get("baseline_urls"):
|
||||
proof_browser = browser.capture(profile["baseline_urls"][0], run_dir, prefix="proof")
|
||||
_timeline_event(
|
||||
timeline,
|
||||
"browser-replay-after-attack",
|
||||
"completed" if proof_browser.get("present") else "failed",
|
||||
proof_browser.get("reason", ""),
|
||||
)
|
||||
elif browser_required:
|
||||
_timeline_event(timeline, "browser-replay-after-attack", "skipped", "proof browser capture unavailable")
|
||||
|
||||
compose_path = Path(provision_result["compose_path"])
|
||||
container_logs = evidence.collect_container_logs(run_dir, compose_path) if compose_path.exists() and allow_runtime_steps else []
|
||||
_timeline_event(
|
||||
timeline,
|
||||
"collect-logs-and-evidence",
|
||||
"completed" if allow_runtime_steps else "skipped",
|
||||
f"container_logs={len(container_logs)}",
|
||||
)
|
||||
|
||||
browser_present = bool(baseline_browser.get("present")) and bool(proof_browser.get("present"))
|
||||
browser_payload = {
|
||||
"required": browser_required,
|
||||
"present": browser_present,
|
||||
"refs": baseline_browser.get("refs", []) + proof_browser.get("refs", []),
|
||||
"baseline_refs": baseline_browser.get("refs", []),
|
||||
"proof_refs": proof_browser.get("refs", []),
|
||||
"baseline_title": baseline_browser.get("page_title"),
|
||||
"proof_title": proof_browser.get("page_title"),
|
||||
}
|
||||
|
||||
blocked_reason = provision_result.get("blocked_reason")
|
||||
if browser_required and not browser_present:
|
||||
blocked_reason = blocked_reason or baseline_browser.get("reason") or proof_browser.get("reason") or "browser evidence incomplete"
|
||||
|
||||
verification_mode = profile.get("verification_mode", "synthetic")
|
||||
artifact_mode = profile.get("artifact_mode", profile.get("provisioning_mode", "synthetic"))
|
||||
verification_status = "triage-manual"
|
||||
if provision_result.get("status") == "blocked-artifact":
|
||||
verification_status = "blocked-artifact"
|
||||
elif browser_required and not browser_present:
|
||||
verification_status = "triage-manual"
|
||||
elif any(step.get("status") == "failed" for step in attack_payload.get("steps", [])):
|
||||
verification_status = "triage-manual"
|
||||
elif artifact_mode == "synthetic":
|
||||
verification_status = "verified-synthetic"
|
||||
else:
|
||||
verification_status = "verified-real"
|
||||
|
||||
finished_at = isoformat(now_utc())
|
||||
bundle = _build_run_bundle(
|
||||
advisory=advisory,
|
||||
profile=profile,
|
||||
run_id=resolved_run_id,
|
||||
verification_status=verification_status,
|
||||
verification_mode=verification_mode,
|
||||
artifact_mode=artifact_mode,
|
||||
baseline_refs=_existing_refs(run_dir / "logs" / "baseline.json"),
|
||||
attack_steps=attack_payload.get("steps", []),
|
||||
browser_refs=browser_payload["refs"],
|
||||
container_log_refs=container_logs,
|
||||
request_log_refs=_existing_refs(run_dir / "logs" / "attack.json", run_dir / "logs" / "baseline.json"),
|
||||
compose_refs=[str(compose_path)] if compose_path.exists() else [],
|
||||
browser_evidence=browser_payload,
|
||||
timeline=timeline,
|
||||
started_at=started_at,
|
||||
finished_at=finished_at,
|
||||
blocked_reason=blocked_reason,
|
||||
)
|
||||
_timeline_event(bundle["timeline"], "update-registry-and-reports", "completed", resolved_run_id)
|
||||
report_refs = render.render_run(bundle)
|
||||
bundle["report_refs"] = report_refs
|
||||
evidence.write_run_bundle(run_dir, bundle)
|
||||
ensure_dir(RUNS_DIR)
|
||||
write_json(RUNS_DIR / f"{resolved_run_id}.json", bundle)
|
||||
if sync_outputs:
|
||||
_sync_registry_outputs()
|
||||
else:
|
||||
render.render_dashboard()
|
||||
return bundle
|
||||
|
||||
|
||||
def cmd_catalog_sync(args) -> int:
|
||||
summary = catalog.sync_catalog(write_profiles=True, write_repro_map=True)
|
||||
print(summary)
|
||||
@@ -158,66 +348,8 @@ def cmd_verify(args) -> int:
|
||||
|
||||
|
||||
def cmd_run_case(args) -> int:
|
||||
advisory = _load_advisory(args.case)
|
||||
profile = _resolve_profile(advisory)
|
||||
run_id = args.run_id or _compose_run_id(advisory)
|
||||
run_dir = _run_dir(run_id)
|
||||
|
||||
provision_result = provision.prepare(profile, run_dir, dry_run=args.dry_run)
|
||||
allow_runtime_steps = provision_result.get("status") not in {"blocked-artifact"}
|
||||
baseline_payload = (
|
||||
baseline.collect(profile, run_dir) if profile.get("baseline_urls") and allow_runtime_steps else {"observations": []}
|
||||
)
|
||||
attack_payload = (
|
||||
attack.run_attack(profile, advisory, run_dir, dry_run=args.dry_run) if allow_runtime_steps else {"steps": []}
|
||||
)
|
||||
|
||||
browser_payload = {"required": bool(profile.get("browser_assertions", {}).get("required")), "present": False, "refs": []}
|
||||
blocked_reason = provision_result.get("blocked_reason")
|
||||
if browser_payload["required"] and not args.dry_run and profile.get("baseline_urls") and allow_runtime_steps:
|
||||
browser_payload = browser.capture(profile["baseline_urls"][0], run_dir, prefix="proof")
|
||||
if not browser_payload.get("present"):
|
||||
blocked_reason = blocked_reason or browser_payload.get("reason")
|
||||
|
||||
compose_path = Path(provision_result["compose_path"])
|
||||
container_logs = evidence.collect_container_logs(run_dir, compose_path) if compose_path.exists() and allow_runtime_steps else []
|
||||
|
||||
verification_status = "triage-manual"
|
||||
verification_mode = profile.get("verification_mode", "synthetic")
|
||||
artifact_mode = profile.get("artifact_mode", profile.get("provisioning_mode", "synthetic"))
|
||||
if args.dry_run:
|
||||
verification_status = "triage-manual"
|
||||
blocked_reason = blocked_reason or "dry-run only"
|
||||
elif provision_result.get("status") == "blocked-artifact":
|
||||
verification_status = "blocked-artifact"
|
||||
elif browser_payload.get("required") and not browser_payload.get("present"):
|
||||
verification_status = "triage-manual"
|
||||
elif artifact_mode == "synthetic":
|
||||
verification_status = "verified-synthetic"
|
||||
else:
|
||||
verification_status = "verified-real"
|
||||
|
||||
bundle = _build_run_bundle(
|
||||
advisory=advisory,
|
||||
profile=profile,
|
||||
run_id=run_id,
|
||||
verification_status=verification_status,
|
||||
verification_mode=verification_mode,
|
||||
artifact_mode=artifact_mode,
|
||||
baseline_refs=[str(run_dir / "logs" / "baseline.json")] if baseline_payload.get("observations") else [],
|
||||
attack_steps=attack_payload.get("steps", []),
|
||||
browser_refs=browser_payload.get("refs", []),
|
||||
container_log_refs=container_logs,
|
||||
request_log_refs=[str(run_dir / "logs" / "attack.json"), str(run_dir / "logs" / "baseline.json")],
|
||||
blocked_reason=blocked_reason,
|
||||
)
|
||||
report_refs = render.render_run(bundle)
|
||||
bundle["report_refs"] = report_refs
|
||||
evidence.write_run_bundle(run_dir, bundle)
|
||||
ensure_dir(RUNS_DIR)
|
||||
write_json(RUNS_DIR / f"{run_id}.json", bundle)
|
||||
render.render_dashboard()
|
||||
print(bundle)
|
||||
result = _execute_case(args.case, run_id=args.run_id, dry_run=args.dry_run, sync_outputs=not args.dry_run)
|
||||
print(result)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -225,7 +357,9 @@ def cmd_run_system(args) -> int:
|
||||
advisories = [item for item in load_json_dir(ADVISORIES_DIR) if item.get("system_id") == args.system]
|
||||
selected = advisories[: args.limit]
|
||||
for advisory in selected:
|
||||
cmd_run_case(argparse.Namespace(case=advisory["canonical_id"], run_id=None, dry_run=args.dry_run))
|
||||
_execute_case(advisory["canonical_id"], run_id=None, dry_run=args.dry_run, sync_outputs=False)
|
||||
if selected and not args.dry_run:
|
||||
_sync_registry_outputs()
|
||||
print({"system": args.system, "count": len(selected)})
|
||||
return 0
|
||||
|
||||
@@ -237,7 +371,9 @@ def cmd_run_batch(args) -> int:
|
||||
task_queue.enqueue_from_registry(only_hotlane=args.only_hotlane, limit=args.limit)
|
||||
items = task_queue.dequeue(limit=args.limit)
|
||||
for item in items:
|
||||
cmd_run_case(argparse.Namespace(case=item["advisory_id"], run_id=None, dry_run=args.dry_run))
|
||||
_execute_case(item["advisory_id"], run_id=None, dry_run=args.dry_run, sync_outputs=False)
|
||||
if items and not args.dry_run:
|
||||
_sync_registry_outputs()
|
||||
print({"processed": len(items)})
|
||||
return 0
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户