"""MeshSafe 统一中文标签体系。 所有控制台页面(概览 / 任务历史 / 测试靶场)共用同一套中文映射, 保证同一个后端字段在任何页面显示的名称、颜色、说明完全一致。 设计目标 -------- 1. **术语统一** —— verdict / action / severity / engine / mode 全部中文化, 英文原值保留在 title 属性里供运维排查时对照。 2. **风险分级体系** —— 把「放行 / 可疑 / 恶意 / 策略拒绝 / 异常」归入 4 个风险等级(安全 / 注意 / 高危 / 故障),与策略引擎的裁决语义对齐。 3. **单一事实源** —— 新增页面或新增字段时只需在这里登记, 不用再到每个模板里写一遍 if-else。 """ from __future__ import annotations from typing import Any, Dict, List, Optional, Tuple # ============================================================================= # 风险等级体系(全局统一) # ============================================================================= #: 风险等级 → (中文名, 徽章 CSS 类, 语义说明) RISK_LEVELS: Dict[str, Tuple[str, str, str]] = { "safe": ("安全", "ok", "未检出任何风险信号,正常放行"), "notice": ("注意", "warn", "存在可疑信号,建议人工确认后放行"), "high": ("高危", "bad", "确认存在恶意特征,必须阻断"), "fault": ("故障", "err", "检测过程异常,按安全优先策略阻断"), "idle": ("处理中", "muted", "检测尚未完成"), } #: 风险等级排序(从高到低),用于排序统计 RISK_ORDER: List[str] = ["high", "notice", "fault", "safe", "idle"] # ============================================================================= # 裁决结论(verdict) # ============================================================================= #: verdict → (中文名, 风险等级) VERDICT_LABELS: Dict[str, Tuple[str, str]] = { "clean": ("放行", "safe"), "pass": ("放行", "safe"), "known": ("放行", "safe"), "suspicious": ("可疑", "notice"), "review": ("可疑", "notice"), "malicious": ("恶意", "high"), "reject": ("策略拒绝", "fault"), "error": ("检测异常", "fault"), "pending": ("处理中", "idle"), } # ============================================================================= # 处置动作(action) # ============================================================================= #: action → (中文名, 风险等级) ACTION_LABELS: Dict[str, Tuple[str, str]] = { "pass": ("放行", "safe"), "pass_with_flag": ("带标记放行", "notice"), "manual_review": ("人工复核", "notice"), "reject": ("拦截", "high"), "reject_and_notify": ("拦截并告警", "high"), "error": ("异常阻断", "fault"), "pending": ("处理中", "idle"), } # ============================================================================= # 严重度(severity) # ============================================================================= #: severity → (中文名, 徽章 CSS 类) SEVERITY_LABELS: Dict[str, Tuple[str, str]] = { "critical": ("严重", "bad"), "high": ("高危", "bad"), "medium": ("中危", "warn"), "low": ("低危", "notice"), "info": ("提示", "muted"), } # ============================================================================= # 检测引擎(engine) # ============================================================================= ENGINE_LABELS: Dict[str, str] = { "structure": "文件结构分析", "clamav": "ClamAV 病毒库", "document": "文档深度检测", "keywords": "关键词匹配", "yara": "YARA 规则匹配", "binary": "PE/二进制分析", "extractor": "归档解压", } # ============================================================================= # 扫描模式(mode) # ============================================================================= MODE_LABELS: Dict[str, str] = { "sync": "同步", "async": "异步", } # ============================================================================= # 样本分类(测试靶场 category) # ============================================================================= CATEGORY_LABELS: Dict[str, str] = { "web_shell": "Webshell", "office_macro": "Office 宏", "windows_pe": "Windows 可执行", "linux_malware": "Linux 恶意脚本", "zero_tolerance": "零容忍载体", "encrypted": "加密文档", "negative": "阴性对照", "unknown": "未分类", } # ============================================================================= # 渠道(channel)—— 摆渡来源 # ============================================================================= CHANNEL_LABELS: Dict[str, str] = { "default": "默认通道", "duandao": "摆渡通道", "console_upload": "控制台上传", "test_center": "测试靶场", } # ============================================================================= # 对外查询接口 # ============================================================================= def verdict_label(verdict: Any) -> str: """verdict → 中文名(未知值原样返回)。""" key = str(verdict or "").strip().lower() return VERDICT_LABELS.get(key, (str(verdict or "-"), "idle"))[0] def verdict_risk(verdict: Any) -> str: """verdict → 风险等级 key。""" key = str(verdict or "").strip().lower() return VERDICT_LABELS.get(key, ("-", "idle"))[1] def verdict_badge(verdict: Any) -> str: """verdict → 徽章 CSS 类。""" key = str(verdict or "").strip().lower() return RISK_LEVELS.get(verdict_risk(key), ("-", "muted"))[1] def action_label(action: Any) -> str: """action → 中文名。""" key = str(action or "").strip().lower() return ACTION_LABELS.get(key, (str(action or "-"), "idle"))[0] def action_risk(action: Any) -> str: """action → 风险等级 key。""" key = str(action or "").strip().lower() return ACTION_LABELS.get(key, ("-", "idle"))[1] def action_badge(action: Any) -> str: """action → 徽章 CSS 类。""" key = str(action or "").strip().lower() return RISK_LEVELS.get(action_risk(key), ("-", "muted"))[1] def severity_label(severity: Any) -> str: """severity → 中文名。""" key = str(severity or "").strip().lower() return SEVERITY_LABELS.get(key, (str(severity or "-"), "muted"))[0] def severity_badge(severity: Any) -> str: """severity → 徽章 CSS 类。""" key = str(severity or "").strip().lower() return SEVERITY_LABELS.get(key, ("-", "muted"))[1] def engine_label(engine: Any) -> str: """engine → 中文名。""" key = str(engine or "").strip().lower() return ENGINE_LABELS.get(key, str(engine or "-")) def mode_label(mode: Any) -> str: """mode → 中文名。""" key = str(mode or "").strip().lower() return MODE_LABELS.get(key, str(mode or "-")) def category_label(category: Any) -> str: """category → 中文名。""" key = str(category or "").strip().lower() return CATEGORY_LABELS.get(key, str(category or "-")) def channel_label(channel: Any) -> str: """channel → 中文名。""" key = str(channel or "").strip().lower() return CHANNEL_LABELS.get(key, str(channel or "-")) def risk_label(risk: Any) -> str: """风险等级 key → 中文名。""" key = str(risk or "").strip().lower() return RISK_LEVELS.get(key, (str(risk or "-"), "muted", ""))[0] def risk_badge(risk: Any) -> str: """风险等级 key → 徽章 CSS 类。""" key = str(risk or "").strip().lower() return RISK_LEVELS.get(key, ("-", "muted", ""))[1] def risk_desc(risk: Any) -> str: """风险等级 key → 语义说明。""" key = str(risk or "").strip().lower() return RISK_LEVELS.get(key, ("-", "muted", ""))[2] # ============================================================================= # 组合辅助 # ============================================================================= def risk_matrix() -> List[Dict[str, Any]]: """返回完整风险等级体系(供页面渲染说明表/图例)。""" return [ { "key": key, "label": RISK_LEVELS[key][0], "badge": RISK_LEVELS[key][1], "desc": RISK_LEVELS[key][2], } for key in RISK_ORDER if key in RISK_LEVELS ] def verdict_summary(verdict_dist: Dict[str, int]) -> List[Dict[str, Any]]: """把 {verdict: count} 转成按风险等级聚合的展示列表。 输出按「高危 → 注意 → 故障 → 安全 → 处理中」排序, 每项含:中文名、徽章类、计数、占比、原始 verdict key。 同风险等级内的多个 verdict 会被合并(例如 malicious 与 reject 都是高危)。 """ total = sum(verdict_dist.values()) or 0 # 先按 verdict 映射到风险等级 buckets: Dict[str, Dict[str, Any]] = {} for v, cnt in verdict_dist.items(): risk = verdict_risk(v) if risk not in buckets: buckets[risk] = { "key": risk, "label": risk_label(risk), "badge": risk_badge(risk), "desc": risk_desc(risk), "count": 0, "verdicts": [], } buckets[risk]["count"] += int(cnt) buckets[risk]["verdicts"].append(str(v)) items = list(buckets.values()) for it in items: it["pct"] = round(it["count"] / total * 100, 1) if total else 0.0 items.sort(key=lambda x: RISK_ORDER.index(x["key"]) if x["key"] in RISK_ORDER else 99) return items