更新: 89 个文件 - 2026-03-17 02:00:01
这个提交包含在:
@@ -7,9 +7,9 @@ import shutil
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from lab.config import ADVISORIES_DIR, CASE_RUNS_DIR, DASHBOARD_DIR, ROOT, RUNS_DIR
|
||||
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, unique, write_json, write_text
|
||||
from lab.utils import ensure_dir, isoformat, load_json_dir, now_utc, read_yaml, unique, write_json, write_text
|
||||
|
||||
|
||||
TEMPLATES_DIR = ROOT / "scripts" / "lab" / "dashboard_templates"
|
||||
@@ -17,21 +17,44 @@ LOVART_TEMPLATE_DIR = TEMPLATES_DIR / "lovart"
|
||||
LEGACY_TEMPLATE_DIR = TEMPLATES_DIR / "legacy"
|
||||
LOVART_VENDOR_MANIFEST = LOVART_TEMPLATE_DIR / "vendor" / "source-manifest.json"
|
||||
ROOT_JSON_FILES = ["summary.json", "runs.json", "systems.json", "advisories.json", "profiles.json"]
|
||||
ROOT_JSON_FILES.append("architecture.json")
|
||||
|
||||
CATEGORY_LABELS = {
|
||||
"cms": "CMS / 内容平台",
|
||||
"ecommerce": "电商系统",
|
||||
"frameworks": "Web 框架与运行时",
|
||||
"servers": "服务器与边界层",
|
||||
"platforms": "开源平台与后台系统",
|
||||
}
|
||||
|
||||
TIER_LABELS = {
|
||||
"history-full": "历史全量",
|
||||
"rolling-24m": "近两年全量",
|
||||
}
|
||||
|
||||
STATUS_LABELS = {
|
||||
"verified-real": "真实版本已实证",
|
||||
"verified-synthetic": "合成靶场已实证",
|
||||
"blocked-artifact": "制品阻塞",
|
||||
"blocked-destructive": "破坏性风险阻塞",
|
||||
"triage-manual": "人工分诊",
|
||||
"suspected": "仅疑似命中",
|
||||
}
|
||||
|
||||
|
||||
def mermaid_from_steps(run: Dict[str, Any]) -> str:
|
||||
lines = [
|
||||
"flowchart LR",
|
||||
'A["Select Advisory"] --> B["Resolve Repro Profile"]',
|
||||
'B --> C["Provision Compose Environment"]',
|
||||
'C --> D["Baseline Snapshot"]',
|
||||
'D --> E["Controlled Attack Steps"]',
|
||||
'E --> F["Browser Replay"]',
|
||||
'F --> G["Collect Logs and Evidence"]',
|
||||
'G --> H["Update Registry and Reports"]',
|
||||
'A["选择 Advisory"] --> B["解析 Repro Profile"]',
|
||||
'B --> C["生成 Compose 环境"]',
|
||||
'C --> D["采集基线快照"]',
|
||||
'D --> E["执行受控攻击步骤"]',
|
||||
'E --> F["浏览器回放验证"]',
|
||||
'F --> G["收集日志与证据"]',
|
||||
'G --> H["回写 Registry 与报告"]',
|
||||
]
|
||||
if run.get("blocked_reason"):
|
||||
lines.append(f'H --> I["Blocked: {run["blocked_reason"][:60]}"]')
|
||||
lines.append(f'H --> I["阻塞: {run["blocked_reason"][:60]}"]')
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@@ -157,6 +180,371 @@ def _reasoning_lines(advisory: Dict[str, Any], profile: Dict[str, Any]) -> List[
|
||||
return unique(notes)
|
||||
|
||||
|
||||
def _display_value(value: Any) -> str:
|
||||
if value is None or value == "":
|
||||
return "-"
|
||||
if isinstance(value, bool):
|
||||
return "是" if value else "否"
|
||||
if isinstance(value, (int, float)):
|
||||
return str(value)
|
||||
if isinstance(value, list):
|
||||
return "\n".join(_display_value(item) for item in value if item not in (None, "")) or "-"
|
||||
if isinstance(value, dict):
|
||||
return json.dumps(value, ensure_ascii=False, indent=2)
|
||||
return str(value)
|
||||
|
||||
|
||||
def _field(label: str, value: Any) -> Dict[str, str]:
|
||||
return {"label": label, "value": _display_value(value)}
|
||||
|
||||
|
||||
def _stat(label: str, value: Any) -> Dict[str, str]:
|
||||
return {"label": label, "value": _display_value(value)}
|
||||
|
||||
|
||||
def _link(label: str, href: str, description: str) -> Dict[str, str]:
|
||||
return {"label": label, "href": href, "description": description}
|
||||
|
||||
|
||||
def _status_label(value: str | None) -> str:
|
||||
return STATUS_LABELS.get(value or "", value or "-")
|
||||
|
||||
|
||||
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")}
|
||||
|
||||
route_links = [
|
||||
_link("正式工作台", "./index.html", "本地化主 UI,默认入口。"),
|
||||
_link("旧版工作台", "./legacy/index.html", "保留的 legacy 回退入口。"),
|
||||
_link("项目功能文档", "./docs/project-features.html", "项目能力、目录结构与自动化链路总览。"),
|
||||
_link("前端设计文档", "./docs/frontend-dashboard-design.html", "当前本地工作台的交互与视觉规范。"),
|
||||
_link("安全编码索引", "./docs/secure-code-index.html", "secure-code 修复库本地镜像。"),
|
||||
_link("README 镜像", "./docs/root-readme.html", "仓库根 README 的本地镜像。"),
|
||||
_link("授权模型", "./docs/authorization-model.html", "允许目标范围、全局原则与记录要求。"),
|
||||
_link("source-map 真值", "./docs/source-map.html", "系统覆盖、来源和输出目录真值。"),
|
||||
_link("repro-map 真值", "./docs/repro-map.html", "复现族路由、浏览器要求和日志策略。"),
|
||||
_link("覆盖矩阵", "./docs/coverage-matrix.html", "自动生成覆盖摘要的本地镜像。"),
|
||||
_link("设计来源清单", "./docs/design-source.html", "Lovart 模板本地 vendor manifest。"),
|
||||
_link("架构库镜像", "./docs/architecture-library.html", "当前架构库的结构化镜像页。"),
|
||||
]
|
||||
|
||||
data_links = [
|
||||
_link("summary.json", "./summary.json", "全局摘要、状态分布和最近失败。"),
|
||||
_link("runs.json", "./runs.json", "最近 run 的结构化详情。"),
|
||||
_link("systems.json", "./systems.json", "系统级覆盖与浏览器证据摘要。"),
|
||||
_link("advisories.json", "./advisories.json", "advisory 元数据与来源。"),
|
||||
_link("profiles.json", "./profiles.json", "repro profile 元数据。"),
|
||||
_link("architecture.json", "./architecture.json", "当前架构库结构化 JSON。"),
|
||||
]
|
||||
|
||||
category_items: List[Dict[str, Any]] = []
|
||||
family_counts: Dict[str, int] = {}
|
||||
tier_counts = {"history-full": 0, "rolling-24m": 0}
|
||||
for system in source_systems:
|
||||
tier = system.get("tier", "rolling-24m")
|
||||
tier_counts[tier] = tier_counts.get(tier, 0) + 1
|
||||
repro = repro_by_system.get(system.get("system_id"), {})
|
||||
family = repro.get("default_repro_family") or "未定义"
|
||||
family_counts[family] = family_counts.get(family, 0) + 1
|
||||
|
||||
for category_id in sorted(CATEGORY_LABELS, key=lambda item: CATEGORY_LABELS[item]):
|
||||
systems_in_category = [item for item in source_systems if item.get("category") == category_id]
|
||||
if not systems_in_category:
|
||||
continue
|
||||
history_full = sum(1 for item in systems_in_category if item.get("tier") == "history-full")
|
||||
rolling = sum(1 for item in systems_in_category if item.get("tier") == "rolling-24m")
|
||||
system_nodes: List[Dict[str, Any]] = []
|
||||
for system in sorted(systems_in_category, key=lambda item: item.get("display_name", item.get("system_id", ""))):
|
||||
repro = repro_by_system.get(system.get("system_id"), {})
|
||||
official_sources = system.get("official_sources", []) or []
|
||||
ecosystem_sources = system.get("ecosystem_sources", []) or []
|
||||
research_sources = system.get("research_sources", []) or []
|
||||
system_nodes.append(
|
||||
{
|
||||
"title": f"{system.get('display_name', system.get('system_id'))} ({system.get('system_id')})",
|
||||
"summary": f"{TIER_LABELS.get(system.get('tier'), system.get('tier'))} · {', '.join(system.get('advisory_modes', [])) or '未定义模式'}",
|
||||
"open": False,
|
||||
"badges": [
|
||||
TIER_LABELS.get(system.get("tier"), system.get("tier", "-")),
|
||||
f"官方源 {len(official_sources)}",
|
||||
f"生态源 {len(ecosystem_sources)}",
|
||||
f"研究源 {len(research_sources)}",
|
||||
],
|
||||
"fields": [
|
||||
_field("输出目录", system.get("output_dir")),
|
||||
_field("Advisory 模式", system.get("advisory_modes", [])),
|
||||
_field("Secure-Code 主题", system.get("secure_code_topics", [])),
|
||||
_field("CPE 关键字", system.get("cpe_keys", [])),
|
||||
_field("GHSA 关键字", system.get("ghsa_keywords", [])),
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
"title": "来源配置",
|
||||
"summary": "官方、生态权威与研究补充来源。",
|
||||
"open": False,
|
||||
"fields": [
|
||||
_field("官方来源", [entry.get("name") for entry in official_sources]),
|
||||
_field("生态来源", [entry.get("name") for entry in ecosystem_sources]),
|
||||
_field("研究来源", [entry.get("name") for entry in research_sources]),
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "复现默认值",
|
||||
"summary": "repro-map 中的默认攻击族、浏览器要求和日志策略。",
|
||||
"open": False,
|
||||
"fields": [
|
||||
_field("默认漏洞家族", repro.get("default_repro_family")),
|
||||
_field("浏览器默认要求", repro.get("browser_required_default")),
|
||||
_field("优先制品模式", repro.get("provisioning_mode_preference", [])),
|
||||
_field("种子策略", repro.get("seed_strategy")),
|
||||
_field("日志采集器", repro.get("log_collectors", [])),
|
||||
_field("报告模板", repro.get("report_template")),
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
category_items.append(
|
||||
{
|
||||
"title": CATEGORY_LABELS.get(category_id, category_id),
|
||||
"summary": f"{len(systems_in_category)} 个系统 · 历史全量 {history_full} · 近两年全量 {rolling}",
|
||||
"open": False,
|
||||
"stats": [
|
||||
_stat("系统数", len(systems_in_category)),
|
||||
_stat("历史全量", history_full),
|
||||
_stat("近两年全量", rolling),
|
||||
],
|
||||
"items": system_nodes,
|
||||
}
|
||||
)
|
||||
|
||||
repro_family_nodes = [
|
||||
{
|
||||
"title": family,
|
||||
"summary": f"默认路由到该 family 的系统数:{count}",
|
||||
"open": False,
|
||||
"fields": [_field("系统数量", count)],
|
||||
}
|
||||
for family, count in sorted(family_counts.items(), key=lambda item: (-item[1], item[0]))
|
||||
]
|
||||
|
||||
recent_failure_nodes = [
|
||||
{
|
||||
"title": item.get("title") or item.get("advisory_id") or item.get("run_id"),
|
||||
"summary": item.get("blocked_reason") or "无额外阻塞说明。",
|
||||
"open": False,
|
||||
"badges": [_status_label(item.get("status"))],
|
||||
"fields": [
|
||||
_field("运行 ID", item.get("run_id")),
|
||||
_field("漏洞条目", item.get("advisory_id")),
|
||||
_field("状态", _status_label(item.get("status"))),
|
||||
_field("阻塞原因", item.get("blocked_reason")),
|
||||
],
|
||||
}
|
||||
for item in summary.get("recent_failures", [])
|
||||
]
|
||||
|
||||
status_nodes = [
|
||||
{
|
||||
"title": _status_label(status),
|
||||
"summary": f"当前累计 {count} 条。",
|
||||
"open": False,
|
||||
"fields": [
|
||||
_field("状态编码", status),
|
||||
_field("数量", count),
|
||||
],
|
||||
}
|
||||
for status, count in sorted(summary.get("statuses", {}).items(), key=lambda item: (-item[1], item[0]))
|
||||
]
|
||||
|
||||
return {
|
||||
"generated_at": summary.get("generated_at"),
|
||||
"title": "当前架构库",
|
||||
"summary": "工作台、控制面、数据层、授权边界与系统覆盖的当前真值视图。",
|
||||
"sections": [
|
||||
{
|
||||
"title": "仓库定位与当前状态",
|
||||
"summary": "授权攻防实验与研究知识库;仅适用于自有资产、本地靶场和明确授权目标。",
|
||||
"open": True,
|
||||
"badges": ["LAB ONLY", "AUTHORIZED TARGETS ONLY", "非生产安全基线"],
|
||||
"stats": [
|
||||
_stat("纳管系统", len(source_systems)),
|
||||
_stat("历史全量系统", tier_counts.get("history-full", 0)),
|
||||
_stat("近两年全量系统", tier_counts.get("rolling-24m", 0)),
|
||||
_stat("当前运行", summary.get("run_count", 0)),
|
||||
_stat("当前漏洞条目", summary.get("advisory_count", 0)),
|
||||
],
|
||||
"fields": [
|
||||
_field("仓库根目录", str(ROOT)),
|
||||
_field("默认本地地址", "http://127.0.0.1:8734/"),
|
||||
_field("自动刷新周期", "5 秒"),
|
||||
_field("生成时间", summary.get("generated_at")),
|
||||
],
|
||||
"links": route_links[:4],
|
||||
},
|
||||
{
|
||||
"title": "授权边界与目标模型",
|
||||
"summary": "所有实验都绑定到本地、自建公网或明确授权目标,不面向无关第三方资产。",
|
||||
"open": True,
|
||||
"stats": [
|
||||
_stat("允许目标类型", 3),
|
||||
_stat("禁止类型", 1),
|
||||
],
|
||||
"fields": [
|
||||
_field("允许目标", ["lab-local", "lab-public", "authorized-third-party"]),
|
||||
_field("禁止目标", ["out-of-scope", "无归属证明目标", "公共知名站点", "泛互联网枚举"]),
|
||||
_field("全局原则", [
|
||||
"任何公网验证前先确认资产归属或授权关系。",
|
||||
"优先只读探测、最小化回显验证和低频实验。",
|
||||
"涉及账户、令牌、敏感数据和业务写入时采用最小必要动作。",
|
||||
"不做泛互联网枚举,不对无关公共站点复用同类测试。",
|
||||
]),
|
||||
],
|
||||
"links": [
|
||||
_link("授权模型镜像", "./docs/authorization-model.html", "目标分类、原则与记录要求。"),
|
||||
_link("项目 README 镜像", "./docs/root-readme.html", "仓库定位、能力矩阵与自动化入口。"),
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "控制面与自动化入口",
|
||||
"summary": "Intel 控制面负责情报入库;Lab 控制面负责本地部署、攻击验证、证据收集和看板生成。",
|
||||
"open": True,
|
||||
"items": [
|
||||
{
|
||||
"title": "情报控制面(Intel)",
|
||||
"summary": "负责 source adapter、规范化、渲染、校验和 PR 流程。",
|
||||
"open": False,
|
||||
"fields": [
|
||||
_field("CLI 入口", "python3 /Users/x/websafe/scripts/intel/main.py"),
|
||||
_field("主要命令", [
|
||||
"render",
|
||||
"validate",
|
||||
"hotlane",
|
||||
"ingest --since last-success",
|
||||
"reconcile",
|
||||
"backfill --tier history-full --dry-run",
|
||||
"open-pr --dry-run",
|
||||
]),
|
||||
_field("定时入口", [
|
||||
"scripts/intel/run-hourly.sh",
|
||||
"scripts/intel/run-nightly.sh",
|
||||
"scripts/intel/run-weekly-reconcile.sh",
|
||||
]),
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "实证控制面(Lab)",
|
||||
"summary": "负责 catalog、compose、seed、baseline、attack、browser、evidence、render 和 queue。",
|
||||
"open": False,
|
||||
"fields": [
|
||||
_field("CLI 入口", "python3 /Users/x/websafe/scripts/lab/main.py"),
|
||||
_field("主要命令", [
|
||||
"catalog sync",
|
||||
"validate",
|
||||
"run-case",
|
||||
"run-system",
|
||||
"run-batch",
|
||||
"render-run",
|
||||
"serve-dashboard --port 8734",
|
||||
"cleanup",
|
||||
"retry-failures",
|
||||
]),
|
||||
_field("关键模块", [
|
||||
"catalog/",
|
||||
"provision/",
|
||||
"compose/",
|
||||
"seed/",
|
||||
"baseline/",
|
||||
"attack/",
|
||||
"browser/",
|
||||
"evidence/",
|
||||
"render/",
|
||||
"queue/",
|
||||
]),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "数据层与本地地址",
|
||||
"summary": "Registry、生成层、run bundle 与 docs 镜像共同构成工作台的本地数据面。",
|
||||
"open": True,
|
||||
"items": [
|
||||
{
|
||||
"title": "真值层",
|
||||
"summary": "统一的 registry 与 repro/source 配置。",
|
||||
"open": False,
|
||||
"fields": [
|
||||
_field("漏洞条目 Registry", "08-threat-intel/registry/advisories/*.json"),
|
||||
_field("系统 Registry", "08-threat-intel/registry/systems/*.json"),
|
||||
_field("运行 Registry", "08-threat-intel/registry/runs/*.json"),
|
||||
_field("source-map 真值", "08-threat-intel/source-map.yaml"),
|
||||
_field("repro-map 真值", "08-threat-intel/repro-map.yaml"),
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "生成层与展示层",
|
||||
"summary": "dashboard JSON、run report、docs 镜像与本地静态 UI。",
|
||||
"open": False,
|
||||
"links": route_links + data_links,
|
||||
"fields": [
|
||||
_field("工作台根目录", "08-threat-intel/generated/dashboard/"),
|
||||
_field("运行归档根目录", "06-case-studies/generated-runs/<run-id>/"),
|
||||
_field("工作台入口", "/index.html"),
|
||||
_field("旧版入口", "/legacy/index.html"),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "系统覆盖分组",
|
||||
"summary": "基于 source-map 和 repro-map 生成的当前分组视图,可展开查看每个系统的来源、输出目录和复现默认值。",
|
||||
"open": True,
|
||||
"items": category_items,
|
||||
},
|
||||
{
|
||||
"title": "Repro 路由概览",
|
||||
"summary": "按默认漏洞家族聚合当前系统路由,帮助查看 family runner 覆盖面。",
|
||||
"open": True,
|
||||
"items": repro_family_nodes,
|
||||
},
|
||||
{
|
||||
"title": "当前生成态与阻塞概览",
|
||||
"summary": "当前 render 后的状态分布、失败摘要与最近可见阻塞。",
|
||||
"open": True,
|
||||
"stats": [
|
||||
_stat("Run 数", summary.get("run_count", 0)),
|
||||
_stat("Advisory 数", summary.get("advisory_count", 0)),
|
||||
_stat("状态类型", len(summary.get("statuses", {}))),
|
||||
_stat("最近失败", len(summary.get("recent_failures", []))),
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
"title": "状态分布",
|
||||
"summary": "verification_status 当前计数。",
|
||||
"open": False,
|
||||
"items": status_nodes,
|
||||
},
|
||||
{
|
||||
"title": "最近失败",
|
||||
"summary": "当前 dashboard 摘要里可见的失败或人工分诊样本。",
|
||||
"open": False,
|
||||
"items": recent_failure_nodes or [
|
||||
{
|
||||
"title": "暂无失败样本",
|
||||
"summary": "当前 summary.json 中没有 recent_failures。",
|
||||
"open": False,
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _dashboard_doc_page(title: str, body: str, description: str) -> str:
|
||||
return f"""<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
@@ -242,7 +630,7 @@ def _dashboard_doc_page(title: str, body: str, description: str) -> str:
|
||||
<main>
|
||||
<div class="panel">
|
||||
<div class="actions">
|
||||
<a class="chip" href="../index.html">Back to dashboard</a>
|
||||
<a class="chip" href="../index.html">返回工作台</a>
|
||||
</div>
|
||||
<h1>{html.escape(title)}</h1>
|
||||
<div class="meta">{html.escape(description)}</div>
|
||||
@@ -284,7 +672,7 @@ def _copy_tree(source: Path, destination: Path) -> None:
|
||||
shutil.copy2(path, target)
|
||||
|
||||
|
||||
def _write_dashboard_docs() -> None:
|
||||
def _write_dashboard_docs(architecture: Dict[str, Any]) -> None:
|
||||
docs_dir = DASHBOARD_DIR / "docs"
|
||||
ensure_dir(docs_dir)
|
||||
sources = [
|
||||
@@ -292,19 +680,49 @@ def _write_dashboard_docs() -> None:
|
||||
"project-features.html",
|
||||
"项目功能与特性总览",
|
||||
(ROOT / "docs" / "project-features.md").read_text(encoding="utf-8"),
|
||||
"Dashboard-local mirror of the repo feature guide.",
|
||||
"工作台内置镜像页:仓库功能、目录和自动化链路说明。",
|
||||
),
|
||||
(
|
||||
"frontend-dashboard-design.html",
|
||||
"本地前端工作台设计文档",
|
||||
(ROOT / "docs" / "frontend-dashboard-design.md").read_text(encoding="utf-8"),
|
||||
"Dashboard-local mirror of the UI and interaction specification.",
|
||||
"工作台内置镜像页:前端交互、展示结构和视觉规范。",
|
||||
),
|
||||
(
|
||||
"secure-code-index.html",
|
||||
"安全编码修复库索引",
|
||||
(ROOT / "05-defense" / "secure-code" / "INDEX.md").read_text(encoding="utf-8"),
|
||||
"Dashboard-local mirror of the secure-code library index.",
|
||||
"工作台内置镜像页:secure-code 修复主题索引。",
|
||||
),
|
||||
(
|
||||
"root-readme.html",
|
||||
"仓库 README 镜像",
|
||||
(ROOT / "README.md").read_text(encoding="utf-8"),
|
||||
"工作台内置镜像页:仓库定位、能力矩阵、入口和自动化入口。",
|
||||
),
|
||||
(
|
||||
"authorization-model.html",
|
||||
"授权模型镜像",
|
||||
(ROOT / "09-scope-and-targeting" / "authorization-model.md").read_text(encoding="utf-8"),
|
||||
"工作台内置镜像页:目标范围、授权模型、最小化验证建议和记录要求。",
|
||||
),
|
||||
(
|
||||
"source-map.html",
|
||||
"source-map 真值镜像",
|
||||
SOURCE_MAP_PATH.read_text(encoding="utf-8"),
|
||||
"工作台内置镜像页:系统覆盖、来源、输出目录和 secure-code 主题真值。",
|
||||
),
|
||||
(
|
||||
"repro-map.html",
|
||||
"repro-map 真值镜像",
|
||||
REPRO_MAP_PATH.read_text(encoding="utf-8"),
|
||||
"工作台内置镜像页:默认漏洞家族、浏览器要求和日志策略真值。",
|
||||
),
|
||||
(
|
||||
"coverage-matrix.html",
|
||||
"覆盖矩阵镜像",
|
||||
(ROOT / "08-threat-intel" / "generated" / "coverage-matrix.md").read_text(encoding="utf-8"),
|
||||
"工作台内置镜像页:当前覆盖矩阵生成结果。",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -314,7 +732,15 @@ def _write_dashboard_docs() -> None:
|
||||
"design-source.html",
|
||||
"Lovart 设计来源与本地化清单",
|
||||
manifest_body,
|
||||
"Local vendor manifest for the Lovart-derived dashboard shell.",
|
||||
"工作台内置镜像页:Lovart 来源文件、本地 vendor 路径和本地化说明。",
|
||||
)
|
||||
)
|
||||
sources.append(
|
||||
(
|
||||
"architecture-library.html",
|
||||
"当前架构库镜像",
|
||||
json.dumps(architecture, indent=2, ensure_ascii=False),
|
||||
"工作台内置镜像页:当前架构库结构化数据镜像。",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -368,11 +794,11 @@ def render_run(run: Dict[str, Any]) -> Dict[str, str]:
|
||||
relative_screenshots = [_relative_ref(run_dir, ref) for ref in screenshot_refs]
|
||||
|
||||
md_lines = [
|
||||
f"# Run {run['run_id']}",
|
||||
f"# 运行 {run['run_id']}",
|
||||
"",
|
||||
"> `LAB ONLY` | `AUTHORIZED TARGETS ONLY` | 自动生成 run bundle",
|
||||
"",
|
||||
f"- Advisory: `{run['advisory_id']}`",
|
||||
f"- 漏洞条目: `{run['advisory_id']}`",
|
||||
f"- 系统: `{run['system_id']}`",
|
||||
f"- Repro Profile: `{run['repro_profile_id']}`",
|
||||
f"- 实证状态: `{run['verification_status']}`",
|
||||
@@ -465,20 +891,20 @@ def render_run(run: Dict[str, Any]) -> Dict[str, str]:
|
||||
|
||||
html_body = [
|
||||
"<!doctype html>",
|
||||
"<html><head><meta charset='utf-8'><title>websafe run report</title>",
|
||||
"<html><head><meta charset='utf-8'><title>websafe 运行报告</title>",
|
||||
"<style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;line-height:1.55;background:#f8fafc;color:#0f172a;} code,pre{background:#e2e8f0;padding:.2rem .4rem;border-radius:.3rem;} pre{white-space:pre-wrap;} .grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem;} .card{border:1px solid #cbd5e1;padding:1rem;border-radius:.75rem;background:#fff;} table{width:100%;border-collapse:collapse;background:#fff;border:1px solid #cbd5e1;border-radius:.75rem;overflow:hidden;} th,td{padding:.75rem;border-bottom:1px solid #e2e8f0;text-align:left;vertical-align:top;} img{max-width:100%;border:1px solid #cbd5e1;border-radius:.5rem;} .gallery{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:1rem;}</style>",
|
||||
"</head><body>",
|
||||
f"<h1>Run {html.escape(run['run_id'])}</h1>",
|
||||
f"<h1>运行 {html.escape(run['run_id'])}</h1>",
|
||||
"<div class='grid'>",
|
||||
f"<div class='card'><strong>Advisory</strong><br><code>{html.escape(run['advisory_id'])}</code></div>",
|
||||
f"<div class='card'><strong>Status</strong><br><code>{html.escape(run['verification_status'])}</code></div>",
|
||||
f"<div class='card'><strong>Profile</strong><br><code>{html.escape(run['repro_profile_id'])}</code></div>",
|
||||
f"<div class='card'><strong>Artifact Mode</strong><br><code>{html.escape(run['artifact_mode'])}</code></div>",
|
||||
f"<div class='card'><strong>漏洞条目</strong><br><code>{html.escape(run['advisory_id'])}</code></div>",
|
||||
f"<div class='card'><strong>实证状态</strong><br><code>{html.escape(run['verification_status'])}</code></div>",
|
||||
f"<div class='card'><strong>复现 Profile</strong><br><code>{html.escape(run['repro_profile_id'])}</code></div>",
|
||||
f"<div class='card'><strong>Artifact 模式</strong><br><code>{html.escape(run['artifact_mode'])}</code></div>",
|
||||
"</div>",
|
||||
"<h2>Mermaid Timeline</h2>",
|
||||
"<h2>Mermaid 时间线</h2>",
|
||||
f"<pre>{html.escape(mermaid_from_steps(run))}</pre>",
|
||||
"<h2>Timeline</h2>",
|
||||
"<table><thead><tr><th>Time</th><th>Step</th><th>Status</th><th>Detail</th></tr></thead><tbody>",
|
||||
"<h2>运行时间线</h2>",
|
||||
"<table><thead><tr><th>时间</th><th>步骤</th><th>状态</th><th>说明</th></tr></thead><tbody>",
|
||||
]
|
||||
if run.get("timeline"):
|
||||
for item in run["timeline"]:
|
||||
@@ -490,7 +916,7 @@ def render_run(run: Dict[str, Any]) -> Dict[str, str]:
|
||||
f"<td>{html.escape(item.get('detail', '') or '-')}</td>"
|
||||
"</tr>"
|
||||
)
|
||||
html_body.extend(["</tbody></table>", "<h2>Attack Steps</h2>", "<table><thead><tr><th>Tool</th><th>Status</th><th>Output</th></tr></thead><tbody>"])
|
||||
html_body.extend(["</tbody></table>", "<h2>攻击步骤</h2>", "<table><thead><tr><th>工具</th><th>状态</th><th>输出</th></tr></thead><tbody>"])
|
||||
if run.get("attack_steps"):
|
||||
for step in run["attack_steps"]:
|
||||
html_body.append(
|
||||
@@ -501,16 +927,16 @@ def render_run(run: Dict[str, Any]) -> Dict[str, str]:
|
||||
"</tr>"
|
||||
)
|
||||
else:
|
||||
html_body.append("<tr><td><code>-</code></td><td><code>skipped</code></td><td><code>no attack steps</code></td></tr>")
|
||||
html_body.append("<tr><td><code>-</code></td><td><code>skipped</code></td><td><code>当前没有攻击步骤</code></td></tr>")
|
||||
html_body.extend(["</tbody></table>"])
|
||||
if relative_screenshots:
|
||||
html_body.extend(["<h2>Browser Screenshots</h2>", "<div class='gallery'>"])
|
||||
html_body.extend(["<h2>浏览器截图</h2>", "<div class='gallery'>"])
|
||||
for ref in relative_screenshots:
|
||||
html_body.append(
|
||||
f"<figure><img src='{html.escape(ref)}' alt='{html.escape(Path(ref).stem)}'><figcaption><code>{html.escape(ref)}</code></figcaption></figure>"
|
||||
)
|
||||
html_body.append("</div>")
|
||||
html_body.extend(["<h2>Evidence</h2><ul>"])
|
||||
html_body.extend(["<h2>证据清单</h2><ul>"])
|
||||
for ref in run.get("compose_refs", []) + run.get("browser_refs", []) + run.get("container_log_refs", []) + run.get("request_log_refs", []):
|
||||
html_body.append(f"<li><code>{html.escape(_relative_ref(run_dir, ref))}</code></li>")
|
||||
html_body.extend(["</ul>", "</body></html>"])
|
||||
@@ -523,6 +949,8 @@ def render_dashboard() -> Dict[str, str]:
|
||||
ensure_dir(DASHBOARD_DIR)
|
||||
advisory_records = load_json_dir(ADVISORIES_DIR)
|
||||
runs = load_json_dir(RUNS_DIR)
|
||||
source_map = read_yaml(SOURCE_MAP_PATH, default={}) or {}
|
||||
repro_map = read_yaml(REPRO_MAP_PATH, default={}) or {}
|
||||
advisory_map = {item["canonical_id"]: item for item in advisory_records if item.get("canonical_id")}
|
||||
profile_map = load_profiles()
|
||||
|
||||
@@ -594,7 +1022,7 @@ def render_dashboard() -> Dict[str, str]:
|
||||
_artifact_group(
|
||||
item,
|
||||
"reports",
|
||||
"Reports",
|
||||
"报告与运行产物",
|
||||
[
|
||||
cloned["dashboard_refs"]["report_html"],
|
||||
cloned["dashboard_refs"]["report_md"],
|
||||
@@ -603,12 +1031,12 @@ def render_dashboard() -> Dict[str, str]:
|
||||
],
|
||||
use_dashboard_refs=True,
|
||||
),
|
||||
_artifact_group(item, "compose", "Compose", item.get("compose_refs", [])),
|
||||
_artifact_group(item, "baseline", "Baseline Snapshots", item.get("baseline_refs", [])),
|
||||
_artifact_group(item, "attack", "Attack Outputs", _attack_result_refs(item)),
|
||||
_artifact_group(item, "browser", "Browser Evidence", item.get("browser_refs", [])),
|
||||
_artifact_group(item, "container", "Container Logs", item.get("container_log_refs", [])),
|
||||
_artifact_group(item, "requests", "Request Logs", request_only_refs),
|
||||
_artifact_group(item, "compose", "Compose 编排", item.get("compose_refs", [])),
|
||||
_artifact_group(item, "baseline", "基线快照", item.get("baseline_refs", [])),
|
||||
_artifact_group(item, "attack", "攻击输出", _attack_result_refs(item)),
|
||||
_artifact_group(item, "browser", "浏览器证据", item.get("browser_refs", [])),
|
||||
_artifact_group(item, "container", "容器日志", item.get("container_log_refs", [])),
|
||||
_artifact_group(item, "requests", "请求与探测日志", request_only_refs),
|
||||
]
|
||||
cloned["artifact_groups"] = [group for group in cloned["artifact_groups"] if group["count"]]
|
||||
decorated_runs.append(cloned)
|
||||
@@ -641,8 +1069,10 @@ def render_dashboard() -> Dict[str, str]:
|
||||
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()})
|
||||
architecture = _build_architecture_data(summary, source_map, repro_map)
|
||||
write_json(DASHBOARD_DIR / "architecture.json", architecture)
|
||||
|
||||
_write_dashboard_docs()
|
||||
_write_dashboard_docs(architecture)
|
||||
_write_design_source_manifest()
|
||||
_render_root_dashboard_shell()
|
||||
_render_legacy_dashboard_shell()
|
||||
|
||||
在新工单中引用
屏蔽一个用户