@comate/zulu 1.4.0-beta.4 → 1.4.0-beta.6
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/comate-engine/assets/skills/auto-commit/SKILL.md +2 -0
- package/comate-engine/assets/skills/auto-commit-sandbox-comate/SKILL.md +2 -2
- package/comate-engine/assets/skills/code-review/SKILL.md +6 -5
- package/comate-engine/assets/skills/code-review/agents/custom-reviewer.md +2 -2
- package/comate-engine/assets/skills/code-review/agents/meta-reviewer.md +2 -2
- package/comate-engine/assets/skills/code-review/agents/style-reviewer.md +72 -10
- package/comate-engine/assets/skills/code-review/references/dispatch-template.md +12 -12
- package/comate-engine/assets/skills/code-review/references/rules/Java/JAVA_STYLE_RULES.md +11 -5
- package/comate-engine/assets/skills/code-security/SKILL.md +110 -41
- package/comate-engine/assets/skills/code-security/references/credential_hosting.md +190 -28
- package/comate-engine/assets/skills/code-security/references/vul_analysis-go_sql_injection.md +149 -0
- package/comate-engine/assets/skills/code-security/references/vul_analysis-java_sql_injection.md +185 -0
- package/comate-engine/assets/skills/code-security/references/vul_analysis-php_sql_injection.md +147 -0
- package/comate-engine/assets/skills/code-security/references/vul_analysis-python_sql_injection.md +143 -0
- package/comate-engine/assets/skills/code-security/references/vul_repair-go_sql_injection.md +2 -2
- package/comate-engine/assets/skills/code-security/references/vul_repair-sca.md +225 -0
- package/comate-engine/assets/skills/code-security/scripts/credential_hosting.py +12 -10
- package/comate-engine/assets/skills/code-security/scripts/credential_open_page.py +125 -0
- package/comate-engine/assets/skills/code-security/scripts/credential_poll.py +12 -9
- package/comate-engine/assets/skills/code-security/scripts/credential_url.py +81 -0
- package/comate-engine/assets/skills/code-security/scripts/ducc/get_claude_session_id.sh +33 -0
- package/comate-engine/assets/skills/code-security/scripts/ducc/open_browser.py +191 -0
- package/comate-engine/assets/skills/code-security/scripts/parse_scan_result.py +99 -16
- package/comate-engine/assets/skills/code-security/scripts/repair_vulnerability.py +66 -13
- package/comate-engine/assets/skills/code-security/scripts/scan_vulnerability.py +44 -12
- package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/SKILL.md +8 -8
- package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/long_running_task.md +0 -15
- package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/testing_strategy.md +1 -1
- package/comate-engine/assets/skills/create-image/SKILL.md +197 -206
- package/comate-engine/assets/skills/create-image/scripts/generate-image.ps1 +213 -0
- package/comate-engine/assets/skills/create-image/scripts/generate-image.sh +322 -0
- package/comate-engine/assets/skills/create-subagent/SKILL.md +23 -5
- package/comate-engine/fallbackServer.js +1 -1
- package/comate-engine/node_modules/@comate/plugin-shared-internals/dist/index.js +1 -1
- package/comate-engine/server.js +89 -66
- package/dist/bundle/index.js +3 -3
- package/package.json +1 -1
- package/scripts/postinstall.js +4 -3
- package/comate-engine/assets/skills/code-review/evals/SKILL.md +0 -334
- package/comate-engine/assets/skills/code-review/evals/agents/gt-generator.md +0 -76
- package/comate-engine/assets/skills/code-review/evals/agents/miner.md +0 -87
- package/comate-engine/assets/skills/code-review/evals/agents/score-judge.md +0 -168
- package/comate-engine/assets/skills/code-review/evals/references/cli-query-template.md +0 -114
- package/comate-engine/assets/skills/code-review/evals/references/gt-schema.md +0 -77
- package/comate-engine/assets/skills/code-review/references/custom-rules/RULE_TEMPLATE.md +0 -141
- /package/comate-engine/assets/commands/{code-review-comate.md → code-review.md} +0 -0
- /package/comate-engine/assets/commands/{debug-comate.md → debug.md} +0 -0
- /package/comate-engine/assets/commands/{unit-test-comate.md → unit-test.md} +0 -0
- /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/backend_dev.md +0 -0
- /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/env_setup.md +0 -0
- /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/frontend_dev.md +0 -0
- /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/git_operations.md +0 -0
- /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/scripts/check_config.py +0 -0
|
@@ -35,6 +35,7 @@ parsed_result.json 格式:
|
|
|
35
35
|
"startLine": 12,
|
|
36
36
|
"endLine": 12,
|
|
37
37
|
"hash": "abc123...",
|
|
38
|
+
"importPath": "com.example:foo:1.2.3",
|
|
38
39
|
"is_sensitive": false,
|
|
39
40
|
"aiAnalysisStatus": 0,
|
|
40
41
|
"aiAnalysisStatusText": "无需分析",
|
|
@@ -43,9 +44,10 @@ parsed_result.json 格式:
|
|
|
43
44
|
|
|
44
45
|
aiAnalysisStatus 说明:
|
|
45
46
|
0 - 无需分析
|
|
46
|
-
1 -
|
|
47
|
+
1 - 分析中(脚本会单独归类到 analyzing_vuls;后续工作流会通过本地兜底分析判定,
|
|
48
|
+
被判定为真实漏洞的会重新合入修复列表)
|
|
47
49
|
2 - 真实漏洞
|
|
48
|
-
3 -
|
|
50
|
+
3 - 误报(始终不进入修复流程)
|
|
49
51
|
"""
|
|
50
52
|
|
|
51
53
|
import argparse
|
|
@@ -83,10 +85,13 @@ def parse_scan_result(scan_result):
|
|
|
83
85
|
# type: (dict) -> dict
|
|
84
86
|
"""解析扫描结果,返回结构化漏洞数据。
|
|
85
87
|
|
|
86
|
-
aiAnalysisStatus=3
|
|
88
|
+
aiAnalysisStatus=3(误报)和 aiAnalysisStatus=1(分析中)的漏洞会被单独归类。
|
|
89
|
+
误报永远不进入修复流程;分析中漏洞需先经工作流的「本地兜底分析」复核,
|
|
90
|
+
被判定为真实漏洞的会被合并回修复列表。
|
|
87
91
|
"""
|
|
88
|
-
data = scan_result.get("data", {})
|
|
89
|
-
|
|
92
|
+
data = scan_result.get("data", {}) or {}
|
|
93
|
+
sarif = data.get("sarif") or {}
|
|
94
|
+
runs = sarif.get("runs") or data.get("runs") or []
|
|
90
95
|
|
|
91
96
|
# 提取 bundleHash(由 scan_vulnerability.py 写入顶层)
|
|
92
97
|
bundle_hash = scan_result.get("bundleHash", "")
|
|
@@ -122,6 +127,8 @@ def parse_scan_result(scan_result):
|
|
|
122
127
|
level = level_config.get("level", "NONE") if level_config else "NONE"
|
|
123
128
|
level_cn = LEVEL_MAP.get(level, "低危")
|
|
124
129
|
vul_hash = result.get("properties", {}).get("hash", "")
|
|
130
|
+
# SCA 类漏洞会带有非空 importPath(依赖引入路径),用于后续判定是否走 SCA 本地修复
|
|
131
|
+
import_path = result.get("properties", {}).get("importPath", "") or ""
|
|
125
132
|
|
|
126
133
|
# AI 分析状态
|
|
127
134
|
ai_status = result.get("properties", {}).get("aiAnalysisStatus", 0)
|
|
@@ -172,6 +179,7 @@ def parse_scan_result(scan_result):
|
|
|
172
179
|
"startLine": start_line,
|
|
173
180
|
"endLine": end_line,
|
|
174
181
|
"hash": vul_hash,
|
|
182
|
+
"importPath": import_path,
|
|
175
183
|
"is_sensitive": is_sensitive,
|
|
176
184
|
"aiAnalysisStatus": ai_status,
|
|
177
185
|
"aiAnalysisStatusText": ai_status_text,
|
|
@@ -197,6 +205,23 @@ def parse_scan_result(scan_result):
|
|
|
197
205
|
false_positive_vuls.sort(key=sort_key)
|
|
198
206
|
analyzing_vuls.sort(key=sort_key)
|
|
199
207
|
|
|
208
|
+
# 去重:仅当 hash 相同(即扫描端认定为同一漏洞)时才合并;
|
|
209
|
+
# 不能仅按 (file, startLine, endLine, is_sensitive) 去重——多条来源不同的漏洞
|
|
210
|
+
# 可能共享同一个 sink 点(例如多个数据流汇入同一行),它们的 hash 各不相同,
|
|
211
|
+
# 必须各自保留以便后续修复完整覆盖。
|
|
212
|
+
def deduplicate(vuls):
|
|
213
|
+
seen = set()
|
|
214
|
+
result = []
|
|
215
|
+
for v in vuls:
|
|
216
|
+
key = v.get("hash") or (v["file"], v["startLine"], v["endLine"], v["is_sensitive"])
|
|
217
|
+
if key not in seen:
|
|
218
|
+
seen.add(key)
|
|
219
|
+
result.append(v)
|
|
220
|
+
return result
|
|
221
|
+
|
|
222
|
+
common_vuls = deduplicate(common_vuls)
|
|
223
|
+
sensitive_vuls = deduplicate(sensitive_vuls)
|
|
224
|
+
|
|
200
225
|
return {
|
|
201
226
|
"total": len(common_vuls) + len(sensitive_vuls) + len(false_positive_vuls) + len(analyzing_vuls),
|
|
202
227
|
"common_count": len(common_vuls),
|
|
@@ -263,6 +288,11 @@ def format_vul_report(vuls, title="漏洞报告", project_dir=""):
|
|
|
263
288
|
loc_link = _make_file_link(vul["file"], vul["startLine"], project_dir)
|
|
264
289
|
lines.append(" - {}".format(loc_link))
|
|
265
290
|
|
|
291
|
+
# SCA 漏洞展示依赖引入路径(importPath),便于定位需要升级的依赖链
|
|
292
|
+
import_path = vul.get("importPath", "")
|
|
293
|
+
if import_path:
|
|
294
|
+
lines.append(" - **依赖引入路径**:`{}`".format(import_path))
|
|
295
|
+
|
|
266
296
|
# 数据流折叠展示
|
|
267
297
|
if vul["codeFlows"]:
|
|
268
298
|
lines.append(" <details><summary>数据流</summary>\n")
|
|
@@ -303,30 +333,32 @@ def format_full_report(parsed, project_dir=""):
|
|
|
303
333
|
if false_positive_count > 0:
|
|
304
334
|
summary_parts.append("**{}** 个误报(已由 AI 分析确认,无需修复)".format(false_positive_count))
|
|
305
335
|
|
|
336
|
+
if analyzing_count > 0:
|
|
337
|
+
summary_parts.append("**{}** 个漏洞正在 AI 分析中(暂未判定)".format(analyzing_count))
|
|
338
|
+
|
|
306
339
|
if summary_parts:
|
|
307
340
|
parts.append("扫描发现 {}。\n".format(",".join(summary_parts)))
|
|
308
341
|
else:
|
|
309
|
-
|
|
310
|
-
parts.append("扫描完成,**{}** 个漏洞正在 AI 分析中,暂未发现已确认的漏洞。\n".format(analyzing_count))
|
|
311
|
-
else:
|
|
312
|
-
parts.append("扫描完成,未发现漏洞。\n")
|
|
342
|
+
parts.append("扫描完成,未发现漏洞。\n")
|
|
313
343
|
return "\n".join(parts)
|
|
314
344
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
# 只有误报没有真实漏洞的情况
|
|
319
|
-
if real_vul_count == 0 and false_positive_count > 0:
|
|
345
|
+
# 只有误报没有真实漏洞、且没有分析中的情况(才能断言“全部为误报”)
|
|
346
|
+
if real_vul_count == 0 and false_positive_count > 0 and analyzing_count == 0:
|
|
320
347
|
parts.append("所有检测到的漏洞均已被 AI 分析确认为误报,无需进行修复。\n")
|
|
321
348
|
if parsed.get("false_positive_vuls"):
|
|
322
349
|
parts.append(format_vul_report(parsed["false_positive_vuls"], "误报漏洞列表(仅供参考)", project_dir))
|
|
323
350
|
return "\n".join(parts)
|
|
324
351
|
|
|
352
|
+
# 仅剩分析中的情况(无真实漏洞、无误报)
|
|
353
|
+
if real_vul_count == 0 and false_positive_count == 0 and analyzing_count > 0:
|
|
354
|
+
parts.append("当前暂无已确认的漏洞,需等待 AI 分析完成后再评估。\n")
|
|
355
|
+
return "\n".join(parts)
|
|
356
|
+
|
|
325
357
|
if parsed["common_vuls"]:
|
|
326
358
|
parts.append(format_vul_report(parsed["common_vuls"], "普通漏洞报告", project_dir))
|
|
327
359
|
|
|
328
360
|
if parsed["sensitive_vuls"]:
|
|
329
|
-
parts.append(
|
|
361
|
+
parts.append(format_sensitive_table(parsed["sensitive_vuls"], 10, project_dir))
|
|
330
362
|
|
|
331
363
|
# 误报漏洞折叠展示
|
|
332
364
|
if false_positive_count > 0:
|
|
@@ -346,6 +378,42 @@ def format_full_report(parsed, project_dir=""):
|
|
|
346
378
|
|
|
347
379
|
return "\n".join(parts)
|
|
348
380
|
|
|
381
|
+
def format_sensitive_table(vuls, collapse_threshold=5, project_dir=""):
|
|
382
|
+
# type: (list, int, str) -> str
|
|
383
|
+
"""将硬编码漏洞列表格式化为 Markdown 表格。
|
|
384
|
+
|
|
385
|
+
当漏洞数量超过 collapse_threshold 时,使用 <details> 标签折叠展示,
|
|
386
|
+
点击可展开或收起;否则直接展示完整表格。
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
vuls: 漏洞列表
|
|
390
|
+
collapse_threshold: 超过该行数时触发折叠(默认 5)
|
|
391
|
+
project_dir: 项目根目录,用于生成可点击的绝对路径链接。若为空,则相对路径链接。
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
Markdown
|
|
395
|
+
"""
|
|
396
|
+
if not vuls:
|
|
397
|
+
return "未发现硬编码漏洞。\n"
|
|
398
|
+
|
|
399
|
+
total = len(vuls)
|
|
400
|
+
header = "| 序号 | 漏洞名称 | 漏洞位置 | 漏洞等级 |\n| --- | --- | --- | --- |"
|
|
401
|
+
rows = []
|
|
402
|
+
for idx, vul in enumerate(vuls, 1):
|
|
403
|
+
loc_link = _make_file_link(vul["file"], vul["startLine"], project_dir)
|
|
404
|
+
rows.append("| {} | {} | {} | {} |".format(idx, vul["name"], loc_link, vul["level_cn"]))
|
|
405
|
+
|
|
406
|
+
if total <= collapse_threshold:
|
|
407
|
+
return "{}\n{}\n".format(header, "\n".join(rows))
|
|
408
|
+
|
|
409
|
+
# 前 collapse_threshold 条直接展示,超出部分用 <details> 折叠
|
|
410
|
+
visible = "{}\n{}".format(header, "\n".join(rows[:collapse_threshold]))
|
|
411
|
+
hidden_rows = rows[collapse_threshold:]
|
|
412
|
+
hidden_table = "{}\n{}".format(header, "\n".join(hidden_rows))
|
|
413
|
+
collapsed = "<details>\n<summary>展开查看剩余 {} 条漏洞</summary>\n\n{}\n\n</details>".format(
|
|
414
|
+
len(hidden_rows), hidden_table
|
|
415
|
+
)
|
|
416
|
+
return "{}\n\n{}\n".format(visible, collapsed)
|
|
349
417
|
|
|
350
418
|
def main():
|
|
351
419
|
"""
|
|
@@ -374,6 +442,21 @@ def main():
|
|
|
374
442
|
# 解析
|
|
375
443
|
parsed = parse_scan_result(scan_result)
|
|
376
444
|
|
|
445
|
+
# 紧凑统计行(必须优先打印,保证即使后续长报告被截断,计数仍可被模型读取)
|
|
446
|
+
stats_line = (
|
|
447
|
+
"STATS: total={total} common={common} sensitive={sensitive}"
|
|
448
|
+
" false_positive={fp} analyzing={analyzing}"
|
|
449
|
+
).format(
|
|
450
|
+
total=parsed["total"],
|
|
451
|
+
common=parsed["common_count"],
|
|
452
|
+
sensitive=parsed["sensitive_count"],
|
|
453
|
+
fp=parsed["false_positive_count"],
|
|
454
|
+
analyzing=parsed["analyzing_count"],
|
|
455
|
+
)
|
|
456
|
+
print(stats_line)
|
|
457
|
+
print(stats_line, file=sys.stderr)
|
|
458
|
+
print("")
|
|
459
|
+
|
|
377
460
|
# 输出 Markdown 报告到标准输出
|
|
378
461
|
report = format_full_report(parsed, args.project_dir)
|
|
379
462
|
print(report)
|
|
@@ -389,4 +472,4 @@ def main():
|
|
|
389
472
|
|
|
390
473
|
|
|
391
474
|
if __name__ == "__main__":
|
|
392
|
-
main()
|
|
475
|
+
main()
|
|
@@ -30,8 +30,8 @@ import utils
|
|
|
30
30
|
|
|
31
31
|
logger = logging.getLogger("repair")
|
|
32
32
|
|
|
33
|
-
# 修复轮询最大次数(每次间隔 3 秒,约
|
|
34
|
-
MAX_REPAIR_POLLS =
|
|
33
|
+
# 修复轮询最大次数(每次间隔 3 秒,约 5 分钟)
|
|
34
|
+
MAX_REPAIR_POLLS = 100
|
|
35
35
|
REPAIR_POLL_INTERVAL = 3
|
|
36
36
|
MAX_UPLOAD_RETRIES = 10
|
|
37
37
|
|
|
@@ -40,8 +40,10 @@ def diff_file_content(file_path, new_content):
|
|
|
40
40
|
# type: (str, str) -> str
|
|
41
41
|
"""比较原文件与修复后内容,使用 difflib 生成精确 diff,返回 diff 列表 JSON。
|
|
42
42
|
|
|
43
|
-
每个不连续的变更区域(hunk)生成独立的 {from_content, to_content}
|
|
44
|
-
|
|
43
|
+
每个不连续的变更区域(hunk)生成独立的 {from_content, to_content} 对。
|
|
44
|
+
携带 n=3 上下文行,确保:
|
|
45
|
+
1. from_content 包含足够上下文在文件中唯一,str.replace 不会误匹配
|
|
46
|
+
2. 纯插入 hunk(只有 + 行)也有上下文锚点,from_content 不会为空字符串
|
|
45
47
|
"""
|
|
46
48
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
47
49
|
old_content = f.read()
|
|
@@ -49,8 +51,8 @@ def diff_file_content(file_path, new_content):
|
|
|
49
51
|
old_lines = old_content.splitlines(keepends=True)
|
|
50
52
|
new_lines = new_content.splitlines(keepends=True)
|
|
51
53
|
|
|
52
|
-
#
|
|
53
|
-
diff = list(difflib.unified_diff(old_lines, new_lines, n=
|
|
54
|
+
# n=3 携带上下文行,避免 from_content 为空或匹配不唯一
|
|
55
|
+
diff = list(difflib.unified_diff(old_lines, new_lines, n=3))
|
|
54
56
|
|
|
55
57
|
diffs = [] # type: list
|
|
56
58
|
current_from = [] # type: list
|
|
@@ -68,6 +70,16 @@ def diff_file_content(file_path, new_content):
|
|
|
68
70
|
for line in diff:
|
|
69
71
|
if line.startswith("---") or line.startswith("+++"):
|
|
70
72
|
continue
|
|
73
|
+
elif line.startswith("\\"):
|
|
74
|
+
# 防御性处理:兼容 git/POSIX 风格 diff 的 "" 标记。
|
|
75
|
+
# Python 标准库 difflib 当前版本不会输出该标记,但 splitlines(keepends=True)
|
|
76
|
+
# 已经天然保留了原文件是否以换行结尾的状态——若出现该标记,说明上一行实际无尾随换行,
|
|
77
|
+
# 此处主动剥掉可能被误加入的 "\n",确保 from_content 能在原文件中精确匹配。
|
|
78
|
+
if current_from and current_from[-1].endswith("\n"):
|
|
79
|
+
current_from[-1] = current_from[-1][:-1]
|
|
80
|
+
if current_to and current_to[-1].endswith("\n"):
|
|
81
|
+
current_to[-1] = current_to[-1][:-1]
|
|
82
|
+
continue
|
|
71
83
|
elif line.startswith("@@"):
|
|
72
84
|
# 新 hunk 开始,先保存上一组
|
|
73
85
|
_flush()
|
|
@@ -75,6 +87,11 @@ def diff_file_content(file_path, new_content):
|
|
|
75
87
|
current_from.append(line[1:])
|
|
76
88
|
elif line.startswith("+"):
|
|
77
89
|
current_to.append(line[1:])
|
|
90
|
+
else:
|
|
91
|
+
# 上下文行(空格前缀),同时加入 from 和 to 作为锚点
|
|
92
|
+
context_line = line[1:]
|
|
93
|
+
current_from.append(context_line)
|
|
94
|
+
current_to.append(context_line)
|
|
78
95
|
|
|
79
96
|
# 保存最后一组
|
|
80
97
|
_flush()
|
|
@@ -133,7 +150,7 @@ def repair_vulnerability(root_path, vulnerability_info, username, user_id, chat_
|
|
|
133
150
|
if upload_retry_count > MAX_UPLOAD_RETRIES:
|
|
134
151
|
print("错误: 修复上传重试 {} 次后仍有缺失文件".format(MAX_UPLOAD_RETRIES), file=sys.stderr)
|
|
135
152
|
return {"status": -1, "message": "修复上传重试次数超限"}
|
|
136
|
-
print("上传缺失文件: {} 个 (重试 {}/{})".format(len(missing),
|
|
153
|
+
print("上传缺失文件: {} 个 (重试 {}/{})".format(len(missing),
|
|
137
154
|
upload_retry_count, MAX_UPLOAD_RETRIES), file=sys.stderr)
|
|
138
155
|
upload_files_for_repair(root_path, missing, username, user_id, chat_id)
|
|
139
156
|
time.sleep(REPAIR_POLL_INTERVAL)
|
|
@@ -155,21 +172,39 @@ def repair_vulnerability(root_path, vulnerability_info, username, user_id, chat_
|
|
|
155
172
|
return result
|
|
156
173
|
|
|
157
174
|
poll_count += 1
|
|
158
|
-
|
|
175
|
+
progress = min(99, int(poll_count * 100 / MAX_REPAIR_POLLS))
|
|
176
|
+
print("修复中... {}%".format(progress), file=sys.stderr)
|
|
159
177
|
time.sleep(REPAIR_POLL_INTERVAL)
|
|
160
178
|
|
|
161
|
-
print("错误:
|
|
179
|
+
print("错误: 修复超时,已等待超过预期时间", file=sys.stderr)
|
|
162
180
|
return {"status": -1, "message": "修复超时"}
|
|
163
181
|
|
|
164
182
|
|
|
183
|
+
def is_sca_vuln(vul):
|
|
184
|
+
# type: (dict) -> bool
|
|
185
|
+
"""判定是否为 SCA 类漏洞。
|
|
186
|
+
|
|
187
|
+
SCA 漏洞的修复方式是升级依赖版本,与代码改写无关,后端修复接口对其无能为力,
|
|
188
|
+
因此直接在本地参考修复手册(references/vul_repair-sca.md)处理,不发起后端请求。
|
|
189
|
+
|
|
190
|
+
判定规则:漏洞条目带有非空 `importPath` 字段即为 SCA 漏洞。
|
|
191
|
+
"""
|
|
192
|
+
return bool(vul.get("importPath"))
|
|
193
|
+
|
|
194
|
+
|
|
165
195
|
def build_vulnerability_info(parsed):
|
|
166
196
|
# type: (dict) -> dict
|
|
167
|
-
"""从 parsed_result.json 构建修复接口所需的 vulnerability-info。
|
|
197
|
+
"""从 parsed_result.json 构建修复接口所需的 vulnerability-info。
|
|
198
|
+
|
|
199
|
+
SCA 类漏洞会被过滤掉,不进入后端修复请求,由 agent 在本地参考手册自行修复。
|
|
200
|
+
"""
|
|
168
201
|
bundle_hash = parsed.get("bundle_hash", "")
|
|
169
202
|
common_vuls = parsed.get("common_vuls", [])
|
|
170
203
|
|
|
171
204
|
file_map = {} # type: dict
|
|
172
205
|
for vul in common_vuls:
|
|
206
|
+
if is_sca_vuln(vul):
|
|
207
|
+
continue
|
|
173
208
|
fname = vul.get("file", "")
|
|
174
209
|
if not fname:
|
|
175
210
|
continue
|
|
@@ -207,7 +242,7 @@ def main():
|
|
|
207
242
|
|
|
208
243
|
user_id = utils.make_user_id(args.username)
|
|
209
244
|
|
|
210
|
-
logger.info("repair_vulnerability start: username=%s, chat_id=%s, root_path=%s", args.username,
|
|
245
|
+
logger.info("repair_vulnerability start: username=%s, chat_id=%s, root_path=%s", args.username,
|
|
211
246
|
args.chat_id, args.root_path)
|
|
212
247
|
|
|
213
248
|
root_path = os.path.realpath(args.root_path)
|
|
@@ -228,6 +263,23 @@ def main():
|
|
|
228
263
|
print("无普通漏洞需要修复", file=sys.stderr)
|
|
229
264
|
sys.exit(0)
|
|
230
265
|
vulnerability_info = build_vulnerability_info(parsed)
|
|
266
|
+
# 全部为 SCA 漏洞时,后端 files 为空,直接走本地兜底,由 agent 参考
|
|
267
|
+
# references/vul_repair-sca.md 修复
|
|
268
|
+
if not vulnerability_info.get("files"):
|
|
269
|
+
sca_count = sum(1 for v in parsed.get("common_vuls", []) if is_sca_vuln(v))
|
|
270
|
+
result = {
|
|
271
|
+
"status": -3,
|
|
272
|
+
"message": "all_vulns_are_sca_use_local_fallback",
|
|
273
|
+
"fallback": True,
|
|
274
|
+
"sca_only": True,
|
|
275
|
+
"sca_count": sca_count,
|
|
276
|
+
"data": {"files": []},
|
|
277
|
+
}
|
|
278
|
+
output_file = os.path.join(output_dir, "repair_result.json")
|
|
279
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
280
|
+
json.dump(result, f, ensure_ascii=False, indent=2)
|
|
281
|
+
print(output_file)
|
|
282
|
+
return
|
|
231
283
|
else:
|
|
232
284
|
try:
|
|
233
285
|
vulnerability_info = json.loads(args.vulnerability_info)
|
|
@@ -235,7 +287,8 @@ def main():
|
|
|
235
287
|
print("错误: 漏洞信息 JSON 解析失败: {}".format(e), file=sys.stderr)
|
|
236
288
|
sys.exit(1)
|
|
237
289
|
|
|
238
|
-
result = repair_vulnerability(
|
|
290
|
+
result = repair_vulnerability(
|
|
291
|
+
root_path, vulnerability_info, args.username, user_id, args.chat_id)
|
|
239
292
|
|
|
240
293
|
output_file = os.path.join(output_dir, "repair_result.json")
|
|
241
294
|
with open(output_file, "w", encoding="utf-8") as f:
|
|
@@ -244,4 +297,4 @@ def main():
|
|
|
244
297
|
|
|
245
298
|
|
|
246
299
|
if __name__ == "__main__":
|
|
247
|
-
main()
|
|
300
|
+
main()
|
|
@@ -244,16 +244,31 @@ def get_analyzing_count(result):
|
|
|
244
244
|
return count
|
|
245
245
|
|
|
246
246
|
|
|
247
|
+
def _has_valid_runs(result):
|
|
248
|
+
# type: (dict) -> bool
|
|
249
|
+
"""检查结果中是否包含有效的 runs 数据(非空列表)。"""
|
|
250
|
+
data = result.get("data") or {}
|
|
251
|
+
sarif = data.get("sarif") or {}
|
|
252
|
+
runs = sarif.get("runs") or data.get("runs")
|
|
253
|
+
return bool(runs)
|
|
254
|
+
|
|
255
|
+
|
|
247
256
|
def _poll_ai_analysis(scan_info, chat_id, username, user_id, ai_analysis_timeout):
|
|
248
257
|
# type: (dict, str, str, str, int) -> dict
|
|
249
|
-
"""轮询等待 AI 分析完成,返回最新结果。
|
|
258
|
+
"""轮询等待 AI 分析完成,返回最新结果。
|
|
259
|
+
|
|
260
|
+
维护 last_valid_result:仅当响应 status != 1 且 runs 非空时更新。
|
|
261
|
+
超时或循环结束时返回 last_valid_result(而非最后一次可能为空的响应),
|
|
262
|
+
避免服务端中间态导致漏洞数据丢失。
|
|
263
|
+
"""
|
|
250
264
|
start_time = time.time()
|
|
251
265
|
poll_interval = 10
|
|
266
|
+
last_valid_result = None # type: dict | None
|
|
252
267
|
|
|
253
268
|
while True:
|
|
254
269
|
elapsed = time.time() - start_time
|
|
255
270
|
if elapsed >= ai_analysis_timeout:
|
|
256
|
-
print("\nAI 分析等待超时(已等待 {}
|
|
271
|
+
print("\nAI 分析等待超时(已等待 {} 分钟),将使用最近有效结果继续".format(
|
|
257
272
|
int(elapsed // 60)), file=sys.stderr)
|
|
258
273
|
logger.warning("AI analysis timeout after %d seconds", int(elapsed))
|
|
259
274
|
break
|
|
@@ -266,17 +281,29 @@ def _poll_ai_analysis(scan_info, chat_id, username, user_id, ai_analysis_timeout
|
|
|
266
281
|
json_body=scan_info,
|
|
267
282
|
)
|
|
268
283
|
|
|
284
|
+
# 记录有效结果:status != 1 且 runs 非空
|
|
285
|
+
if result.get("status") != 1 and _has_valid_runs(result):
|
|
286
|
+
last_valid_result = result
|
|
287
|
+
|
|
269
288
|
analyzing_count = get_analyzing_count(result)
|
|
270
|
-
|
|
289
|
+
# status=1 表示服务端扫描/分析仍在进行中,此时 runs 通常为 null,
|
|
290
|
+
# 不能仅凭 analyzing_count == 0 就判定分析完成,需要 status != 1 才算真正完成
|
|
291
|
+
if result.get("status") != 1 and analyzing_count == 0:
|
|
271
292
|
print("\nAI 分析完成!", file=sys.stderr)
|
|
272
293
|
logger.info("AI analysis completed after %d seconds", int(time.time() - start_time))
|
|
273
294
|
return result
|
|
274
295
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
296
|
+
elapsed_progress = min(99, int(elapsed * 100 / ai_analysis_timeout)) if ai_analysis_timeout > 0 else 0
|
|
297
|
+
if result.get("status") == 1:
|
|
298
|
+
print("AI 分析中... {}%(服务端仍在处理)".format(elapsed_progress), file=sys.stderr)
|
|
299
|
+
else:
|
|
300
|
+
print("AI 分析中... {}%(剩余 {} 个漏洞待分析)".format(
|
|
301
|
+
elapsed_progress, analyzing_count), file=sys.stderr)
|
|
279
302
|
|
|
303
|
+
# 超时:优先返回最近一次有效结果,避免返回 runs 为空的中间态
|
|
304
|
+
if last_valid_result is not None:
|
|
305
|
+
return last_valid_result
|
|
306
|
+
# 兜底:如果从未拿到过有效结果,返回最后一次响应(调用方会做写入前校验)
|
|
280
307
|
return result
|
|
281
308
|
|
|
282
309
|
|
|
@@ -325,11 +352,12 @@ def scan_vulnerability(root_path, chat_id="", username="", user_id="", wait_ai=T
|
|
|
325
352
|
if result.get("status") != 1:
|
|
326
353
|
break
|
|
327
354
|
poll_count += 1
|
|
328
|
-
|
|
355
|
+
progress = min(99, int(poll_count * 100 / MAX_SCAN_POLLS))
|
|
356
|
+
print("扫描中... {}%".format(progress), file=sys.stderr)
|
|
329
357
|
time.sleep(SCAN_POLL_INTERVAL)
|
|
330
358
|
|
|
331
359
|
if result.get("status") == 1:
|
|
332
|
-
print("错误:
|
|
360
|
+
print("错误: 扫描超时,已等待超过预期时间", file=sys.stderr)
|
|
333
361
|
return {"status": -1, "message": "扫描超时"}, bundle_hash
|
|
334
362
|
|
|
335
363
|
# 第二阶段:等待 AI 分析完成(可选)
|
|
@@ -417,10 +445,14 @@ def main():
|
|
|
417
445
|
root_path, args.scan_result, chat_id=args.chat_id,
|
|
418
446
|
username=args.username, user_id=user_id,
|
|
419
447
|
)
|
|
420
|
-
# 原地更新 scan_result.json
|
|
448
|
+
# 原地更新 scan_result.json(仅当结果包含有效 runs 时才覆盖,防止超时中间态清空漏洞)
|
|
421
449
|
output_file = os.path.realpath(args.scan_result)
|
|
422
|
-
|
|
423
|
-
|
|
450
|
+
if _has_valid_runs(result):
|
|
451
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
452
|
+
json.dump(result, f, ensure_ascii=False, indent=2)
|
|
453
|
+
else:
|
|
454
|
+
print("警告: AI 分析结果中 runs 为空,保留原 scan_result.json 不覆盖", file=sys.stderr)
|
|
455
|
+
logger.warning("AI analysis result has empty runs, skip overwriting %s", output_file)
|
|
424
456
|
print(output_file)
|
|
425
457
|
else:
|
|
426
458
|
# 正常扫描模式
|
package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/SKILL.md
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: create-automation
|
|
2
|
+
name: create-automation
|
|
3
3
|
description: |
|
|
4
4
|
帮助用户创建、配置和管理 Comate Automation 自动化任务。当用户表达以下意图时触发:
|
|
5
5
|
- 创建/设置/新建自动化任务、定时任务、cron 任务
|
|
@@ -7,6 +7,10 @@ description: |
|
|
|
7
7
|
- "每天/每周/每月自动帮我做..."
|
|
8
8
|
- 配置 webhook 触发任务
|
|
9
9
|
不适用于普通的一次性任务请求,仅适用于需要周期性或事件驱动执行的自动化配置。
|
|
10
|
+
metadata:
|
|
11
|
+
enableWhen:
|
|
12
|
+
- isComateIDE
|
|
13
|
+
disable-model-invocation: true
|
|
10
14
|
---
|
|
11
15
|
|
|
12
16
|
# create_automation_tasks Skill
|
|
@@ -54,8 +58,6 @@ execution:
|
|
|
54
58
|
---
|
|
55
59
|
|
|
56
60
|
这里写任务指令(query)。必须自包含,包含所有必要上下文。
|
|
57
|
-
|
|
58
|
-
完成后请将执行摘要写入 promise_done_summary.md。
|
|
59
61
|
````
|
|
60
62
|
|
|
61
63
|
同一任务可配置多个 trigger(schedule 至多一个):
|
|
@@ -122,7 +124,6 @@ AUTOMATION.md 的 body(query)会被一个独立的 Comate Agent Session 执
|
|
|
122
124
|
**Agent 已知的上下文**:
|
|
123
125
|
- 通过 Unattended Prompt 注入,Agent 知道自己是**无人值守自动化 Session**,不会等待用户确认
|
|
124
126
|
- Agent 知道触发类型、时间、workspace、Git 状态等元信息
|
|
125
|
-
- Agent 知道完成任务后必须写入 `promise_done_summary.md` 文件作为成功信号
|
|
126
127
|
|
|
127
128
|
**Agent 不具备的能力**:
|
|
128
129
|
- 没有本次对话的历史上下文(每次 run 是全新 Session)
|
|
@@ -177,7 +178,7 @@ Agent 已自动加载 `.comate/rules/` 下的所有规则,大多数情况下
|
|
|
177
178
|
2. **按需读取相关模板**:只读取与用户任务相关的模板
|
|
178
179
|
3. **提取策略融入 query 或直接引用**:
|
|
179
180
|
- 如果模板中的策略需要针对用户项目做具体适配(如替换具体命令、路径),则从模板中提取要点写进 query
|
|
180
|
-
- 如果模板整体适用且无需适配,可以在 query 中引用模板的**绝对路径**让执行 Agent 自行读取,例如:`关于 Git 操作细节,参考
|
|
181
|
+
- 如果模板整体适用且无需适配,可以在 query 中引用模板的**绝对路径**让执行 Agent 自行读取,例如:`关于 Git 操作细节,参考 ~/.comate/skills/.system/create-automation/references/git_operations.md`(注意:必须使用绝对路径,不能用 `{skill_dir}` 变量,因为执行 Agent 无法解析该变量)
|
|
181
182
|
|
|
182
183
|
---
|
|
183
184
|
|
|
@@ -211,7 +212,7 @@ Agent 已自动加载 `.comate/rules/` 下的所有规则,大多数情况下
|
|
|
211
212
|
|
|
212
213
|
在设计 query 之前,先判断任务复杂度,选择对应策略:
|
|
213
214
|
|
|
214
|
-
- **一条命令能完成**(跑测试、lint、生成报告)→
|
|
215
|
+
- **一条命令能完成**(跑测试、lint、生成报告)→ 直接写命令,**不读取任何模板,不添加任何额外策略**。示例 query:`运行 npm test。`
|
|
215
216
|
- **2-3 个步骤,逻辑清晰**(依赖更新 + 测试验证)→ 按需读取 1-2 个模板,提取适用要点
|
|
216
217
|
- **多步骤端到端流程**(拉代码 → 修改 → 测试 → push)→ 读取多个模板组合设计,参考 `long_running_task.md` 设计分步方案
|
|
217
218
|
|
|
@@ -219,7 +220,6 @@ Agent 已自动加载 `.comate/rules/` 下的所有规则,大多数情况下
|
|
|
219
220
|
- **环境配置写在 query 最前面**(如果需要的话):Agent 在全新 Session 中启动,不会继承用户终端的环境。参考 `references/env_setup.md` 中的策略
|
|
220
221
|
- **利用 Skills/Rules 引用减少 query 冗余**:不需要在 query 中重复写 Rules 中已有的规范,Agent 会自动加载。但可以提示 Agent 注意某个 Rule 或使用某个 Skill(见上方"引用 Skills 和 Rules"语法)
|
|
221
222
|
- 所有必要信息写进 query(不能依赖对话历史或隐含假设)
|
|
222
|
-
- 结尾加上:`完成后请将执行摘要写入 promise_done_summary.md。`
|
|
223
223
|
|
|
224
224
|
**3. 展示 AUTOMATION.md 并确认**
|
|
225
225
|
|
|
@@ -237,7 +237,7 @@ python3 {skill_dir}/scripts/check_config.py ~/.comate/automations/{taskName}/AUT
|
|
|
237
237
|
|
|
238
238
|
**5. 引导试跑**
|
|
239
239
|
|
|
240
|
-
> "配置已就绪。建议先去 Automations Dashboard 找到该任务,点击**立即运行(Run Now)**跑一次,在 Session History
|
|
240
|
+
> "配置已就绪。建议先去 Automations Dashboard 找到该任务,点击**立即运行(Run Now)**跑一次,在 Session History 中查看结果是否符合预期"
|
|
241
241
|
|
|
242
242
|
### 管理已有任务
|
|
243
243
|
|
|
@@ -58,21 +58,6 @@ Automation 任务可能被重复执行(定时触发、补跑等),query 设
|
|
|
58
58
|
|
|
59
59
|
关键原则:**区分"代码问题"和"环境问题"**。如果测试失败是因为环境配置(缺少依赖、端口占用、服务未启动),应尝试修复环境而非修改代码。至少尝试 3 种不同的修复方案后再判定为阻塞。
|
|
60
60
|
|
|
61
|
-
### 执行摘要规范
|
|
62
|
-
|
|
63
|
-
Automation Agent 的唯一交付物是 `promise_done_summary.md`。对于复杂任务,建议在 query 中明确摘要格式:
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
完成后将执行摘要写入 promise_done_summary.md,包含:
|
|
67
|
-
- 执行了哪些步骤,每步的结果(成功/失败/跳过)
|
|
68
|
-
- 如果有代码修改,列出修改的文件和改动性质
|
|
69
|
-
- 关键的终端输出、测试结果数据(作为验证证据)
|
|
70
|
-
- 遇到的问题和处理方式
|
|
71
|
-
- 需要人工关注的事项(如:测试覆盖不足、某个修复需要人工确认等)
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
这份摘要是用户判断任务执行质量的唯一依据,应当清晰、诚实、包含充分证据。如果任务未完全成功,不要写这个文件——让 Session 以 stopped 状态结束,摘要中记录原因即可。
|
|
75
|
-
|
|
76
61
|
## query 片段示例
|
|
77
62
|
|
|
78
63
|
**场景:全量安全扫描 + 自动修复**
|