codebee 0.1.101 → 0.1.103

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.
Files changed (134) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -21
  3. package/README.md +3 -6
  4. package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +88 -0
  5. package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +126 -0
  6. package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
  7. package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +210 -0
  8. package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
  9. package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
  10. package/app/.playwright-cli/run-detail-after.png +0 -0
  11. package/app/core/acceptance.py +100 -100
  12. package/app/core/acp_client.py +457 -426
  13. package/app/core/aiflavor.py +197 -197
  14. package/app/core/analytics.py +38 -38
  15. package/app/core/artifacts.py +148 -148
  16. package/app/core/assets.py +110 -110
  17. package/app/core/attachments.py +623 -623
  18. package/app/core/audit.py +30 -30
  19. package/app/core/backend_profiles.py +188 -188
  20. package/app/core/backup.py +470 -470
  21. package/app/core/beekeeper.py +183 -183
  22. package/app/core/benchstore.py +102 -102
  23. package/app/core/board.py +21 -17
  24. package/app/core/bookmeta.py +1036 -1036
  25. package/app/core/bookmeta_catalog.py +180 -180
  26. package/app/core/branching.py +137 -137
  27. package/app/core/builtin_agent.py +13 -4
  28. package/app/core/capability.py +144 -144
  29. package/app/core/catalog.py +28 -6
  30. package/app/core/chaptersafe.py +32 -32
  31. package/app/core/checkpoints.py +117 -31
  32. package/app/core/cleanup.py +303 -303
  33. package/app/core/command_auth.py +302 -302
  34. package/app/core/compaction.py +201 -201
  35. package/app/core/contracts.py +255 -255
  36. package/app/core/covergen.py +356 -356
  37. package/app/core/defectretro.py +43 -43
  38. package/app/core/diagnostics.py +114 -114
  39. package/app/core/dispatch.py +1 -1
  40. package/app/core/dispatch_log.py +128 -128
  41. package/app/core/env_scrub.py +85 -85
  42. package/app/core/errorlog.py +137 -137
  43. package/app/core/eval_matrix.py +91 -82
  44. package/app/core/evaluation.py +174 -174
  45. package/app/core/failure_notify.py +290 -290
  46. package/app/core/flow_graph.py +33 -33
  47. package/app/core/goal_service.py +167 -167
  48. package/app/core/health.py +389 -389
  49. package/app/core/history.py +38 -38
  50. package/app/core/hooks.py +162 -162
  51. package/app/core/knowledge_pipeline.py +86 -86
  52. package/app/core/manager.py +2385 -2385
  53. package/app/core/mcp_client.py +7 -2
  54. package/app/core/mocks.py +64 -64
  55. package/app/core/notify.py +201 -201
  56. package/app/core/novel_quality.py +181 -181
  57. package/app/core/openapi.py +217 -209
  58. package/app/core/operations.py +228 -228
  59. package/app/core/paihang.py +145 -145
  60. package/app/core/pipeline.py +6 -3
  61. package/app/core/policy.py +9 -0
  62. package/app/core/portguard.py +111 -111
  63. package/app/core/presence.py +77 -77
  64. package/app/core/preview.py +293 -293
  65. package/app/core/project_memory.py +206 -206
  66. package/app/core/prompt_guard.py +78 -78
  67. package/app/core/publish/__init__.py +7 -7
  68. package/app/core/publish/auto.py +659 -562
  69. package/app/core/publish/chaprows.py +138 -138
  70. package/app/core/publish/fanqie.py +16 -10
  71. package/app/core/publish/flow.py +29 -1
  72. package/app/core/publish/flows-fanqie-calibrated.json +442 -442
  73. package/app/core/publish/manager.py +1185 -1177
  74. package/app/core/publish/qimao.py +193 -193
  75. package/app/core/publish/volumes.py +108 -108
  76. package/app/core/publish/ws.py +139 -139
  77. package/app/core/quality_gate.py +201 -201
  78. package/app/core/redact.py +38 -38
  79. package/app/core/repeat_guard.py +129 -129
  80. package/app/core/retrieval.py +137 -137
  81. package/app/core/sandbox_runtime.py +95 -0
  82. package/app/core/selfupdate.py +384 -384
  83. package/app/core/session_log.py +161 -161
  84. package/app/core/sessions.py +312 -312
  85. package/app/core/settings.py +215 -215
  86. package/app/core/settings_schema.py +282 -282
  87. package/app/core/share_page.py +114 -114
  88. package/app/core/skill_scan.py +139 -139
  89. package/app/core/skillpacks/market/character-bible.md +66 -66
  90. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  91. package/app/core/skillpacks/market/git-workflow.md +57 -57
  92. package/app/core/skillpacks/market/release-notes.md +72 -72
  93. package/app/core/skillpacks/market/weekly-report.md +71 -71
  94. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  95. package/app/core/step_runner.py +132 -132
  96. package/app/core/store.py +13 -3
  97. package/app/core/story_tracking.py +275 -275
  98. package/app/core/task_compile.py +199 -199
  99. package/app/core/telemetry.py +292 -292
  100. package/app/core/tlsctx.py +64 -64
  101. package/app/core/token_meter.py +147 -147
  102. package/app/core/tool_outputs.py +107 -107
  103. package/app/core/tracing.py +207 -207
  104. package/app/core/upstream.py +41 -41
  105. package/app/core/usage.py +1038 -1038
  106. package/app/core/volumes.py +389 -389
  107. package/app/core/webhooks.py +89 -89
  108. package/app/core/wxdigest.py +865 -865
  109. package/app/core/zentao.py +2692 -2692
  110. package/app/main.py +1 -0
  111. package/app/mcp_server.py +244 -244
  112. package/app/output/_createbook_body.json +1 -0
  113. package/app/output/_probe_fq_click.py +50 -0
  114. package/app/output/_probe_fq_form.py +42 -0
  115. package/app/output/_probe_fq_modal.py +55 -0
  116. package/app/output/_probe_fq_points.py +71 -0
  117. package/app/output/_probe_fq_submit.py +34 -0
  118. package/app/output/_probe_fq_watch.py +62 -0
  119. package/app/output/workbench-clean-preview.jpg +0 -0
  120. package/app/output/workbench-reference-render.png +0 -0
  121. package/app/output/workbench-shell.png +0 -0
  122. package/app/pick_dialog.py +110 -110
  123. package/app/ui/app.js +165 -29
  124. package/app/ui/artdiff.js +74 -74
  125. package/app/ui/hive3d.js +491 -223
  126. package/app/ui/i18n.js +18 -1
  127. package/app/ui/icons/bee.svg +79 -79
  128. package/app/ui/index.html +24 -4
  129. package/app/ui/manifest.json +16 -16
  130. package/app/ui/qrcode.js +2297 -2297
  131. package/app/ui/style.css +548 -18
  132. package/bin/tutti.js +147 -147
  133. package/package.json +1 -1
  134. package/tools/wx_reader.py +261 -261
@@ -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))
@@ -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)