codebee 0.1.103 → 0.1.104
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/LICENSE +21 -21
- package/README.md +2 -3
- package/app/core/acceptance.py +100 -100
- package/app/core/acp_client.py +457 -457
- package/app/core/aiflavor.py +197 -197
- package/app/core/analytics.py +38 -38
- package/app/core/artifacts.py +148 -148
- package/app/core/assets.py +110 -110
- package/app/core/attachments.py +623 -623
- package/app/core/audit.py +30 -30
- package/app/core/backend_profiles.py +188 -188
- package/app/core/backup.py +470 -470
- package/app/core/beekeeper.py +183 -183
- package/app/core/benchstore.py +102 -102
- package/app/core/bookmeta.py +1036 -1036
- package/app/core/bookmeta_catalog.py +180 -180
- package/app/core/branching.py +137 -137
- package/app/core/capability.py +144 -144
- package/app/core/chaptersafe.py +32 -32
- package/app/core/cleanup.py +303 -303
- package/app/core/command_auth.py +302 -302
- package/app/core/compaction.py +201 -201
- package/app/core/contracts.py +255 -255
- package/app/core/covergen.py +356 -356
- package/app/core/defectretro.py +43 -43
- package/app/core/diagnostics.py +114 -114
- package/app/core/dispatch_log.py +128 -128
- package/app/core/env_scrub.py +85 -85
- package/app/core/errorlog.py +137 -137
- package/app/core/eval_matrix.py +91 -91
- package/app/core/evaluation.py +174 -174
- package/app/core/failure_notify.py +290 -290
- package/app/core/flow_graph.py +33 -33
- package/app/core/goal_service.py +167 -167
- package/app/core/health.py +389 -389
- package/app/core/history.py +38 -38
- package/app/core/hooks.py +162 -162
- package/app/core/knowledge_pipeline.py +86 -86
- package/app/core/manager.py +2385 -2385
- package/app/core/mocks.py +64 -64
- package/app/core/notify.py +201 -201
- package/app/core/novel_quality.py +285 -181
- package/app/core/openapi.py +217 -217
- package/app/core/operations.py +228 -228
- package/app/core/paihang.py +145 -145
- package/app/core/pipeline.py +55 -3
- package/app/core/planner.py +7 -0
- package/app/core/portguard.py +111 -111
- package/app/core/presence.py +77 -77
- package/app/core/preview.py +293 -293
- package/app/core/project_memory.py +206 -206
- package/app/core/prompt_guard.py +78 -78
- package/app/core/publish/__init__.py +7 -7
- package/app/core/publish/auto.py +659 -659
- package/app/core/publish/chaprows.py +138 -138
- package/app/core/publish/flows-fanqie-calibrated.json +442 -442
- package/app/core/publish/manager.py +1185 -1185
- package/app/core/publish/qimao.py +193 -193
- package/app/core/publish/volumes.py +108 -108
- package/app/core/publish/ws.py +139 -139
- package/app/core/quality_gate.py +201 -201
- package/app/core/redact.py +38 -38
- package/app/core/repeat_guard.py +129 -129
- package/app/core/retrieval.py +137 -137
- package/app/core/sandbox_runtime.py +95 -95
- package/app/core/selfupdate.py +384 -384
- package/app/core/session_log.py +161 -161
- package/app/core/sessions.py +312 -312
- package/app/core/settings.py +215 -215
- package/app/core/settings_schema.py +282 -282
- package/app/core/share_page.py +114 -114
- package/app/core/skill_scan.py +139 -139
- package/app/core/skillpacks/market/character-bible.md +66 -66
- package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
- package/app/core/skillpacks/market/git-workflow.md +57 -57
- package/app/core/skillpacks/market/release-notes.md +72 -72
- package/app/core/skillpacks/market/weekly-report.md +71 -71
- package/app/core/skillpacks/market/worldview-consistency.md +70 -70
- package/app/core/step_runner.py +132 -132
- package/app/core/story_tracking.py +275 -275
- package/app/core/task_compile.py +199 -199
- package/app/core/telemetry.py +292 -292
- package/app/core/tlsctx.py +64 -64
- package/app/core/token_meter.py +147 -147
- package/app/core/tool_outputs.py +107 -107
- package/app/core/tracing.py +207 -207
- package/app/core/upstream.py +41 -41
- package/app/core/usage.py +1038 -1038
- package/app/core/volumes.py +389 -389
- package/app/core/webhooks.py +89 -89
- package/app/core/wxdigest.py +865 -865
- package/app/core/zentao.py +2692 -2692
- package/app/mcp_server.py +244 -244
- package/app/pick_dialog.py +110 -110
- package/app/ui/artdiff.js +74 -74
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/manifest.json +16 -16
- package/app/ui/qrcode.js +2297 -2297
- package/bin/tutti.js +147 -147
- package/package.json +1 -1
- package/tools/wx_reader.py +261 -261
- package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
- package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
- package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +0 -210
- package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
- package/app/.playwright-cli/run-detail-after.png +0 -0
- package/app/output/_createbook_body.json +0 -1
- package/app/output/_probe_fq_click.py +0 -50
- package/app/output/_probe_fq_form.py +0 -42
- package/app/output/_probe_fq_modal.py +0 -55
- package/app/output/_probe_fq_points.py +0 -71
- package/app/output/_probe_fq_submit.py +0 -34
- package/app/output/_probe_fq_watch.py +0 -62
- package/app/output/workbench-clean-preview.jpg +0 -0
- package/app/output/workbench-reference-render.png +0 -0
- package/app/output/workbench-shell.png +0 -0
package/app/core/share_page.py
CHANGED
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""可分享报告页(借鉴 agency-orchestrator 的 ao report):把 run 成果渲染成
|
|
3
|
-
自包含单文件 HTML——零 JS、内联样式、离线可看,直接发群/发人。
|
|
4
|
-
|
|
5
|
-
纯函数无网络;数据全部来自调用方(store/paths 已读好的对象)。
|
|
6
|
-
"""
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import html as _html
|
|
10
|
-
import time
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def _esc(s):
|
|
14
|
-
return _html.escape(str(s if s is not None else ""), quote=True)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _md_ish(text):
|
|
18
|
-
"""极简 Markdown 呈现:标题/列表/粗体/代码,够报告阅读即可,不做完整解析。"""
|
|
19
|
-
out = []
|
|
20
|
-
for ln in (text or "").splitlines():
|
|
21
|
-
e = _esc(ln)
|
|
22
|
-
if ln.startswith("# "):
|
|
23
|
-
out.append("<h1>%s</h1>" % e[2:])
|
|
24
|
-
elif ln.startswith("## "):
|
|
25
|
-
out.append("<h2>%s</h2>" % e[3:])
|
|
26
|
-
elif ln.startswith("### "):
|
|
27
|
-
out.append("<h3>%s</h3>" % e[4:])
|
|
28
|
-
elif ln.startswith("- "):
|
|
29
|
-
out.append("<li>%s</li>" % e[2:])
|
|
30
|
-
else:
|
|
31
|
-
out.append("<p>%s</p>" % e)
|
|
32
|
-
return "\n".join(out)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
_SHARE_TPL = """<!DOCTYPE html>
|
|
36
|
-
<html lang="zh"><head><meta charset="utf-8">
|
|
37
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
38
|
-
<title>__TITLE__ · CodeBee 任务报告</title>
|
|
39
|
-
<style>
|
|
40
|
-
body{margin:0;background:#f5f6f8;color:#1c2330;font:15px/1.65 -apple-system,"PingFang SC","Microsoft YaHei",sans-serif}
|
|
41
|
-
.wrap{max-width:760px;margin:0 auto;padding:28px 18px 60px}
|
|
42
|
-
.head{border-bottom:3px solid #2b6cb0;padding-bottom:14px;margin-bottom:22px}
|
|
43
|
-
h1{margin:0 0 6px;font-size:24px}
|
|
44
|
-
.meta{color:#5a6472;font-size:13px}
|
|
45
|
-
.badge{display:inline-block;border-radius:999px;padding:2px 12px;font-size:13px;font-weight:600}
|
|
46
|
-
.badge.ok{background:#e6f6ec;color:#177245}.badge.bad{background:#fdeaea;color:#b42318}
|
|
47
|
-
.badge.off{background:#eef0f3;color:#5a6472}
|
|
48
|
-
.card{background:#fff;border:1px solid #e3e6ea;border-radius:12px;padding:18px 20px;margin-bottom:18px}
|
|
49
|
-
.card h2{font-size:16px;margin:0 0 12px;color:#2b6cb0}
|
|
50
|
-
table{border-collapse:collapse;width:100%}
|
|
51
|
-
td{padding:6px 10px;border-bottom:1px solid #eef0f3}
|
|
52
|
-
tr.sum td{font-weight:700;border-top:2px solid #e3e6ea}
|
|
53
|
-
.step{display:flex;gap:10px;padding:7px 0;border-bottom:1px dashed #eef0f3;font-size:13.5px;align-items:baseline}
|
|
54
|
-
.step .n{color:#8a93a0;font-weight:700}.step .role{font-weight:600;min-width:88px}
|
|
55
|
-
.step .who{color:#5a6472;min-width:90px}.step .sum{flex:1}.st{font-size:12px}
|
|
56
|
-
.st.done{color:#177245}.st.failed{color:#b42318}
|
|
57
|
-
h1,h2,h3{margin:14px 0 8px}li{margin:4px 0}.trow{padding:2px 0;border-bottom:1px dotted #eee}
|
|
58
|
-
p{margin:8px 0}.mut{color:#8a93a0}code{background:#f0f2f5;padding:1px 6px;border-radius:4px}
|
|
59
|
-
pre{background:#f0f2f5;padding:12px;border-radius:8px;overflow-x:auto;white-space:pre-wrap}
|
|
60
|
-
</style></head><body><div class="wrap">
|
|
61
|
-
<div class="head"><h1>__TITLE__</h1>
|
|
62
|
-
<div class="meta">__META__ · __ST__ · CodeBee 生成于 __NOW__</div></div>
|
|
63
|
-
<div class="card"><h2>结果</h2><span class="badge __STCLS__">__ST__</span>__OVERALL__</div>
|
|
64
|
-
<div class="card"><h2>步骤</h2>__STEPS__</div>
|
|
65
|
-
<div class="card"><h2>报告</h2>__REPORT__</div>
|
|
66
|
-
<div class="meta">由 <b>CodeBee</b> 多智能体编排台生成</div>
|
|
67
|
-
</div></body></html>"""
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def render_share_html(run, task, report_text):
|
|
71
|
-
"""渲染自包含分享页。run/task/report_text 缺失时优雅降级。"""
|
|
72
|
-
run = run or {}
|
|
73
|
-
task = task or {}
|
|
74
|
-
status = str(run.get("status") or "")
|
|
75
|
-
st_label = {"done": "已完成", "failed": "失败", "cancelled": "已取消",
|
|
76
|
-
"running": "进行中"}.get(status, status)
|
|
77
|
-
st_cls = {"done": "ok", "failed": "bad", "cancelled": "off"}.get(status, "off")
|
|
78
|
-
verdict = run.get("verdict") or {}
|
|
79
|
-
means = verdict.get("scores") or {}
|
|
80
|
-
|
|
81
|
-
score_rows = ""
|
|
82
|
-
for d, v in means.items():
|
|
83
|
-
score_rows += "<tr><td>%s</td><td>%.1f</td></tr>" % (_esc(d), float(v))
|
|
84
|
-
if verdict:
|
|
85
|
-
score_rows += ('<tr class="sum"><td>综合</td>'
|
|
86
|
-
'<td>%.1f</td></tr>' % float(verdict.get("overall") or 0))
|
|
87
|
-
|
|
88
|
-
steps_rows = ""
|
|
89
|
-
for s in run.get("steps") or []:
|
|
90
|
-
who = s.get("agent_label") or s.get("agent") or ""
|
|
91
|
-
if s.get("model"):
|
|
92
|
-
who += " · " + str(s["model"])
|
|
93
|
-
steps_rows += (
|
|
94
|
-
'<div class="step"><span class="n">%02d</span>'
|
|
95
|
-
'<span class="role">%s</span><span class="who">%s</span>'
|
|
96
|
-
'<span class="sum">%s</span><span class="st %s">%s</span></div>'
|
|
97
|
-
% (s.get("n") or 0, _esc(s.get("role") or ""),
|
|
98
|
-
_esc(who),
|
|
99
|
-
_esc((s.get("summary") or "")[:220]), _esc(s.get("status") or ""),
|
|
100
|
-
_esc(s.get("status") or "")))
|
|
101
|
-
|
|
102
|
-
report_html = _md_ish(report_text) if report_text else "<p class='mut'>(暂无报告)</p>"
|
|
103
|
-
overall_note = ("综合 %.1f 分" % float(verdict.get("overall"))
|
|
104
|
-
if verdict.get("overall") is not None else "")
|
|
105
|
-
|
|
106
|
-
return (_SHARE_TPL
|
|
107
|
-
.replace("__TITLE__", _esc(task.get("title") or run.get("title") or "任务报告"))
|
|
108
|
-
.replace("__META__", _esc(task.get("goal") or ""))
|
|
109
|
-
.replace("__ST__", _esc(st_label))
|
|
110
|
-
.replace("__STCLS__", st_cls)
|
|
111
|
-
.replace("__NOW__", _esc(time.strftime("%Y-%m-%d %H:%M")))
|
|
112
|
-
.replace("__OVERALL__", _esc(overall_note))
|
|
113
|
-
.replace("__STEPS__", steps_rows or "<p class='mut'>(无步骤)</p>")
|
|
114
|
-
.replace("__REPORT__", report_html))
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""可分享报告页(借鉴 agency-orchestrator 的 ao report):把 run 成果渲染成
|
|
3
|
+
自包含单文件 HTML——零 JS、内联样式、离线可看,直接发群/发人。
|
|
4
|
+
|
|
5
|
+
纯函数无网络;数据全部来自调用方(store/paths 已读好的对象)。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import html as _html
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _esc(s):
|
|
14
|
+
return _html.escape(str(s if s is not None else ""), quote=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _md_ish(text):
|
|
18
|
+
"""极简 Markdown 呈现:标题/列表/粗体/代码,够报告阅读即可,不做完整解析。"""
|
|
19
|
+
out = []
|
|
20
|
+
for ln in (text or "").splitlines():
|
|
21
|
+
e = _esc(ln)
|
|
22
|
+
if ln.startswith("# "):
|
|
23
|
+
out.append("<h1>%s</h1>" % e[2:])
|
|
24
|
+
elif ln.startswith("## "):
|
|
25
|
+
out.append("<h2>%s</h2>" % e[3:])
|
|
26
|
+
elif ln.startswith("### "):
|
|
27
|
+
out.append("<h3>%s</h3>" % e[4:])
|
|
28
|
+
elif ln.startswith("- "):
|
|
29
|
+
out.append("<li>%s</li>" % e[2:])
|
|
30
|
+
else:
|
|
31
|
+
out.append("<p>%s</p>" % e)
|
|
32
|
+
return "\n".join(out)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
_SHARE_TPL = """<!DOCTYPE html>
|
|
36
|
+
<html lang="zh"><head><meta charset="utf-8">
|
|
37
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
38
|
+
<title>__TITLE__ · CodeBee 任务报告</title>
|
|
39
|
+
<style>
|
|
40
|
+
body{margin:0;background:#f5f6f8;color:#1c2330;font:15px/1.65 -apple-system,"PingFang SC","Microsoft YaHei",sans-serif}
|
|
41
|
+
.wrap{max-width:760px;margin:0 auto;padding:28px 18px 60px}
|
|
42
|
+
.head{border-bottom:3px solid #2b6cb0;padding-bottom:14px;margin-bottom:22px}
|
|
43
|
+
h1{margin:0 0 6px;font-size:24px}
|
|
44
|
+
.meta{color:#5a6472;font-size:13px}
|
|
45
|
+
.badge{display:inline-block;border-radius:999px;padding:2px 12px;font-size:13px;font-weight:600}
|
|
46
|
+
.badge.ok{background:#e6f6ec;color:#177245}.badge.bad{background:#fdeaea;color:#b42318}
|
|
47
|
+
.badge.off{background:#eef0f3;color:#5a6472}
|
|
48
|
+
.card{background:#fff;border:1px solid #e3e6ea;border-radius:12px;padding:18px 20px;margin-bottom:18px}
|
|
49
|
+
.card h2{font-size:16px;margin:0 0 12px;color:#2b6cb0}
|
|
50
|
+
table{border-collapse:collapse;width:100%}
|
|
51
|
+
td{padding:6px 10px;border-bottom:1px solid #eef0f3}
|
|
52
|
+
tr.sum td{font-weight:700;border-top:2px solid #e3e6ea}
|
|
53
|
+
.step{display:flex;gap:10px;padding:7px 0;border-bottom:1px dashed #eef0f3;font-size:13.5px;align-items:baseline}
|
|
54
|
+
.step .n{color:#8a93a0;font-weight:700}.step .role{font-weight:600;min-width:88px}
|
|
55
|
+
.step .who{color:#5a6472;min-width:90px}.step .sum{flex:1}.st{font-size:12px}
|
|
56
|
+
.st.done{color:#177245}.st.failed{color:#b42318}
|
|
57
|
+
h1,h2,h3{margin:14px 0 8px}li{margin:4px 0}.trow{padding:2px 0;border-bottom:1px dotted #eee}
|
|
58
|
+
p{margin:8px 0}.mut{color:#8a93a0}code{background:#f0f2f5;padding:1px 6px;border-radius:4px}
|
|
59
|
+
pre{background:#f0f2f5;padding:12px;border-radius:8px;overflow-x:auto;white-space:pre-wrap}
|
|
60
|
+
</style></head><body><div class="wrap">
|
|
61
|
+
<div class="head"><h1>__TITLE__</h1>
|
|
62
|
+
<div class="meta">__META__ · __ST__ · CodeBee 生成于 __NOW__</div></div>
|
|
63
|
+
<div class="card"><h2>结果</h2><span class="badge __STCLS__">__ST__</span>__OVERALL__</div>
|
|
64
|
+
<div class="card"><h2>步骤</h2>__STEPS__</div>
|
|
65
|
+
<div class="card"><h2>报告</h2>__REPORT__</div>
|
|
66
|
+
<div class="meta">由 <b>CodeBee</b> 多智能体编排台生成</div>
|
|
67
|
+
</div></body></html>"""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def render_share_html(run, task, report_text):
|
|
71
|
+
"""渲染自包含分享页。run/task/report_text 缺失时优雅降级。"""
|
|
72
|
+
run = run or {}
|
|
73
|
+
task = task or {}
|
|
74
|
+
status = str(run.get("status") or "")
|
|
75
|
+
st_label = {"done": "已完成", "failed": "失败", "cancelled": "已取消",
|
|
76
|
+
"running": "进行中"}.get(status, status)
|
|
77
|
+
st_cls = {"done": "ok", "failed": "bad", "cancelled": "off"}.get(status, "off")
|
|
78
|
+
verdict = run.get("verdict") or {}
|
|
79
|
+
means = verdict.get("scores") or {}
|
|
80
|
+
|
|
81
|
+
score_rows = ""
|
|
82
|
+
for d, v in means.items():
|
|
83
|
+
score_rows += "<tr><td>%s</td><td>%.1f</td></tr>" % (_esc(d), float(v))
|
|
84
|
+
if verdict:
|
|
85
|
+
score_rows += ('<tr class="sum"><td>综合</td>'
|
|
86
|
+
'<td>%.1f</td></tr>' % float(verdict.get("overall") or 0))
|
|
87
|
+
|
|
88
|
+
steps_rows = ""
|
|
89
|
+
for s in run.get("steps") or []:
|
|
90
|
+
who = s.get("agent_label") or s.get("agent") or ""
|
|
91
|
+
if s.get("model"):
|
|
92
|
+
who += " · " + str(s["model"])
|
|
93
|
+
steps_rows += (
|
|
94
|
+
'<div class="step"><span class="n">%02d</span>'
|
|
95
|
+
'<span class="role">%s</span><span class="who">%s</span>'
|
|
96
|
+
'<span class="sum">%s</span><span class="st %s">%s</span></div>'
|
|
97
|
+
% (s.get("n") or 0, _esc(s.get("role") or ""),
|
|
98
|
+
_esc(who),
|
|
99
|
+
_esc((s.get("summary") or "")[:220]), _esc(s.get("status") or ""),
|
|
100
|
+
_esc(s.get("status") or "")))
|
|
101
|
+
|
|
102
|
+
report_html = _md_ish(report_text) if report_text else "<p class='mut'>(暂无报告)</p>"
|
|
103
|
+
overall_note = ("综合 %.1f 分" % float(verdict.get("overall"))
|
|
104
|
+
if verdict.get("overall") is not None else "")
|
|
105
|
+
|
|
106
|
+
return (_SHARE_TPL
|
|
107
|
+
.replace("__TITLE__", _esc(task.get("title") or run.get("title") or "任务报告"))
|
|
108
|
+
.replace("__META__", _esc(task.get("goal") or ""))
|
|
109
|
+
.replace("__ST__", _esc(st_label))
|
|
110
|
+
.replace("__STCLS__", st_cls)
|
|
111
|
+
.replace("__NOW__", _esc(time.strftime("%Y-%m-%d %H:%M")))
|
|
112
|
+
.replace("__OVERALL__", _esc(overall_note))
|
|
113
|
+
.replace("__STEPS__", steps_rows or "<p class='mut'>(无步骤)</p>")
|
|
114
|
+
.replace("__REPORT__", report_html))
|
package/app/core/skill_scan.py
CHANGED
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""skill 装前危险模式扫描(借鉴 NVIDIA SkillSpector 的静态扫描思路)。
|
|
3
|
-
|
|
4
|
-
研究数据(SkillSpector 对 31,132 个 skill 的分析):26.1% 含漏洞、5.2% 疑似
|
|
5
|
-
恶意。我们在「白名单闸门 + SSRF 防护 + 剥离式检查」之外补一道**内容静态扫描**:
|
|
6
|
-
装前扫 skill 正文里的危险模式,给风险提示——提示不是拦阻(用户仍可装),
|
|
7
|
-
但危险必须被看见。
|
|
8
|
-
|
|
9
|
-
纯内存函数:输入是文本,输出是发现列表,无网络无落盘。
|
|
10
|
-
"""
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
import re
|
|
14
|
-
|
|
15
|
-
# 危险模式(静态正则;命中即在装前提示行展示)
|
|
16
|
-
# —— 每条:(模式, 类别, 中文说明)
|
|
17
|
-
_PATTERNS = [
|
|
18
|
-
# 代码执行
|
|
19
|
-
(r"\beval\s*\(", "代码执行", "动态 eval 执行任意代码"),
|
|
20
|
-
(r"\bexec\s*\(", "代码执行", "exec 执行任意代码"),
|
|
21
|
-
(r"subprocess|os\.system|Popen", "代码执行", "起子进程执行命令"),
|
|
22
|
-
(r"child_process", "代码执行", "Node.js 子进程(可能执行任意命令)"),
|
|
23
|
-
(r"(?:curl|wget)[^\n|]*\|\s*(?:ba|z|da)?sh\b", "代码执行",
|
|
24
|
-
"下载内容直接管道进 shell 执行(dropper 特征)"),
|
|
25
|
-
(r"Invoke-Expression|\biex\s*\(|DownloadString", "代码执行",
|
|
26
|
-
"PowerShell 下载执行(dropper 特征)"),
|
|
27
|
-
# 数据外发
|
|
28
|
-
(r"https?://(?!api\.|docs\.|github\.com|raw\.githubusercontent)[a-z0-9.-]+/(upload|collect|track|ingest|webhook|callback)",
|
|
29
|
-
"数据外发", "向非常规端点上传/回调数据"),
|
|
30
|
-
(r"requests\.post|urllib\.request|fetch\s*\(", "网络请求", "发起网络请求(确认目标可信)"),
|
|
31
|
-
(r"base64.{0,10}(decode|b64decode|atob)", "数据外发",
|
|
32
|
-
"base64 解码(可能隐藏混淆载荷)"),
|
|
33
|
-
(r"pastebin\.com|webhook\.site|requestbin|pipedream\.net|"
|
|
34
|
-
r"ngrok\.(io|app|dev)|trycloudflare\.com", "数据外发",
|
|
35
|
-
"常见外传/中转信道(pastebin、ngrok 隧道、webhook.site 等)"),
|
|
36
|
-
# 持久化与挖矿(治理巡检 09-24 补:装后驻留与资源盗用的静态特征)
|
|
37
|
-
(r"\bcrontab\b|schtasks|LaunchAgents|CurrentVersion\\Run|StartupItems",
|
|
38
|
-
"持久化", "注册定时任务/自启动项(持久化驻留特征)"),
|
|
39
|
-
(r"stratum\+tcp|xmrig|cryptonight", "可疑意图", "加密货币挖矿特征"),
|
|
40
|
-
# 云元数据/内网端点探测(批4 治理 09-25:169.254.169.254 窃取云凭据是
|
|
41
|
-
# 真实攻击面,此前零覆盖)。只认高信号形态:链路本地元数据地址/云元数据
|
|
42
|
-
# 主机名,或私网地址搭配凭据路径——本地起服务提 127.0.0.1:3000 不误报。
|
|
43
|
-
(r"169\.254\.169\.254|169\.254\.\d{1,3}\.\d{1,3}|metadata\.google\.internal",
|
|
44
|
-
"可疑意图", "触碰云元数据端点(窃取云凭据特征)"),
|
|
45
|
-
(r"(?:192\.168\.\d{1,3}\.\d{1,3}|10\.\d{1,3}\.\d{1,3}\.\d{1,3})[^。\n]{0,40}"
|
|
46
|
-
r"(?:meta-data|credentials|secret|token|\.ssh)",
|
|
47
|
-
"可疑意图", "私网地址搭配凭据路径(内网探测特征)"),
|
|
48
|
-
# 敏感信息读取
|
|
49
|
-
(r"os\.environ|process\.env|getenv", "环境读取", "读取环境变量(可能带走密钥)"),
|
|
50
|
-
(r"\.ssh/|\.aws/|\.npmrc|\.gitconfig|credentials|\.env\b", "敏感文件", "触碰凭据/密钥文件路径"),
|
|
51
|
-
(r"keychain|credential manager|dpapi", "敏感文件", "访问系统凭据库"),
|
|
52
|
-
(r"\.claude/|\.zcode/|\.kimi-code/|\.codebee/", "敏感文件",
|
|
53
|
-
"触碰 AI 助手配置目录(可能篡改系统提示词或模型绑定)"),
|
|
54
|
-
# 提示注入特征
|
|
55
|
-
(r"(ignore|disregard|forget).{0,30}(previous|above|prior|all).{0,20}(instruction|prompt|rule)",
|
|
56
|
-
"提示注入", "指令覆盖话术(试图无视既有规则)"),
|
|
57
|
-
(r"(?:无视|忽略|跳过|忘记)[^。\n]{0,8}(?:之前|以上|前面|上述)[^。\n]{0,6}(?:指令|规则|要求|设定|约束)",
|
|
58
|
-
"提示注入", "中文指令覆盖话术(试图无视既有规则)"),
|
|
59
|
-
(r"system prompt|开发者指令|隐藏指令|系统提示词", "提示注入", "提及系统提示词/隐藏指令"),
|
|
60
|
-
(r"do not (tell|reveal|mention).{0,20}(user|human|player)", "提示注入", "要求对用户隐瞒行为"),
|
|
61
|
-
(r"(?:不要|切勿|别)[^。\n]{0,6}(?:告诉|透露|提及|说明|隐瞒)[^。\n]{0,8}(?:用户|主人|玩家)"
|
|
62
|
-
r"|(?:不要|切勿|别)[^。\n]{0,6}(?:用户|主人|玩家)[^。\n]{0,6}(?:隐瞒|透露|告诉|说明)"
|
|
63
|
-
r"|对(?:用户|主人|玩家)隐瞒",
|
|
64
|
-
"提示注入", "中文话术要求对用户隐瞒行为"),
|
|
65
|
-
# 反拒答/越权
|
|
66
|
-
(r"(you (are|must) (now|act as)|从此你是|你现在必须"
|
|
67
|
-
r"|从现在起你(?:是|扮演|成为|要[^。\n]{0,4}服从)|请你扮演)",
|
|
68
|
-
"越权人格", "试图重设助手人格"),
|
|
69
|
-
# SkillSpector 规则对表补充(0929 第四轮:AR/OH/TR 三族缺口)
|
|
70
|
-
(r"(do not|don't|never|不用|无需|不必)[^\n]{0,14}"
|
|
71
|
-
r"(refuse|decline|reject|reject)|(?:拒绝|推辞)[^\n]{0,6}(?:是)?(?:禁止|不允许|违规)"
|
|
72
|
-
r"|you (?:will|must) (?:always|never fail to) comply",
|
|
73
|
-
"提示注入", "反拒答抑制(试图关掉模型的拒绝能力)"),
|
|
74
|
-
(r"(omit|skip|remove)[^\n]{0,16}(disclaimer|warning|safety)"
|
|
75
|
-
r"|(?:省略|跳过|不要加|去掉)[^\n]{0,10}(?:免责|声明|安全(?:提示|警告|策略))",
|
|
76
|
-
"提示注入", "免责/安全声明抑制"),
|
|
77
|
-
(r"(?:your|the) (?:output|response|answer)[^\n]{0,30}"
|
|
78
|
-
r"(?:will be|is) (?:executed|run|evaluated as (?:code|commands?))"
|
|
79
|
-
r"|(?:输出|回答)(?:会|将)[^\n]{0,12}(?:被)?(?:直接)?(?:执行|当作命令|作为代码)",
|
|
80
|
-
"数据外发", "输出即执行指示(作答将被当命令跑——OH1 输出注入)"),
|
|
81
|
-
(r"(?:keyword|trigger|关键词|触发)[^\n]{0,50}"
|
|
82
|
-
r"(?:any|every|all|任何|所有|一切)|"
|
|
83
|
-
r"(?:any|every|all|任何|所有|一切)[^\n]{0,30}"
|
|
84
|
-
r"(?:keyword|trigger|关键词|触发)",
|
|
85
|
-
"可疑意图", "过宽触发条件(任何输入都激活——TR1 触发滥用)"),
|
|
86
|
-
(r"exfiltrat|渗透|后门|backdoor|keylog", "可疑意图", "包含可疑渗透/后门词汇"),
|
|
87
|
-
]
|
|
88
|
-
|
|
89
|
-
_COMPILED = [(re.compile(p, re.I), cat, desc) for p, cat, desc in _PATTERNS]
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def scan_text(text, max_findings=12):
|
|
93
|
-
"""扫描一段 skill 文本。返回 [{category, detail, line}];空列表=干净。
|
|
94
|
-
|
|
95
|
-
line 是 1 起的行号(供装前提示定位)。同一类别只报首个命中
|
|
96
|
-
(提示行是给人看的,重复刷屏没有信息量)。"""
|
|
97
|
-
findings, seen_cats = [], set()
|
|
98
|
-
if not text:
|
|
99
|
-
return []
|
|
100
|
-
lines = text.splitlines()
|
|
101
|
-
for rx, cat, desc in _COMPILED:
|
|
102
|
-
if cat in seen_cats:
|
|
103
|
-
continue
|
|
104
|
-
for i, ln in enumerate(lines, 1):
|
|
105
|
-
if rx.search(ln):
|
|
106
|
-
findings.append({"category": cat, "detail": desc, "line": i})
|
|
107
|
-
seen_cats.add(cat)
|
|
108
|
-
break
|
|
109
|
-
if len(findings) >= max_findings:
|
|
110
|
-
break
|
|
111
|
-
return findings
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def risk_label(findings):
|
|
115
|
-
"""发现列表 → 风险标签(装前提示行用)。
|
|
116
|
-
|
|
117
|
-
三级:高危(代码执行/敏感文件/可疑意图/数据外发/持久化)任一
|
|
118
|
-
命中 = 高风险;中危类(网络请求/环境读取/提示注入/越权人格)≥2 类
|
|
119
|
-
同时命中 = 中风险;有发现 = 注意;空 = 干净。"""
|
|
120
|
-
if not findings:
|
|
121
|
-
return ""
|
|
122
|
-
cats = {f["category"] for f in findings}
|
|
123
|
-
if cats & {"代码执行", "敏感文件", "可疑意图", "数据外发", "持久化"}:
|
|
124
|
-
return "⚠ 高风险"
|
|
125
|
-
medium_cats = cats & {"网络请求", "环境读取", "提示注入", "越权人格"}
|
|
126
|
-
if len(medium_cats) >= 2:
|
|
127
|
-
return "⚠ 中风险"
|
|
128
|
-
return "△ 注意"
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def scan_summary(text):
|
|
132
|
-
"""一步到位:扫描+汇总成一行提示文案(空=干净返回空串)。"""
|
|
133
|
-
fs = scan_text(text)
|
|
134
|
-
if not fs:
|
|
135
|
-
return ""
|
|
136
|
-
label = risk_label(fs)
|
|
137
|
-
top = "、".join("%s(行%d)" % (f["category"], f["line"]) for f in fs[:4])
|
|
138
|
-
more = " 等 %d 项" % len(fs) if len(fs) > 4 else ""
|
|
139
|
-
return "%s:%s%s" % (label, top, more)
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""skill 装前危险模式扫描(借鉴 NVIDIA SkillSpector 的静态扫描思路)。
|
|
3
|
+
|
|
4
|
+
研究数据(SkillSpector 对 31,132 个 skill 的分析):26.1% 含漏洞、5.2% 疑似
|
|
5
|
+
恶意。我们在「白名单闸门 + SSRF 防护 + 剥离式检查」之外补一道**内容静态扫描**:
|
|
6
|
+
装前扫 skill 正文里的危险模式,给风险提示——提示不是拦阻(用户仍可装),
|
|
7
|
+
但危险必须被看见。
|
|
8
|
+
|
|
9
|
+
纯内存函数:输入是文本,输出是发现列表,无网络无落盘。
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
|
|
15
|
+
# 危险模式(静态正则;命中即在装前提示行展示)
|
|
16
|
+
# —— 每条:(模式, 类别, 中文说明)
|
|
17
|
+
_PATTERNS = [
|
|
18
|
+
# 代码执行
|
|
19
|
+
(r"\beval\s*\(", "代码执行", "动态 eval 执行任意代码"),
|
|
20
|
+
(r"\bexec\s*\(", "代码执行", "exec 执行任意代码"),
|
|
21
|
+
(r"subprocess|os\.system|Popen", "代码执行", "起子进程执行命令"),
|
|
22
|
+
(r"child_process", "代码执行", "Node.js 子进程(可能执行任意命令)"),
|
|
23
|
+
(r"(?:curl|wget)[^\n|]*\|\s*(?:ba|z|da)?sh\b", "代码执行",
|
|
24
|
+
"下载内容直接管道进 shell 执行(dropper 特征)"),
|
|
25
|
+
(r"Invoke-Expression|\biex\s*\(|DownloadString", "代码执行",
|
|
26
|
+
"PowerShell 下载执行(dropper 特征)"),
|
|
27
|
+
# 数据外发
|
|
28
|
+
(r"https?://(?!api\.|docs\.|github\.com|raw\.githubusercontent)[a-z0-9.-]+/(upload|collect|track|ingest|webhook|callback)",
|
|
29
|
+
"数据外发", "向非常规端点上传/回调数据"),
|
|
30
|
+
(r"requests\.post|urllib\.request|fetch\s*\(", "网络请求", "发起网络请求(确认目标可信)"),
|
|
31
|
+
(r"base64.{0,10}(decode|b64decode|atob)", "数据外发",
|
|
32
|
+
"base64 解码(可能隐藏混淆载荷)"),
|
|
33
|
+
(r"pastebin\.com|webhook\.site|requestbin|pipedream\.net|"
|
|
34
|
+
r"ngrok\.(io|app|dev)|trycloudflare\.com", "数据外发",
|
|
35
|
+
"常见外传/中转信道(pastebin、ngrok 隧道、webhook.site 等)"),
|
|
36
|
+
# 持久化与挖矿(治理巡检 09-24 补:装后驻留与资源盗用的静态特征)
|
|
37
|
+
(r"\bcrontab\b|schtasks|LaunchAgents|CurrentVersion\\Run|StartupItems",
|
|
38
|
+
"持久化", "注册定时任务/自启动项(持久化驻留特征)"),
|
|
39
|
+
(r"stratum\+tcp|xmrig|cryptonight", "可疑意图", "加密货币挖矿特征"),
|
|
40
|
+
# 云元数据/内网端点探测(批4 治理 09-25:169.254.169.254 窃取云凭据是
|
|
41
|
+
# 真实攻击面,此前零覆盖)。只认高信号形态:链路本地元数据地址/云元数据
|
|
42
|
+
# 主机名,或私网地址搭配凭据路径——本地起服务提 127.0.0.1:3000 不误报。
|
|
43
|
+
(r"169\.254\.169\.254|169\.254\.\d{1,3}\.\d{1,3}|metadata\.google\.internal",
|
|
44
|
+
"可疑意图", "触碰云元数据端点(窃取云凭据特征)"),
|
|
45
|
+
(r"(?:192\.168\.\d{1,3}\.\d{1,3}|10\.\d{1,3}\.\d{1,3}\.\d{1,3})[^。\n]{0,40}"
|
|
46
|
+
r"(?:meta-data|credentials|secret|token|\.ssh)",
|
|
47
|
+
"可疑意图", "私网地址搭配凭据路径(内网探测特征)"),
|
|
48
|
+
# 敏感信息读取
|
|
49
|
+
(r"os\.environ|process\.env|getenv", "环境读取", "读取环境变量(可能带走密钥)"),
|
|
50
|
+
(r"\.ssh/|\.aws/|\.npmrc|\.gitconfig|credentials|\.env\b", "敏感文件", "触碰凭据/密钥文件路径"),
|
|
51
|
+
(r"keychain|credential manager|dpapi", "敏感文件", "访问系统凭据库"),
|
|
52
|
+
(r"\.claude/|\.zcode/|\.kimi-code/|\.codebee/", "敏感文件",
|
|
53
|
+
"触碰 AI 助手配置目录(可能篡改系统提示词或模型绑定)"),
|
|
54
|
+
# 提示注入特征
|
|
55
|
+
(r"(ignore|disregard|forget).{0,30}(previous|above|prior|all).{0,20}(instruction|prompt|rule)",
|
|
56
|
+
"提示注入", "指令覆盖话术(试图无视既有规则)"),
|
|
57
|
+
(r"(?:无视|忽略|跳过|忘记)[^。\n]{0,8}(?:之前|以上|前面|上述)[^。\n]{0,6}(?:指令|规则|要求|设定|约束)",
|
|
58
|
+
"提示注入", "中文指令覆盖话术(试图无视既有规则)"),
|
|
59
|
+
(r"system prompt|开发者指令|隐藏指令|系统提示词", "提示注入", "提及系统提示词/隐藏指令"),
|
|
60
|
+
(r"do not (tell|reveal|mention).{0,20}(user|human|player)", "提示注入", "要求对用户隐瞒行为"),
|
|
61
|
+
(r"(?:不要|切勿|别)[^。\n]{0,6}(?:告诉|透露|提及|说明|隐瞒)[^。\n]{0,8}(?:用户|主人|玩家)"
|
|
62
|
+
r"|(?:不要|切勿|别)[^。\n]{0,6}(?:用户|主人|玩家)[^。\n]{0,6}(?:隐瞒|透露|告诉|说明)"
|
|
63
|
+
r"|对(?:用户|主人|玩家)隐瞒",
|
|
64
|
+
"提示注入", "中文话术要求对用户隐瞒行为"),
|
|
65
|
+
# 反拒答/越权
|
|
66
|
+
(r"(you (are|must) (now|act as)|从此你是|你现在必须"
|
|
67
|
+
r"|从现在起你(?:是|扮演|成为|要[^。\n]{0,4}服从)|请你扮演)",
|
|
68
|
+
"越权人格", "试图重设助手人格"),
|
|
69
|
+
# SkillSpector 规则对表补充(0929 第四轮:AR/OH/TR 三族缺口)
|
|
70
|
+
(r"(do not|don't|never|不用|无需|不必)[^\n]{0,14}"
|
|
71
|
+
r"(refuse|decline|reject|reject)|(?:拒绝|推辞)[^\n]{0,6}(?:是)?(?:禁止|不允许|违规)"
|
|
72
|
+
r"|you (?:will|must) (?:always|never fail to) comply",
|
|
73
|
+
"提示注入", "反拒答抑制(试图关掉模型的拒绝能力)"),
|
|
74
|
+
(r"(omit|skip|remove)[^\n]{0,16}(disclaimer|warning|safety)"
|
|
75
|
+
r"|(?:省略|跳过|不要加|去掉)[^\n]{0,10}(?:免责|声明|安全(?:提示|警告|策略))",
|
|
76
|
+
"提示注入", "免责/安全声明抑制"),
|
|
77
|
+
(r"(?:your|the) (?:output|response|answer)[^\n]{0,30}"
|
|
78
|
+
r"(?:will be|is) (?:executed|run|evaluated as (?:code|commands?))"
|
|
79
|
+
r"|(?:输出|回答)(?:会|将)[^\n]{0,12}(?:被)?(?:直接)?(?:执行|当作命令|作为代码)",
|
|
80
|
+
"数据外发", "输出即执行指示(作答将被当命令跑——OH1 输出注入)"),
|
|
81
|
+
(r"(?:keyword|trigger|关键词|触发)[^\n]{0,50}"
|
|
82
|
+
r"(?:any|every|all|任何|所有|一切)|"
|
|
83
|
+
r"(?:any|every|all|任何|所有|一切)[^\n]{0,30}"
|
|
84
|
+
r"(?:keyword|trigger|关键词|触发)",
|
|
85
|
+
"可疑意图", "过宽触发条件(任何输入都激活——TR1 触发滥用)"),
|
|
86
|
+
(r"exfiltrat|渗透|后门|backdoor|keylog", "可疑意图", "包含可疑渗透/后门词汇"),
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
_COMPILED = [(re.compile(p, re.I), cat, desc) for p, cat, desc in _PATTERNS]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def scan_text(text, max_findings=12):
|
|
93
|
+
"""扫描一段 skill 文本。返回 [{category, detail, line}];空列表=干净。
|
|
94
|
+
|
|
95
|
+
line 是 1 起的行号(供装前提示定位)。同一类别只报首个命中
|
|
96
|
+
(提示行是给人看的,重复刷屏没有信息量)。"""
|
|
97
|
+
findings, seen_cats = [], set()
|
|
98
|
+
if not text:
|
|
99
|
+
return []
|
|
100
|
+
lines = text.splitlines()
|
|
101
|
+
for rx, cat, desc in _COMPILED:
|
|
102
|
+
if cat in seen_cats:
|
|
103
|
+
continue
|
|
104
|
+
for i, ln in enumerate(lines, 1):
|
|
105
|
+
if rx.search(ln):
|
|
106
|
+
findings.append({"category": cat, "detail": desc, "line": i})
|
|
107
|
+
seen_cats.add(cat)
|
|
108
|
+
break
|
|
109
|
+
if len(findings) >= max_findings:
|
|
110
|
+
break
|
|
111
|
+
return findings
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def risk_label(findings):
|
|
115
|
+
"""发现列表 → 风险标签(装前提示行用)。
|
|
116
|
+
|
|
117
|
+
三级:高危(代码执行/敏感文件/可疑意图/数据外发/持久化)任一
|
|
118
|
+
命中 = 高风险;中危类(网络请求/环境读取/提示注入/越权人格)≥2 类
|
|
119
|
+
同时命中 = 中风险;有发现 = 注意;空 = 干净。"""
|
|
120
|
+
if not findings:
|
|
121
|
+
return ""
|
|
122
|
+
cats = {f["category"] for f in findings}
|
|
123
|
+
if cats & {"代码执行", "敏感文件", "可疑意图", "数据外发", "持久化"}:
|
|
124
|
+
return "⚠ 高风险"
|
|
125
|
+
medium_cats = cats & {"网络请求", "环境读取", "提示注入", "越权人格"}
|
|
126
|
+
if len(medium_cats) >= 2:
|
|
127
|
+
return "⚠ 中风险"
|
|
128
|
+
return "△ 注意"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def scan_summary(text):
|
|
132
|
+
"""一步到位:扫描+汇总成一行提示文案(空=干净返回空串)。"""
|
|
133
|
+
fs = scan_text(text)
|
|
134
|
+
if not fs:
|
|
135
|
+
return ""
|
|
136
|
+
label = risk_label(fs)
|
|
137
|
+
top = "、".join("%s(行%d)" % (f["category"], f["line"]) for f in fs[:4])
|
|
138
|
+
more = " 等 %d 项" % len(fs) if len(fs) > 4 else ""
|
|
139
|
+
return "%s:%s%s" % (label, top, more)
|