@comate/zulu 1.3.5 → 1.3.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/server.js +21 -21
- package/dist/bundle/index.js +2 -2
- package/package.json +1 -1
- package/comate-engine/assets/skills/auto-commit/SKILL.md +0 -436
- package/comate-engine/assets/skills/auto-commit/references/issue_type_mapping.json +0 -19
- package/comate-engine/assets/skills/auto-commit/references/new_version_instruction.md +0 -196
- package/comate-engine/assets/skills/auto-commit/references/old_version_instruction.md +0 -189
- package/comate-engine/assets/skills/auto-commit/references/query_reference.md +0 -176
- package/comate-engine/assets/skills/auto-commit/scripts/compat.py +0 -86
- package/comate-engine/assets/skills/auto-commit/scripts/create_card_cli.py +0 -67
- package/comate-engine/assets/skills/auto-commit/scripts/git_diff_cli.py +0 -196
- package/comate-engine/assets/skills/auto-commit/scripts/git_utils.py +0 -230
- package/comate-engine/assets/skills/auto-commit/scripts/icafe/__init__.py +0 -66
- package/comate-engine/assets/skills/auto-commit/scripts/icafe/client.py +0 -473
- package/comate-engine/assets/skills/auto-commit/scripts/icafe/farseer.py +0 -52
- package/comate-engine/assets/skills/auto-commit/scripts/icafe/matching.py +0 -781
- package/comate-engine/assets/skills/auto-commit/scripts/logger.py +0 -32
- package/comate-engine/assets/skills/auto-commit/scripts/match_card_cli.py +0 -37
- package/comate-engine/assets/skills/auto-commit/scripts/recognize_card_cli.py +0 -63
- package/comate-engine/assets/skills/auto-commit-comate/references/new_version_instruction.md +0 -209
- package/comate-engine/assets/skills/auto-commit-comate/references/old_version_instruction.md +0 -208
- package/comate-engine/assets/skills/auto-commit-comate/scripts/compat.py +0 -86
- package/comate-engine/assets/skills/build-web-page-comate/SKILL.md +0 -160
- package/comate-engine/assets/skills/build-web-page-comate/setup-html-scaffold.md +0 -49
- package/comate-engine/assets/skills/build-web-page-comate/setup-react-scaffold.md +0 -103
- package/comate-engine/assets/skills/build-web-page-comate/work-with-user-intent.md +0 -112
- package/comate-engine/assets/skills/code-security/SKILL.md +0 -176
- package/comate-engine/assets/skills/code-security/references/credential_hosting.md +0 -102
- package/comate-engine/assets/skills/code-security/references/vul_repair_sensitive.md +0 -219
- package/comate-engine/assets/skills/code-security/scripts/build_repair_info.py +0 -0
- package/comate-engine/assets/skills/code-security/scripts/credential_hosting.py +0 -99
- package/comate-engine/assets/skills/code-security/scripts/credential_poll.py +0 -350
- package/comate-engine/assets/skills/code-security/scripts/http_client.py +0 -173
- package/comate-engine/assets/skills/code-security/scripts/parse_scan_result.py +0 -301
- package/comate-engine/assets/skills/code-security/scripts/repair_vulnerability.py +0 -261
- package/comate-engine/assets/skills/code-security/scripts/report_chat.py +0 -198
- package/comate-engine/assets/skills/code-security/scripts/scan_vulnerability.py +0 -316
- package/comate-engine/assets/skills/comate-docs-comate/references/query_content.md +0 -83
- package/comate-engine/assets/skills/comate-docs-comate/references/query_repo.md +0 -57
- package/comate-engine/assets/skills/comate-docs-comate/scripts/ku_operator.py +0 -1575
- package/comate-engine/assets/skills/create-skill-comate/references/output-patterns.md +0 -82
- package/comate-engine/assets/skills/create-skill-comate/references/workflows.md +0 -28
- package/comate-engine/assets/skills/create-skill-comate/scripts/init_skill.py +0 -308
- package/comate-engine/node_modules/@comate/plugin-host/dist/index-B8VdZIx4.js +0 -1
- package/comate-engine/node_modules/@comate/plugin-host/dist/index-QEN4ay0E.js +0 -1
- package/comate-engine/node_modules/@comate/plugin-host/dist/user-DAIE9qbz.js +0 -44
- package/comate-engine/node_modules/@comate/plugin-host/dist/user-vP8ulngb.js +0 -44
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
扫描结果解析与展示工具 - 解析 scan_result.json 文件,输出标准化漏洞报告。
|
|
4
|
-
|
|
5
|
-
用法:
|
|
6
|
-
python3 parse_scan_result.py --scan-result <扫描结果文件路径> [--output-dir <输出目录>]
|
|
7
|
-
|
|
8
|
-
输出:
|
|
9
|
-
1. 标准输出打印 Markdown 格式漏洞报告(供直接展示给用户)
|
|
10
|
-
2. 在输出目录生成 parsed_result.json,包含结构化漏洞数据(供后续修复脚本使用)
|
|
11
|
-
|
|
12
|
-
parsed_result.json 格式:
|
|
13
|
-
{
|
|
14
|
-
"total": 10,
|
|
15
|
-
"common_count": 7,
|
|
16
|
-
"sensitive_count": 3,
|
|
17
|
-
"bundle_hash": "xxx",
|
|
18
|
-
"common_vuls": [ ... ],
|
|
19
|
-
"sensitive_vuls": [ ... ]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
每个漏洞条目包含:
|
|
23
|
-
{
|
|
24
|
-
"ruleID": "codescan_java_mybatis-java_sqli",
|
|
25
|
-
"name": "Sql 注入漏洞",
|
|
26
|
-
"description": "...",
|
|
27
|
-
"suggestion": "...",
|
|
28
|
-
"level": "ERROR",
|
|
29
|
-
"level_cn": "严重",
|
|
30
|
-
"file": "src/main/java/...",
|
|
31
|
-
"startLine": 12,
|
|
32
|
-
"endLine": 12,
|
|
33
|
-
"hash": "abc123...",
|
|
34
|
-
"is_sensitive": false,
|
|
35
|
-
"codeFlows": [ {"file": "...", "line": 63, "message": "..."}, ... ]
|
|
36
|
-
}
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
import argparse
|
|
40
|
-
from collections import OrderedDict
|
|
41
|
-
import json
|
|
42
|
-
import os
|
|
43
|
-
import sys
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
LEVEL_MAP = {
|
|
47
|
-
"ERROR": "严重",
|
|
48
|
-
"WARNING": "高危",
|
|
49
|
-
"NOTE": "中危",
|
|
50
|
-
"NONE": "低危",
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
# 等级排序优先级(越小越严重)
|
|
54
|
-
LEVEL_PRIORITY = {
|
|
55
|
-
"ERROR": 0,
|
|
56
|
-
"WARNING": 1,
|
|
57
|
-
"NOTE": 2,
|
|
58
|
-
"NONE": 3,
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def parse_scan_result(scan_result):
|
|
63
|
-
# type: (dict) -> dict
|
|
64
|
-
"""解析扫描结果,返回结构化漏洞数据。"""
|
|
65
|
-
data = scan_result.get("data", {})
|
|
66
|
-
runs = data.get("sarif", {}).get("runs", []) or data.get("runs", [])
|
|
67
|
-
|
|
68
|
-
# 提取 bundleHash(由 scan_vulnerability.py 写入顶层)
|
|
69
|
-
bundle_hash = scan_result.get("bundleHash", "")
|
|
70
|
-
|
|
71
|
-
# 建立 ruleID -> rule 映射
|
|
72
|
-
rule_map = {}
|
|
73
|
-
for run in runs:
|
|
74
|
-
tool = run.get("tool", {})
|
|
75
|
-
# 兼容 rules 在 tool 或 tool.driver 下
|
|
76
|
-
rules = tool.get("rules", []) or tool.get("driver", {}).get("rules", [])
|
|
77
|
-
for rule in rules:
|
|
78
|
-
rule_id = rule.get("id", "")
|
|
79
|
-
if rule_id:
|
|
80
|
-
rule_map[rule_id] = rule
|
|
81
|
-
|
|
82
|
-
# 解析漏洞结果
|
|
83
|
-
common_vuls = []
|
|
84
|
-
sensitive_vuls = []
|
|
85
|
-
|
|
86
|
-
for run in runs:
|
|
87
|
-
results = run.get("results", []) or run.get("result", [])
|
|
88
|
-
for result in results:
|
|
89
|
-
rule_id = result.get("ruleID", "") or result.get("ruleId", "")
|
|
90
|
-
rule = rule_map.get(rule_id, {})
|
|
91
|
-
|
|
92
|
-
# 基础信息
|
|
93
|
-
name = rule.get("name", rule_id)
|
|
94
|
-
description = rule.get("description", "") or result.get("message", {}).get("text", "")
|
|
95
|
-
suggestion = rule.get("suggestion", "")
|
|
96
|
-
level_config = rule.get("defaultConfiguration", {})
|
|
97
|
-
level = level_config.get("level", "NONE") if level_config else "NONE"
|
|
98
|
-
level_cn = LEVEL_MAP.get(level, "低危")
|
|
99
|
-
vul_hash = result.get("properties", {}).get("hash", "")
|
|
100
|
-
|
|
101
|
-
# 位置信息
|
|
102
|
-
locations = result.get("locations", [])
|
|
103
|
-
file_uri = ""
|
|
104
|
-
start_line = 0
|
|
105
|
-
end_line = 0
|
|
106
|
-
if locations:
|
|
107
|
-
phys = locations[0].get("physicalLocation", {})
|
|
108
|
-
artifact = phys.get("artifactLocation", {})
|
|
109
|
-
file_uri = artifact.get("uri", "")
|
|
110
|
-
region = artifact.get("region", {})
|
|
111
|
-
start_line = region.get("startLine", 0)
|
|
112
|
-
end_line = region.get("endLine", start_line)
|
|
113
|
-
|
|
114
|
-
# 数据流信息
|
|
115
|
-
code_flows_raw = result.get("codeFlows", [])
|
|
116
|
-
code_flows = []
|
|
117
|
-
if code_flows_raw:
|
|
118
|
-
thread_flows = code_flows_raw[0].get("threadFlows", [])
|
|
119
|
-
if thread_flows:
|
|
120
|
-
for loc_wrapper in thread_flows[0].get("locations", []):
|
|
121
|
-
loc = loc_wrapper.get("location", {})
|
|
122
|
-
loc_phys = loc.get("physicalLocation", {})
|
|
123
|
-
loc_file = loc_phys.get("artifactLocation", {}).get("uri", "")
|
|
124
|
-
loc_region = loc_phys.get("region", {})
|
|
125
|
-
loc_line = loc_region.get("startLine", 0)
|
|
126
|
-
loc_msg = loc.get("message", {}).get("text", "")
|
|
127
|
-
code_flows.append({
|
|
128
|
-
"file": loc_file,
|
|
129
|
-
"line": loc_line,
|
|
130
|
-
"message": loc_msg,
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
is_sensitive = "sensitive" in rule_id.lower()
|
|
134
|
-
|
|
135
|
-
vul_entry = {
|
|
136
|
-
"ruleID": rule_id,
|
|
137
|
-
"name": name,
|
|
138
|
-
"description": description,
|
|
139
|
-
"suggestion": suggestion,
|
|
140
|
-
"level": level,
|
|
141
|
-
"level_cn": level_cn,
|
|
142
|
-
"file": file_uri,
|
|
143
|
-
"startLine": start_line,
|
|
144
|
-
"endLine": end_line,
|
|
145
|
-
"hash": vul_hash,
|
|
146
|
-
"is_sensitive": is_sensitive,
|
|
147
|
-
"codeFlows": code_flows,
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if is_sensitive:
|
|
151
|
-
sensitive_vuls.append(vul_entry)
|
|
152
|
-
else:
|
|
153
|
-
common_vuls.append(vul_entry)
|
|
154
|
-
|
|
155
|
-
# 按严重程度排序
|
|
156
|
-
def sort_key(v):
|
|
157
|
-
return (LEVEL_PRIORITY.get(v["level"], 99), v["file"], v["startLine"])
|
|
158
|
-
|
|
159
|
-
common_vuls.sort(key=sort_key)
|
|
160
|
-
sensitive_vuls.sort(key=sort_key)
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
"total": len(common_vuls) + len(sensitive_vuls),
|
|
164
|
-
"common_count": len(common_vuls),
|
|
165
|
-
"sensitive_count": len(sensitive_vuls),
|
|
166
|
-
"bundle_hash": bundle_hash,
|
|
167
|
-
"common_vuls": common_vuls,
|
|
168
|
-
"sensitive_vuls": sensitive_vuls,
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def _file_basename(file_path):
|
|
173
|
-
# type: (str) -> str
|
|
174
|
-
return os.path.basename(file_path) if file_path else ""
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def format_vul_report(vuls, title="漏洞报告"):
|
|
178
|
-
# type: (list, str) -> str
|
|
179
|
-
"""将漏洞列表按类型分组格式化为 Markdown 报告。
|
|
180
|
-
|
|
181
|
-
同一 ruleID 的漏洞归为一组,描述/等级/修复建议只展示一次,
|
|
182
|
-
漏洞位置列表化展示,数据流用 <details> 折叠。
|
|
183
|
-
"""
|
|
184
|
-
if not vuls:
|
|
185
|
-
return ""
|
|
186
|
-
|
|
187
|
-
# 按 ruleID 分组,保持原有排序
|
|
188
|
-
groups = OrderedDict() # type: OrderedDict[str, list]
|
|
189
|
-
for vul in vuls:
|
|
190
|
-
rule_id = vul["ruleID"]
|
|
191
|
-
if rule_id not in groups:
|
|
192
|
-
groups[rule_id] = []
|
|
193
|
-
groups[rule_id].append(vul)
|
|
194
|
-
|
|
195
|
-
lines = ["**{}**\n".format(title)]
|
|
196
|
-
|
|
197
|
-
for group_idx, (rule_id, group_vuls) in enumerate(groups.items(), 1):
|
|
198
|
-
rep = group_vuls[0] # 取第一个作为代表获取公共信息
|
|
199
|
-
count = len(group_vuls)
|
|
200
|
-
count_suffix = "({} 处)".format(count) if count > 1 else ""
|
|
201
|
-
|
|
202
|
-
lines.append("{}. **{}**{}".format(group_idx, rep["name"], count_suffix))
|
|
203
|
-
if rep["description"]:
|
|
204
|
-
lines.append("- **漏洞描述**:{}".format(rep["description"]))
|
|
205
|
-
lines.append("- **漏洞等级**:{}".format(rep["level_cn"]))
|
|
206
|
-
if rep["suggestion"]:
|
|
207
|
-
lines.append("- **修复建议**:{}".format(rep["suggestion"]))
|
|
208
|
-
|
|
209
|
-
# 漏洞位置列表
|
|
210
|
-
lines.append("- **漏洞位置**:")
|
|
211
|
-
for vul in group_vuls:
|
|
212
|
-
fname = _file_basename(vul["file"])
|
|
213
|
-
loc_label = "{}:{}".format(fname, vul["startLine"]) if fname else "未知位置"
|
|
214
|
-
loc_link = "[{}]({}#L{})".format(loc_label, vul["file"], vul["startLine"]) if vul["file"] else loc_label
|
|
215
|
-
lines.append(" - {}".format(loc_link))
|
|
216
|
-
|
|
217
|
-
# 数据流折叠展示
|
|
218
|
-
if vul["codeFlows"]:
|
|
219
|
-
lines.append(" <details><summary>数据流</summary>\n")
|
|
220
|
-
for hop_idx, hop in enumerate(vul["codeFlows"], 1):
|
|
221
|
-
hop_fname = _file_basename(hop["file"])
|
|
222
|
-
hop_label = "{}:{}".format(hop_fname, hop["line"]) if hop_fname else "未知"
|
|
223
|
-
hop_link = "[{}]({}#L{})".format(hop_label, hop["file"], hop["line"]) if hop["file"] else hop_label
|
|
224
|
-
hop_msg = hop.get("message", "")
|
|
225
|
-
if hop_msg:
|
|
226
|
-
lines.append(" {}. {} — {}".format(hop_idx, hop_link, hop_msg))
|
|
227
|
-
else:
|
|
228
|
-
lines.append(" {}. {}".format(hop_idx, hop_link))
|
|
229
|
-
lines.append("\n </details>")
|
|
230
|
-
lines.append("")
|
|
231
|
-
|
|
232
|
-
return "\n".join(lines)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def format_full_report(parsed):
|
|
236
|
-
# type: (dict) -> str
|
|
237
|
-
"""生成完整的展示报告(包含两类漏洞)。"""
|
|
238
|
-
parts = []
|
|
239
|
-
|
|
240
|
-
if parsed["common_count"] > 0 and parsed["sensitive_count"] > 0:
|
|
241
|
-
parts.append("扫描发现 **{}** 个普通漏洞和 **{}** 个硬编码漏洞。\n".format(
|
|
242
|
-
parsed["common_count"], parsed["sensitive_count"]))
|
|
243
|
-
elif parsed["common_count"] > 0:
|
|
244
|
-
parts.append("扫描发现 **{}** 个普通漏洞。\n".format(parsed["common_count"]))
|
|
245
|
-
elif parsed["sensitive_count"] > 0:
|
|
246
|
-
parts.append("扫描发现 **{}** 个硬编码漏洞。\n".format(parsed["sensitive_count"]))
|
|
247
|
-
else:
|
|
248
|
-
parts.append("扫描完成,未发现漏洞。\n")
|
|
249
|
-
return "\n".join(parts)
|
|
250
|
-
|
|
251
|
-
if parsed["common_vuls"]:
|
|
252
|
-
parts.append(format_vul_report(parsed["common_vuls"], "普通漏洞报告"))
|
|
253
|
-
|
|
254
|
-
if parsed["sensitive_vuls"]:
|
|
255
|
-
parts.append(format_vul_report(parsed["sensitive_vuls"], "硬编码漏洞报告"))
|
|
256
|
-
|
|
257
|
-
return "\n".join(parts)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def main():
|
|
261
|
-
"""
|
|
262
|
-
主函数入口,解析命令行参数并处理扫描结果
|
|
263
|
-
|
|
264
|
-
Args:
|
|
265
|
-
无(通过命令行参数 --scan-result 和 --output-dir 传入)
|
|
266
|
-
|
|
267
|
-
Returns:
|
|
268
|
-
无(结果直接输出到标准输出和文件)
|
|
269
|
-
"""
|
|
270
|
-
parser = argparse.ArgumentParser(description="扫描结果解析与展示工具")
|
|
271
|
-
parser.add_argument("--scan-result", required=True, help="扫描结果文件路径 (scan_result.json)")
|
|
272
|
-
parser.add_argument("--output-dir", default=None, help="结构化结果输出目录,默认与输入文件同目录")
|
|
273
|
-
args = parser.parse_args()
|
|
274
|
-
|
|
275
|
-
# 读取扫描结果
|
|
276
|
-
try:
|
|
277
|
-
with open(args.scan_result, "r", encoding="utf-8") as f:
|
|
278
|
-
scan_result = json.load(f)
|
|
279
|
-
except Exception as e:
|
|
280
|
-
print("错误: 读取扫描结果失败 {}: {}".format(args.scan_result, e), file=sys.stderr)
|
|
281
|
-
sys.exit(1)
|
|
282
|
-
|
|
283
|
-
# 解析
|
|
284
|
-
parsed = parse_scan_result(scan_result)
|
|
285
|
-
|
|
286
|
-
# 输出 Markdown 报告到标准输出
|
|
287
|
-
report = format_full_report(parsed)
|
|
288
|
-
print(report)
|
|
289
|
-
|
|
290
|
-
# 保存结构化 JSON
|
|
291
|
-
# 默认输出到输入文件同目录,自然跟随项目隔离
|
|
292
|
-
output_dir = args.output_dir or os.path.dirname(os.path.abspath(args.scan_result))
|
|
293
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
294
|
-
output_file = os.path.join(output_dir, "parsed_result.json")
|
|
295
|
-
with open(output_file, "w", encoding="utf-8") as f:
|
|
296
|
-
json.dump(parsed, f, ensure_ascii=False, indent=2)
|
|
297
|
-
print(output_file, file=sys.stderr)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if __name__ == "__main__":
|
|
301
|
-
main()
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
漏洞修复工具 - 根据扫描结果修复指定文件的漏洞。
|
|
4
|
-
|
|
5
|
-
用法:
|
|
6
|
-
python3 repair_vulnerability.py --root-path <项目目录> --username <用户名> --parsed-result <parsed_result.json路径>
|
|
7
|
-
python3 repair_vulnerability.py --root-path <项目目录> --username <用户名> --vulnerability-info '<漏洞信息JSON>'
|
|
8
|
-
|
|
9
|
-
支持两种输入方式(二选一):
|
|
10
|
-
--parsed-result: 直接传入 parsed_result.json 路径,脚本自动提取普通漏洞并构建修复信息
|
|
11
|
-
--vulnerability-info: 传入漏洞信息 JSON 字符串
|
|
12
|
-
|
|
13
|
-
流程:
|
|
14
|
-
1. 解析漏洞信息,计算待修复文件的哈希
|
|
15
|
-
2. 调用修复接口
|
|
16
|
-
3. 如有缺失文件则上传后重试
|
|
17
|
-
4. 返回包含 diff_content 的修复结果
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
import argparse
|
|
21
|
-
import base64
|
|
22
|
-
import hashlib
|
|
23
|
-
import json
|
|
24
|
-
import logging
|
|
25
|
-
import os
|
|
26
|
-
import sys
|
|
27
|
-
import time
|
|
28
|
-
import uuid
|
|
29
|
-
from typing import Dict, List, Tuple
|
|
30
|
-
|
|
31
|
-
import http_client # noqa: F401 (triggers shared logging config)
|
|
32
|
-
|
|
33
|
-
HOST = "https://comate-sec.baidu-int.com"
|
|
34
|
-
|
|
35
|
-
USERNAME = ""
|
|
36
|
-
USER_ID = ""
|
|
37
|
-
|
|
38
|
-
logger = logging.getLogger("repair")
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def build_headers():
|
|
42
|
-
# type: () -> Dict[str, str]
|
|
43
|
-
return {
|
|
44
|
-
"Comate-Username": USERNAME,
|
|
45
|
-
"Comate-User-Id": USER_ID,
|
|
46
|
-
"SAST-Request-ID": str(uuid.uuid4()),
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def calc_sha256(file_path):
|
|
51
|
-
# type: (str) -> str
|
|
52
|
-
sha256_hash = hashlib.sha256()
|
|
53
|
-
with open(file_path, "rb") as f:
|
|
54
|
-
for chunk in iter(lambda: f.read(4096), b""):
|
|
55
|
-
sha256_hash.update(chunk)
|
|
56
|
-
return sha256_hash.hexdigest()
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def read_file_content(file_path):
|
|
60
|
-
# type: (str) -> str
|
|
61
|
-
"""读取文件内容,二进制文件用 base64 编码。"""
|
|
62
|
-
try:
|
|
63
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
64
|
-
return f.read()
|
|
65
|
-
except UnicodeDecodeError:
|
|
66
|
-
with open(file_path, "rb") as f:
|
|
67
|
-
return base64.b64encode(f.read()).decode("ascii")
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def diff_file_content(file_path, new_content):
|
|
71
|
-
# type: (str, str) -> str
|
|
72
|
-
"""比较原文件与修复后内容,返回 {from_content, to_content} JSON。"""
|
|
73
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
74
|
-
old_content = f.read()
|
|
75
|
-
|
|
76
|
-
old_lines = old_content.split("\n")
|
|
77
|
-
new_lines = new_content.split("\n")
|
|
78
|
-
|
|
79
|
-
from_parts = [] # type: List[str]
|
|
80
|
-
to_parts = [] # type: List[str]
|
|
81
|
-
|
|
82
|
-
oi, ni = 0, 0
|
|
83
|
-
while oi < len(old_lines) and ni < len(new_lines):
|
|
84
|
-
if old_lines[oi] == new_lines[ni]:
|
|
85
|
-
oi += 1
|
|
86
|
-
ni += 1
|
|
87
|
-
else:
|
|
88
|
-
from_parts.append(old_lines[oi] + "\n")
|
|
89
|
-
to_parts.append(new_lines[ni] + "\n")
|
|
90
|
-
oi += 1
|
|
91
|
-
ni += 1
|
|
92
|
-
|
|
93
|
-
while oi < len(old_lines):
|
|
94
|
-
from_parts.append(old_lines[oi] + "\n")
|
|
95
|
-
oi += 1
|
|
96
|
-
|
|
97
|
-
while ni < len(new_lines):
|
|
98
|
-
to_parts.append(new_lines[ni] + "\n")
|
|
99
|
-
ni += 1
|
|
100
|
-
|
|
101
|
-
return json.dumps(
|
|
102
|
-
{"from_content": "".join(from_parts), "to_content": "".join(to_parts)},
|
|
103
|
-
ensure_ascii=False,
|
|
104
|
-
indent=2,
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def upload_files_for_repair(root_path, missing_files):
|
|
109
|
-
# type: (str, List[str]) -> None
|
|
110
|
-
"""上传修复过程中缺失的文件。"""
|
|
111
|
-
payload = {"files": {}}
|
|
112
|
-
for name in missing_files:
|
|
113
|
-
file_path = os.path.join(root_path, name)
|
|
114
|
-
try:
|
|
115
|
-
content = read_file_content(file_path)
|
|
116
|
-
file_hash = calc_sha256(file_path)
|
|
117
|
-
payload["files"][name] = {"hash": file_hash, "content": content}
|
|
118
|
-
except Exception as e:
|
|
119
|
-
print("警告: 读取文件失败 {}: {}".format(file_path, e), file=sys.stderr)
|
|
120
|
-
|
|
121
|
-
http_client.put("{}/api/v1/upload".format(HOST), headers=build_headers(), json_body=payload)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def repair_vulnerability(root_path, vulnerability_info):
|
|
125
|
-
# type: (str, Dict) -> Dict
|
|
126
|
-
"""执行漏洞修复,返回包含 diff 的修复结果。"""
|
|
127
|
-
# 计算待修复文件的哈希
|
|
128
|
-
for file_info in vulnerability_info.get("files", []):
|
|
129
|
-
file_path = os.path.join(root_path, file_info["name"])
|
|
130
|
-
if os.path.isfile(file_path):
|
|
131
|
-
file_info["hash"] = calc_sha256(file_path)
|
|
132
|
-
|
|
133
|
-
repair_type = vulnerability_info.get("type", 2)
|
|
134
|
-
file_count = len(vulnerability_info.get("files", []))
|
|
135
|
-
logger.info("repair start: type=%d, files=%d", repair_type, file_count)
|
|
136
|
-
api_url = "{}/api/v2/repair_file".format(HOST)
|
|
137
|
-
print("开始修复...", file=sys.stderr)
|
|
138
|
-
|
|
139
|
-
while True:
|
|
140
|
-
result = http_client.post(
|
|
141
|
-
api_url,
|
|
142
|
-
headers=build_headers(),
|
|
143
|
-
json_body=vulnerability_info,
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
# 如有缺失文件,先上传
|
|
147
|
-
missing = result.get("data", {}).get("missingFiles", [])
|
|
148
|
-
if missing:
|
|
149
|
-
print("上传缺失文件: {} 个".format(len(missing)), file=sys.stderr)
|
|
150
|
-
upload_files_for_repair(root_path, missing)
|
|
151
|
-
time.sleep(3)
|
|
152
|
-
continue
|
|
153
|
-
|
|
154
|
-
# status != 0 表示完成
|
|
155
|
-
if result.get("status") != 0:
|
|
156
|
-
# 生成 diff
|
|
157
|
-
files_data = result.get("data", {}).get("files", [])
|
|
158
|
-
for file_data in files_data:
|
|
159
|
-
repaired_content = file_data.get("repairedContent", "")
|
|
160
|
-
if repaired_content:
|
|
161
|
-
file_path = os.path.join(root_path, file_data["name"])
|
|
162
|
-
if os.path.isfile(file_path):
|
|
163
|
-
diff_json = diff_file_content(file_path, repaired_content)
|
|
164
|
-
file_data["diff_content"] = diff_json
|
|
165
|
-
# 置空防止泄露完整源码
|
|
166
|
-
file_data["repairedContent"] = ""
|
|
167
|
-
return result
|
|
168
|
-
|
|
169
|
-
print("修复中,等待结果...", file=sys.stderr)
|
|
170
|
-
time.sleep(3)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def build_vulnerability_info(parsed):
|
|
174
|
-
# type: (dict) -> dict
|
|
175
|
-
"""从 parsed_result.json 构建修复接口所需的 vulnerability-info。"""
|
|
176
|
-
bundle_hash = parsed.get("bundle_hash", "")
|
|
177
|
-
common_vuls = parsed.get("common_vuls", [])
|
|
178
|
-
|
|
179
|
-
file_map = {} # type: dict
|
|
180
|
-
for vul in common_vuls:
|
|
181
|
-
fname = vul.get("file", "")
|
|
182
|
-
if not fname:
|
|
183
|
-
continue
|
|
184
|
-
if fname not in file_map:
|
|
185
|
-
file_map[fname] = {"name": fname, "vulList": []}
|
|
186
|
-
file_map[fname]["vulList"].append({
|
|
187
|
-
"ruleID": vul.get("ruleID", ""),
|
|
188
|
-
"line": vul.get("startLine", 0),
|
|
189
|
-
"hash": vul.get("hash", ""),
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
"files": list(file_map.values()),
|
|
194
|
-
"type": 2,
|
|
195
|
-
"extra": {
|
|
196
|
-
"bundleHash": bundle_hash,
|
|
197
|
-
},
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def main():
|
|
202
|
-
global USERNAME, USER_ID
|
|
203
|
-
parser = argparse.ArgumentParser(description="代码安全漏洞修复工具")
|
|
204
|
-
parser.add_argument("--root-path", required=True, help="项目根目录")
|
|
205
|
-
parser.add_argument("--username", required=True, help="Comate 用户名")
|
|
206
|
-
parser.add_argument("--vulnerability-info", default=None, help="漏洞信息 JSON 字符串")
|
|
207
|
-
parser.add_argument("--parsed-result", default=None,
|
|
208
|
-
help="解析结果文件路径 (parsed_result.json),与 --vulnerability-info 二选一")
|
|
209
|
-
parser.add_argument("--output-dir", default=None, help="结果输出目录,默认为 skill 临时目录")
|
|
210
|
-
args = parser.parse_args()
|
|
211
|
-
|
|
212
|
-
if not args.vulnerability_info and not args.parsed_result:
|
|
213
|
-
print("错误: 必须提供 --vulnerability-info 或 --parsed-result 之一", file=sys.stderr)
|
|
214
|
-
sys.exit(1)
|
|
215
|
-
|
|
216
|
-
USERNAME = args.username
|
|
217
|
-
USER_ID = hashlib.md5(USERNAME.encode("utf-8")).hexdigest()[:12]
|
|
218
|
-
|
|
219
|
-
logger.info("repair_vulnerability start: username=%s, root_path=%s", USERNAME, args.root_path)
|
|
220
|
-
|
|
221
|
-
root_path = os.path.realpath(args.root_path)
|
|
222
|
-
if not os.path.isdir(root_path):
|
|
223
|
-
print("错误: 目录不存在 {}".format(root_path), file=sys.stderr)
|
|
224
|
-
sys.exit(1)
|
|
225
|
-
|
|
226
|
-
# 默认输出到 skill 临时目录,按项目路径隔离子目录避免并发冲突
|
|
227
|
-
skill_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
228
|
-
project_name = os.path.basename(root_path)
|
|
229
|
-
path_hash = hashlib.md5(root_path.encode("utf-8")).hexdigest()[:8]
|
|
230
|
-
default_output = os.path.join(skill_dir, ".tmp", "{}_{}".format(project_name, path_hash))
|
|
231
|
-
output_dir = os.path.realpath(args.output_dir) if args.output_dir else default_output
|
|
232
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
233
|
-
|
|
234
|
-
if args.parsed_result:
|
|
235
|
-
try:
|
|
236
|
-
with open(args.parsed_result, "r", encoding="utf-8") as f:
|
|
237
|
-
parsed = json.load(f)
|
|
238
|
-
except Exception as e:
|
|
239
|
-
print("错误: 读取解析结果失败 {}: {}".format(args.parsed_result, e), file=sys.stderr)
|
|
240
|
-
sys.exit(1)
|
|
241
|
-
if not parsed.get("common_vuls"):
|
|
242
|
-
print("无普通漏洞需要修复", file=sys.stderr)
|
|
243
|
-
sys.exit(0)
|
|
244
|
-
vulnerability_info = build_vulnerability_info(parsed)
|
|
245
|
-
else:
|
|
246
|
-
try:
|
|
247
|
-
vulnerability_info = json.loads(args.vulnerability_info)
|
|
248
|
-
except json.JSONDecodeError as e:
|
|
249
|
-
print("错误: 漏洞信息 JSON 解析失败: {}".format(e), file=sys.stderr)
|
|
250
|
-
sys.exit(1)
|
|
251
|
-
|
|
252
|
-
result = repair_vulnerability(root_path, vulnerability_info)
|
|
253
|
-
|
|
254
|
-
output_file = os.path.join(output_dir, "repair_result.json")
|
|
255
|
-
with open(output_file, "w", encoding="utf-8") as f:
|
|
256
|
-
json.dump(result, f, ensure_ascii=False, indent=2)
|
|
257
|
-
print(output_file)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if __name__ == "__main__":
|
|
261
|
-
main()
|