Files

224 lines
11 KiB
HTML
Raw Permalink Normal View History

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<link rel="icon" href="/static/logo.png">
<title>策略配置 - MeshSafe 控制台</title>
<style>
* { box-sizing: border-box; }
body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
margin: 0; background: #f5f6fa; color: #333; }
{% include "_nav_style.html" %}
main { padding: 24px; max-width: 1400px; margin: 0 auto; }
h2 { margin: 0 0 16px; font-size: 18px; }
.tabs { display: flex; border-bottom: 2px solid #e0e0e0; margin-bottom: 16px; }
.tab { padding: 10px 20px; cursor: pointer; font-size: 14px; color: #666; border-bottom: 2px solid transparent; margin-bottom: -2px; }
.tab.active { color: #1a2a6c; border-bottom-color: #1a2a6c; font-weight: 500; }
.panel { display: none; }
.panel.active { display: block; }
.card { background: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,.06); margin-bottom: 16px; }
.card h3 { margin: 0 0 12px; font-size: 14px; color: #555; font-weight: 600; }
textarea { width: 100%; min-height: 480px; font-family: "JetBrains Mono", Consolas, monospace;
font-size: 13px; padding: 14px; border: 1px solid #ddd; border-radius: 6px;
resize: vertical; line-height: 1.5; }
textarea:focus { border-color: #1a2a6c; outline: none; }
.toolbar { display: flex; gap: 10px; margin-top: 12px; align-items: center; }
button { padding: 9px 20px; border: none; border-radius: 6px; font-size: 14px; cursor: pointer; }
.btn-primary { background: #1a2a6c; color: white; font-weight: 500; }
.btn-primary:hover { background: #243a8c; }
.btn-ghost { background: #f0f0f0; color: #555; }
.btn-ghost:hover { background: #e4e4e4; }
.btn-danger { background: #c62828; color: white; }
.btn-danger:hover { background: #d32f2f; }
.versions { width: 100%; border-collapse: collapse; font-size: 13px; }
.versions th { background: #f8f9fa; padding: 8px 12px; text-align: left; color: #666;
font-weight: 500; border-bottom: 1px solid #eee; }
.versions td { padding: 8px 12px; border-bottom: 1px solid #f0f0f0; font-family: monospace; }
.chip { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px;
margin: 2px; }
.chip-zt { background: #ffebee; color: #c62828; }
.chip-warn { background: #fff3e0; color: #ef6c00; }
.chip-info { background: #e3f2fd; color: #1565c0; }
.chip-ok { background: #e8f5e9; color: #2e7d32; }
.form-row { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; }
.form-row label { width: 160px; font-size: 13px; color: #555; }
.form-row input[type=number], .form-row input[type=text] { padding: 8px 10px; border: 1px solid #ddd;
border-radius: 5px; font-size: 13px; width: 120px; }
</style>
</head>
<body>
{% set nav_active = 'policy' %}{% include "_nav.html" %}
<main>
<div class="tabs">
<div class="tab active" data-tab="yaml">YAML 模式(直接编辑)</div>
<div class="tab" data-tab="rules">规则清单(只读预览)</div>
<div class="tab" data-tab="yara">YARA 规则库({{ yara_loaded_count }} 条已加载)</div>
<div class="tab" data-tab="versions">版本历史</div>
</div>
<!-- ============ YAML 模式 ============ -->
<div id="panel-yaml" class="panel active">
<form method="post" action="/console/policy/save">
<input type="hidden" name="mode" value="yaml">
<div class="card">
<h3>策略内容(数据库为准,YAML 作为导出备份)</h3>
<textarea name="yaml_content" spellcheck="false">{{ yaml_text }}</textarea>
<div class="toolbar">
<button type="submit" class="btn-primary" {% if user.role != 'admin' %}disabled title="admin 权限才能保存"{% endif %}>保存并热重载</button>
<button type="button" class="btn-ghost" onclick="document.querySelector('#panel-yaml textarea').value = document.querySelector('#panel-yaml textarea').value;">刷新</button>
<span style="color:#888;font-size:12px;">当前版本:<b>{{ policy.version }}</b> · SHA256:{{ policy.sha256[:12] }}...</span>
</div>
</div>
</form>
</div>
<!-- ============ 规则清单 ============ -->
<div id="panel-rules" class="panel">
<div class="card">
<h3>零容忍规则集({{ policy.zero_tolerance_rules|length }} 条 · 命中即拒绝)</h3>
<div>
{% if zt_rules_cn %}
{% for r in zt_rules_cn %}
<span class="chip chip-zt" title="{{ r.rule }} — {{ r.cn_desc }}">{{ r.cn_name }}</span>
{% endfor %}
{% else %}
{% for r in policy.zero_tolerance_rules %}
<span class="chip chip-zt">{{ r }}</span>
{% endfor %}
{% endif %}
</div>
</div>
<div class="card">
<h3>策略全局参数</h3>
<table class="versions">
<tr><th>参数名</th><th>说明</th><th>当前值</th></tr>
<tr><td>block_score</td><td>单引擎累计得分 ≥ 此阈值 → 判定为 malicious 直接拒绝</td><td><b>{{ policy.block_score }}</b></td></tr>
<tr><td>review_score</td><td>累计得分落在这个区间 → 判定为 suspicious 转人工复核</td><td>{{ policy.review_score[0] }} ~ {{ policy.review_score[1] }}</td></tr>
<tr><td>engine_failure_mode</td><td>检测引擎异常时的处理策略(fail_closed=拒绝 fail_open=放行)</td><td><b style="color:#c62828;">{{ policy.engine_failure_mode }}</b></td></tr>
<tr><td>max_file_size</td><td>允许上传的单文件最大体积(超过直接拒绝)</td><td>{% set kb = policy.max_file_size / 1024 %}{% set mb = kb / 1024 %}{% if mb >= 1 %}{{ "%.1f"|format(mb) }} MB{% else %}{{ "%.0f"|format(kb) }} KB{% endif %}({{ policy.max_file_size }} 字节)</td></tr>
<tr><td>allow_encrypted_archive</td><td>是否允许带密码的加密 ZIP/RAR/7z 压缩包通过</td><td>{% if policy.allow_encrypted_archive %}<span class="chip chip-ok">允许</span>{% else %}<span class="chip chip-zt">禁止</span>{% endif %}</td></tr>
<tr><td>allow_encrypted_document</td><td>是否允许带密码的加密 Office 文档(.docx/.xlsx/.pptx)通过</td><td>{% if policy.allow_encrypted_document %}<span class="chip chip-ok">允许</span>{% else %}<span class="chip chip-zt">禁止</span>{% endif %}</td></tr>
<tr><td>白名单扩展名</td><td>允许上传的文件扩展名(不在此列表的直接拒绝)</td><td style="font-family:sans-serif;">
{% for e in policy.allowed_extensions %}
<span class="chip chip-info">{{ e }}</span>
{% endfor %}
</td></tr>
</table>
</div>
{% if policy.keyword_rules %}
<div class="card">
<h3>词表规则({{ policy.keyword_rules|length }} 条)</h3>
{% for kw in policy.keyword_rules %}
<div class="chip chip-info">{{ kw.name }}</div>
{% endfor %}
</div>
{% endif %}
</div>
<!-- ============ YARA 规则库 ============ -->
<div id="panel-yara" class="panel">
<div class="card">
<h3>YARA 规则库({{ yara_files_count }} 个文件 · {{ yara_loaded_count }} 条已编译)</h3>
{% if yara_rules %}
<table class="versions">
<thead>
<tr>
<th>规则名</th>
<th>分类</th>
<th>风险等级</th>
<th>误报率</th>
<th>权重</th>
<th>触发次数</th>
<th>参考</th>
</tr>
</thead>
<tbody>
{% for r in yara_rules %}
<tr>
<td style="font-family:monospace;font-size:12px;" title="{{ r.name }}">{{ r.name }}</td>
<td>
<span class="chip chip-info" title="分类代码 {{ r.category }}">{{ r.category_cn }}</span>
</td>
<td>
{% if r.risk_level_cn %}<span class="chip {{ r.risk_level_chip }}">{{ r.risk_level_cn }}</span>
{% else %}<span class="chip chip-ok">{{ r.risk_level }}</span>{% endif %}
</td>
<td style="color:#888;font-size:12px;">{{ r.false_positive_rate }}</td>
<td><b>{{ r.weight }}</b></td>
<td>
{% if r.trigger_count > 0 %}<span class="chip chip-zt">{{ r.trigger_count }}</span>
{% else %}<span style="color:#bbb;">0</span>{% endif %}
</td>
<td style="font-size:11px;color:#888;">{{ r.reference[:50] }}{% if r.reference|length > 50 %}...{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div style="color:#999;text-align:center;padding:30px;">
{% if yara_rules_dir %}
YARA 规则库目录:<code>{{ yara_rules_dir }}</code><br>
目录内 {{ yara_files_count }} 个 .yar 文件
{% if yara_files_count == 0 %}—— 请检查规则文件是否存在{% endif %}
{% else %}
YARA 规则库未加载(可能 yara-python 未安装或规则目录不存在)
{% endif %}
</div>
{% endif %}
</div>
<div class="card" style="margin-top:12px;">
<h3>权重分档说明(来自 policy.yaml → yara_rule_weights)</h3>
<div style="font-size:13px;color:#555;line-height:1.8;">
<span class="chip chip-zt">critical → weight=85</span>
fix: 裁决口径重构 — rule_overrides 改处置地板,YARA 权重分档与阈值解耦 三件事,都围绕"分数只反映证据,不放行由地板保证": - rule_overrides 的 manual_review 旧实现是 score=max(score,65) 并跳过该规则自身权重: 注进去的 65 不是证据,再叠任意 ≥5 分噪声就越过 block_score=70 变拒绝,同时显示分数 与真实证据脱钩。改为步骤 9「复核地板」:权重照常累加,裁决后处置低于复核才抬到 manual_review,已判 reject 不降回。3661 条历史扫描重放 0 收紧 0 放行;把 review_score 下界抬到 36/40/50 或把 DEE 权重降到 10,无地板时干净 exe 静默放行,有地板恒为人工复核。 - DangerousExecutableExtension 进 rule_overrides(manual_review),落实光闸口径「EXE/DLL/ 脚本不自动放行」,不再依赖 weight=30 恰好等于 review_score 下界这种数值巧合。 - YARA 权重分档 85/65/45/25 → 85/60/30/10,只有 critical 一档能独立越过 70; 档位与 rules/*.yar 的 meta.risk_level 一一对应,控制台注释与 policy.html 同步。 - 词表:RemoteDownloadUnix/PowerShell 移出零容忍、改判"工具与 URL/IP 同框", 新增 RemoteDownloadExec(40)/PayloadDropToDisk(45) 带内网源站白名单。裸 cmdlet 名会把 nvidiaInspector.exe 的 add_DownloadFileCompleted 白送 30 分,把 45 分复核顶成 75 分拒绝。 - 风险等级文案:裁决已是 malicious/error 时仍输出"打开风险低"是自相矛盾的对外文案, 强制收敛到 critical。 - 靶场 L5-005 expected_verdict reject→review:旧 85 分靠载体45+时间戳20+未签名10+架构10 堆叠,属权重堆叠误报;真实注入器由 C1_PE_ProcessInjection(85) 拦截。
2026-11-02 20:27:30 +08:00
<span class="chip chip-warn">high → weight=60</span>
<span class="chip chip-info">medium → weight=30</span>
<span class="chip chip-ok">low → weight=10</span>
<span style="color:#888;margin-left:12px;">未列出的规则沿用 rules/*.yar 中 meta.risk_level 推导出的同档权重</span>
</div>
</div>
</div>
<!-- ============ 版本历史 ============ -->
<div id="panel-versions" class="panel">
<div class="card">
<h3>策略版本历史({{ versions|length }} 条)</h3>
<table class="versions">
<thead><tr><th>版本</th><th>SHA256</th><th>创建时间</th><th>操作</th></tr></thead>
<tbody>
{% for v in versions %}
<tr>
<td>{{ v.version }}</td>
<td>{{ v.sha256 }}</td>
<td>{{ v.created_at|bj_time if v.created_at else '' }}</td>
<td>
{% if user.role == 'admin' %}
<form method="post" action="/console/policy/rollback/{{ v.version }}" style="display:inline;">
<button type="submit" class="btn-danger" style="padding:4px 12px;font-size:12px;">回滚到此版本</button>
</form>
{% endif %}
</td>
</tr>
{% else %}
<tr><td colspan="4" style="text-align:center;color:#999;">暂无版本记录(首次部署执行 scripts.init_db)</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</main>
<script>
document.querySelectorAll('.tab').forEach(tab => {
tab.onclick = () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
tab.classList.add('active');
document.getElementById('panel-' + tab.dataset.tab).classList.add('active');
};
});
</script>
</body>
</html>