更新: 2531 个文件 - 2026-03-17 21:00:03
这个提交包含在:
@@ -8,8 +8,8 @@ from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from lab.config import ADVISORIES_DIR, CASE_RUNS_DIR, DASHBOARD_DIR, REPRO_MAP_PATH, ROOT, RUNS_DIR, SOURCE_MAP_PATH
|
||||
from lab.repro import load_profiles
|
||||
from lab.utils import ensure_dir, isoformat, load_json_dir, now_utc, read_yaml, unique, write_json, write_text
|
||||
from lab.repro import annotate_with_latest_run, latest_runs_by_advisory, load_profiles
|
||||
from lab.utils import ensure_dir, isoformat, load_json_dir, now_utc, read_json, read_yaml, unique, write_json, write_text
|
||||
|
||||
|
||||
TEMPLATES_DIR = ROOT / "scripts" / "lab" / "dashboard_templates"
|
||||
@@ -156,9 +156,12 @@ def _profile_meta(profile: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"cleanup_policy": profile.get("cleanup_policy"),
|
||||
"artifact_source": profile.get("artifact_source", {}),
|
||||
"success_criteria": profile.get("success_criteria", []),
|
||||
"success_assertions": profile.get("success_assertions", []),
|
||||
"seed_actions": profile.get("seed_actions", []),
|
||||
"attack_actions": profile.get("attack_actions", []),
|
||||
"browser_assertions": profile.get("browser_assertions", {}),
|
||||
"runner_id": profile.get("runner_id"),
|
||||
"fixture_path": profile.get("fixture_path"),
|
||||
"allowed_target_types": profile.get("allowed_target_types", []),
|
||||
"required_services": profile.get("required_services", []),
|
||||
}
|
||||
@@ -211,6 +214,157 @@ def _status_label(value: str | None) -> str:
|
||||
return STATUS_LABELS.get(value or "", value or "-")
|
||||
|
||||
|
||||
def _family_name(advisory: Dict[str, Any], profile_map: Dict[str, Dict[str, Any]]) -> str:
|
||||
profile = profile_map.get(advisory.get("repro_profile_id") or "", {})
|
||||
return profile.get("vuln_family") or advisory.get("repro_profile_id") or "unknown"
|
||||
|
||||
|
||||
def _latest_advisories(advisories: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
run_map = latest_runs_by_advisory()
|
||||
return [annotate_with_latest_run(item, run_map.get(item.get("canonical_id"))) for item in advisories]
|
||||
|
||||
|
||||
def _build_completeness(
|
||||
advisories: List[Dict[str, Any]],
|
||||
runs: List[Dict[str, Any]],
|
||||
profile_map: Dict[str, Dict[str, Any]],
|
||||
run_summary: Dict[str, Any],
|
||||
) -> Dict[str, Any]:
|
||||
latest_statuses: Dict[str, int] = {}
|
||||
historical_statuses: Dict[str, int] = {}
|
||||
systems: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
for item in advisories:
|
||||
status = item.get("verification_status", "triage-manual")
|
||||
latest_statuses[status] = latest_statuses.get(status, 0) + 1
|
||||
system = systems.setdefault(
|
||||
item["system_id"],
|
||||
{
|
||||
"system_id": item["system_id"],
|
||||
"display_name": item.get("display_name", item["system_id"]),
|
||||
"total": 0,
|
||||
"verified_real": 0,
|
||||
"verified_synthetic": 0,
|
||||
"blocked": 0,
|
||||
"manual": 0,
|
||||
"families": {},
|
||||
},
|
||||
)
|
||||
system["total"] += 1
|
||||
family = _family_name(item, profile_map)
|
||||
family_entry = system["families"].setdefault(
|
||||
family,
|
||||
{"family": family, "total": 0, "verified_real": 0, "verified_synthetic": 0, "blocked": 0, "manual": 0},
|
||||
)
|
||||
family_entry["total"] += 1
|
||||
if status == "verified-real":
|
||||
system["verified_real"] += 1
|
||||
family_entry["verified_real"] += 1
|
||||
elif status == "verified-synthetic":
|
||||
system["verified_synthetic"] += 1
|
||||
family_entry["verified_synthetic"] += 1
|
||||
elif status.startswith("blocked-"):
|
||||
system["blocked"] += 1
|
||||
family_entry["blocked"] += 1
|
||||
else:
|
||||
system["manual"] += 1
|
||||
family_entry["manual"] += 1
|
||||
|
||||
for item in runs:
|
||||
status = item.get("verification_status", "triage-manual")
|
||||
historical_statuses[status] = historical_statuses.get(status, 0) + 1
|
||||
|
||||
systems_list = []
|
||||
for entry in sorted(systems.values(), key=lambda value: value["system_id"]):
|
||||
entry["families"] = sorted(entry["families"].values(), key=lambda value: value["family"])
|
||||
systems_list.append(entry)
|
||||
|
||||
advisory_total = len(advisories)
|
||||
verified_real = latest_statuses.get("verified-real", 0)
|
||||
verified_synthetic = latest_statuses.get("verified-synthetic", 0)
|
||||
blocked = sum(count for key, count in latest_statuses.items() if key.startswith("blocked-"))
|
||||
manual = advisory_total - verified_real - verified_synthetic - blocked
|
||||
complete = advisory_total > 0 and advisory_total == verified_real
|
||||
return {
|
||||
"generated_at": isoformat(now_utc()),
|
||||
"advisory_total": advisory_total,
|
||||
"latest_statuses": latest_statuses,
|
||||
"historical_statuses": historical_statuses,
|
||||
"verified_real": verified_real,
|
||||
"verified_synthetic": verified_synthetic,
|
||||
"blocked": blocked,
|
||||
"manual": manual,
|
||||
"verified_ratio": round((verified_real / advisory_total) * 100, 1) if advisory_total else 0.0,
|
||||
"complete": complete,
|
||||
"systems": systems_list,
|
||||
"ingest_health": {
|
||||
"failure_count": len(run_summary.get("failures", []) or []),
|
||||
"failures": run_summary.get("failures", []) or [],
|
||||
},
|
||||
"historical_blockers": [
|
||||
"Docker daemon unavailable caused provision-compose-environment blocked-artifact.",
|
||||
"Family profiles previously used note-only attack runners and dry-run placeholders.",
|
||||
"Baseline and browser steps were skipped when environment readiness was not enforced.",
|
||||
"Latest completeness now uses one advisory -> latest run semantics instead of historical run piles.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _write_testing_completeness_report(completeness: Dict[str, Any]) -> None:
|
||||
lines = [
|
||||
"# 全库 Advisory 完整度报告",
|
||||
"",
|
||||
f"- 生成时间: `{completeness['generated_at']}`",
|
||||
f"- 最新 advisory 完整度: `{completeness['verified_real']}/{completeness['advisory_total']}` `verified-real`",
|
||||
f"- 合成验证数量: `{completeness['verified_synthetic']}`",
|
||||
f"- 阻塞数量: `{completeness['blocked']}`",
|
||||
f"- 人工/待补证据数量: `{completeness['manual']}`",
|
||||
f"- 完整度百分比: `{completeness['verified_ratio']}%`",
|
||||
"",
|
||||
"## 系统覆盖矩阵",
|
||||
"",
|
||||
"| 系统 | 总数 | verified-real | verified-synthetic | blocked | manual | family 覆盖 |",
|
||||
"| --- | ---: | ---: | ---: | ---: | ---: | --- |",
|
||||
]
|
||||
for system in completeness["systems"]:
|
||||
family_text = ", ".join(
|
||||
f"{item['family']}({item['verified_real']}/{item['total']})" for item in system["families"]
|
||||
)
|
||||
lines.append(
|
||||
f"| {system['system_id']} | {system['total']} | {system['verified_real']} | {system['verified_synthetic']} | {system['blocked']} | {system['manual']} | {family_text} |"
|
||||
)
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"## 历史阻塞项修复纪要",
|
||||
"",
|
||||
]
|
||||
)
|
||||
for item in completeness.get("historical_blockers", []):
|
||||
lines.append(f"- {item}")
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"## Ingest / Source 健康度",
|
||||
"",
|
||||
f"- source failures: `{completeness['ingest_health']['failure_count']}`",
|
||||
]
|
||||
)
|
||||
for item in completeness["ingest_health"].get("failures", []):
|
||||
lines.append(f"- {item}")
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"## 剩余风险说明",
|
||||
"",
|
||||
"- 本报告按 advisory 的最新 run 计算;历史失败 run 仅保留审计价值,不再污染完整度数字。",
|
||||
"- `browser_required=true` 的案例必须同时存在基线与攻击后浏览器证据,缺失则不会进入 `verified-real`。",
|
||||
"- source collector 健康度单独计数;只有当 failures 归零时,报告与 dashboard 才算真正全绿。",
|
||||
]
|
||||
)
|
||||
write_text(ROOT / "docs" / "testing-completeness-report.md", "\n".join(lines))
|
||||
|
||||
|
||||
def _build_architecture_data(summary: Dict[str, Any], source_map: Dict[str, Any], repro_map: Dict[str, Any]) -> Dict[str, Any]:
|
||||
source_systems = source_map.get("systems", []) or []
|
||||
repro_by_system = {item.get("system_id"): item for item in (repro_map.get("systems", []) or []) if item.get("system_id")}
|
||||
@@ -225,6 +379,7 @@ def _build_architecture_data(summary: Dict[str, Any], source_map: Dict[str, Any]
|
||||
_link("旧版工作台", "/legacy/index.html", "保留的 legacy 回退入口。"),
|
||||
_link("项目功能文档", "/docs/project-features.html", "项目能力、目录结构与自动化链路总览。"),
|
||||
_link("前端设计文档", "/docs/frontend-dashboard-design.html", "当前本地工作台的交互与视觉规范。"),
|
||||
_link("完整度报告", "/docs/testing-completeness-report.html", "89 条 advisory 的最新完整度中文报告。"),
|
||||
_link("安全编码索引", "/docs/secure-code-index.html", "secure-code 修复库本地镜像。"),
|
||||
_link("仓库入口镜像", "/docs/root-readme.html", "仓库根 README 的本地镜像。"),
|
||||
_link("授权模型", "/docs/authorization-model.html", "允许目标范围、全局原则与记录要求。"),
|
||||
@@ -237,6 +392,7 @@ def _build_architecture_data(summary: Dict[str, Any], source_map: Dict[str, Any]
|
||||
|
||||
data_links = [
|
||||
_link("summary.json", "/summary.json", "全局摘要、状态分布和最近失败。"),
|
||||
_link("completeness.json", "/data/completeness.json", "最新 advisory 完整度、系统/family 进度与 ingest 健康度。"),
|
||||
_link("runs.json", "/runs.json", "最近 run 的结构化详情。"),
|
||||
_link("systems.json", "/systems.json", "系统级覆盖与浏览器证据摘要。"),
|
||||
_link("advisories.json", "/advisories.json", "漏洞条目元数据与来源。"),
|
||||
@@ -736,6 +892,12 @@ def _write_dashboard_docs(architecture: Dict[str, Any]) -> None:
|
||||
(ROOT / "08-threat-intel" / "generated" / "coverage-matrix.md").read_text(encoding="utf-8"),
|
||||
"工作台内置镜像页:当前覆盖矩阵生成结果。",
|
||||
),
|
||||
(
|
||||
"testing-completeness-report.html",
|
||||
"中文完整度报告",
|
||||
(ROOT / "docs" / "testing-completeness-report.md").read_text(encoding="utf-8"),
|
||||
"工作台内置镜像页:89 条 advisory 最新完整度、family 矩阵与 ingest 健康度。",
|
||||
),
|
||||
]
|
||||
|
||||
manifest_body = LOVART_VENDOR_MANIFEST.read_text(encoding="utf-8") if LOVART_VENDOR_MANIFEST.exists() else "{}"
|
||||
@@ -981,16 +1143,18 @@ def render_dashboard() -> Dict[str, str]:
|
||||
ensure_dir(DASHBOARD_DIR)
|
||||
advisory_records = load_json_dir(ADVISORIES_DIR)
|
||||
runs = load_json_dir(RUNS_DIR)
|
||||
run_summary = read_json(ROOT / "08-threat-intel" / "generated" / "run-summary.json", default={}) or {}
|
||||
source_map = read_yaml(SOURCE_MAP_PATH, default={}) or {}
|
||||
repro_map = read_yaml(REPRO_MAP_PATH, default={}) or {}
|
||||
source_system_map = {item["system_id"]: item for item in source_map.get("systems", []) if item.get("system_id")}
|
||||
advisory_map = {item["canonical_id"]: item for item in advisory_records if item.get("canonical_id")}
|
||||
merged_advisories = _latest_advisories(advisory_records)
|
||||
advisory_map = {item["canonical_id"]: item for item in merged_advisories if item.get("canonical_id")}
|
||||
profile_map = load_profiles()
|
||||
|
||||
_sync_run_bundles(runs)
|
||||
|
||||
systems: Dict[str, Dict[str, Any]] = {}
|
||||
for advisory in advisory_records:
|
||||
for advisory in merged_advisories:
|
||||
system = systems.setdefault(
|
||||
advisory["system_id"],
|
||||
{
|
||||
@@ -1007,6 +1171,7 @@ def render_dashboard() -> Dict[str, str]:
|
||||
"category": source_system_map.get(advisory["system_id"], {}).get("category", advisory.get("category")),
|
||||
"tier": source_system_map.get(advisory["system_id"], {}).get("tier"),
|
||||
"output_dir": source_system_map.get(advisory["system_id"], {}).get("output_dir"),
|
||||
"families": {},
|
||||
},
|
||||
)
|
||||
system["total"] += 1
|
||||
@@ -1019,7 +1184,7 @@ def render_dashboard() -> Dict[str, str]:
|
||||
system["blocked"] += 1
|
||||
else:
|
||||
system["manual"] += 1
|
||||
browser = advisory.get("browser_evidence", {})
|
||||
browser = advisory.get("browser_evidence") or {}
|
||||
if browser.get("required"):
|
||||
system["browser_required"] += 1
|
||||
if browser.get("present"):
|
||||
@@ -1027,6 +1192,13 @@ def render_dashboard() -> Dict[str, str]:
|
||||
latest = advisory.get("updated_at") or advisory.get("published_at") or ""
|
||||
if latest > system["latest_update"]:
|
||||
system["latest_update"] = latest
|
||||
family = _family_name(advisory, profile_map)
|
||||
family_entry = system["families"].setdefault(family, {"family": family, "total": 0, "verified_real": 0, "manual": 0})
|
||||
family_entry["total"] += 1
|
||||
if status == "verified-real":
|
||||
family_entry["verified_real"] += 1
|
||||
elif status not in {"verified-synthetic"}:
|
||||
family_entry["manual"] += 1
|
||||
|
||||
recent_runs = sorted(runs, key=lambda item: item.get("finished_at") or "", reverse=True)[:100]
|
||||
decorated_runs: List[Dict[str, Any]] = []
|
||||
@@ -1079,32 +1251,57 @@ def render_dashboard() -> Dict[str, str]:
|
||||
|
||||
summary = {
|
||||
"generated_at": isoformat(now_utc()),
|
||||
"advisory_count": len(advisory_records),
|
||||
"advisory_count": len(merged_advisories),
|
||||
"run_count": len(runs),
|
||||
"statuses": {},
|
||||
"run_statuses": {},
|
||||
"recent_failures": [],
|
||||
}
|
||||
for item in runs:
|
||||
for item in merged_advisories:
|
||||
status = item.get("verification_status", "triage-manual")
|
||||
summary["statuses"][status] = summary["statuses"].get(status, 0) + 1
|
||||
summary["systems"] = sorted(systems.values(), key=lambda entry: (-entry["total"], entry["system_id"]))
|
||||
for item in runs:
|
||||
status = item.get("verification_status", "triage-manual")
|
||||
summary["run_statuses"][status] = summary["run_statuses"].get(status, 0) + 1
|
||||
summary["systems"] = sorted(
|
||||
[
|
||||
{
|
||||
**entry,
|
||||
"families": sorted(entry["families"].values(), key=lambda value: value["family"]),
|
||||
}
|
||||
for entry in systems.values()
|
||||
],
|
||||
key=lambda entry: (-entry["total"], entry["system_id"]),
|
||||
)
|
||||
summary["recent_failures"] = [
|
||||
{
|
||||
"run_id": item["run_id"],
|
||||
"advisory_id": item["advisory_id"],
|
||||
"run_id": item.get("last_run_id"),
|
||||
"advisory_id": item["canonical_id"],
|
||||
"status": item.get("verification_status"),
|
||||
"title": item.get("advisory_meta", {}).get("title"),
|
||||
"title": item.get("title"),
|
||||
"blocked_reason": item.get("blocked_reason"),
|
||||
}
|
||||
for item in decorated_runs
|
||||
for item in sorted(merged_advisories, key=lambda value: value.get("updated_at") or value.get("published_at") or "", reverse=True)
|
||||
if item.get("verification_status") in {"triage-manual", "blocked-artifact", "blocked-destructive"}
|
||||
][:20]
|
||||
completeness = _build_completeness(merged_advisories, runs, profile_map, run_summary)
|
||||
summary["completeness"] = {
|
||||
"advisory_total": completeness["advisory_total"],
|
||||
"verified_real": completeness["verified_real"],
|
||||
"verified_synthetic": completeness["verified_synthetic"],
|
||||
"blocked": completeness["blocked"],
|
||||
"manual": completeness["manual"],
|
||||
"verified_ratio": completeness["verified_ratio"],
|
||||
"complete": completeness["complete"],
|
||||
}
|
||||
|
||||
write_json(DASHBOARD_DIR / "summary.json", summary)
|
||||
write_json(DASHBOARD_DIR / "runs.json", decorated_runs)
|
||||
write_json(DASHBOARD_DIR / "systems.json", summary["systems"])
|
||||
write_json(DASHBOARD_DIR / "advisories.json", {key: _advisory_meta(value) for key, value in advisory_map.items()})
|
||||
write_json(DASHBOARD_DIR / "profiles.json", {key: _profile_meta(value) for key, value in profile_map.items()})
|
||||
write_json(DASHBOARD_DIR / "data" / "completeness.json", completeness)
|
||||
_write_testing_completeness_report(completeness)
|
||||
architecture = _build_architecture_data(summary, source_map, repro_map)
|
||||
write_json(DASHBOARD_DIR / "architecture.json", architecture)
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户