aike-code-sentinel 0.1.0__py3-none-any.whl
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.
- aike_code_sentinel-0.1.0.dist-info/METADATA +6 -0
- aike_code_sentinel-0.1.0.dist-info/RECORD +0 -0
- aike_code_sentinel-0.1.0.dist-info/WHEEL +4 -0
- code_sentinel/__init__.py +30 -0
- code_sentinel/__pycache__/__init__.cpython-311.pyc +0 -0
- code_sentinel/__pycache__/cli.cpython-311.pyc +0 -0
- code_sentinel/__pycache__/fixer.cpython-311.pyc +0 -0
- code_sentinel/__pycache__/reporter.cpython-311.pyc +0 -0
- code_sentinel/__pycache__/scanner.cpython-311.pyc +0 -0
- code_sentinel/cli.py +382 -0
- code_sentinel/fixer.py +329 -0
- code_sentinel/reporter.py +438 -0
- code_sentinel/scanner.py +817 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
"""
|
|
2
|
+
报告生成器 — Code Sentinel 报告模块
|
|
3
|
+
====================================
|
|
4
|
+
生成 HTML 和 Markdown 格式的安全报告。
|
|
5
|
+
|
|
6
|
+
集成 Genome Core 三省六部审计日志 (audit_log) 和问责机制。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
# ── Genome Core 集成 ──
|
|
16
|
+
try:
|
|
17
|
+
from genome_core.governance import audit_log, check_accountability, route_to_ministry
|
|
18
|
+
from genome_core.security import calc_confidence
|
|
19
|
+
HAS_GENOME_CORE = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
HAS_GENOME_CORE = False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ── Markdown 报告模板 ──
|
|
25
|
+
|
|
26
|
+
MARKDOWN_TEMPLATE = """# Code Sentinel 扫描报告
|
|
27
|
+
|
|
28
|
+
**生成时间**: {timestamp}
|
|
29
|
+
**扫描目标**: {target}
|
|
30
|
+
**Genome Core 集成**: {genome_core_status}
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 摘要
|
|
35
|
+
|
|
36
|
+
| 指标 | 数值 |
|
|
37
|
+
|------|------|
|
|
38
|
+
| 总问题数 | {total} |
|
|
39
|
+
| 高危 (HIGH) | {high} |
|
|
40
|
+
| 中危 (MEDIUM) | {medium} |
|
|
41
|
+
| 低危 (LOW) | {low} |
|
|
42
|
+
| 扫描文件数 | {scanned_files} |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 安全风险 ({security_count})
|
|
47
|
+
|
|
48
|
+
{security_table}
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 性能问题 ({performance_count})
|
|
53
|
+
|
|
54
|
+
{performance_table}
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 代码异味 ({smell_count})
|
|
59
|
+
|
|
60
|
+
{smell_table}
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 依赖风险 ({dependency_count})
|
|
65
|
+
|
|
66
|
+
{dependency_table}
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 修复建议
|
|
71
|
+
|
|
72
|
+
{fix_suggestions}
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
*报告由 Code Sentinel (genome-core 集成) 自动生成*
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ── HTML 报告模板 ──
|
|
81
|
+
|
|
82
|
+
HTML_TEMPLATE = """<!DOCTYPE html>
|
|
83
|
+
<html lang="zh-CN">
|
|
84
|
+
<head>
|
|
85
|
+
<meta charset="UTF-8">
|
|
86
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
87
|
+
<title>Code Sentinel 安全报告</title>
|
|
88
|
+
<style>
|
|
89
|
+
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
|
90
|
+
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
91
|
+
background: #f5f7fa; color: #1a1a2e; line-height: 1.6; padding: 20px; }}
|
|
92
|
+
.container {{ max-width: 1200px; margin: 0 auto; }}
|
|
93
|
+
header {{ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
94
|
+
color: white; padding: 30px; border-radius: 12px; margin-bottom: 24px; }}
|
|
95
|
+
header h1 {{ font-size: 28px; margin-bottom: 8px; }}
|
|
96
|
+
header p {{ opacity: 0.8; font-size: 14px; }}
|
|
97
|
+
.summary {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
98
|
+
gap: 16px; margin-bottom: 24px; }}
|
|
99
|
+
.summary-card {{ background: white; padding: 20px; border-radius: 10px;
|
|
100
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.08); text-align: center; }}
|
|
101
|
+
.summary-card .num {{ font-size: 36px; font-weight: 700; }}
|
|
102
|
+
.summary-card .label {{ font-size: 13px; color: #666; margin-top: 4px; }}
|
|
103
|
+
.card-high {{ border-top: 4px solid #e74c3c; }} .card-high .num {{ color: #e74c3c; }}
|
|
104
|
+
.card-medium {{ border-top: 4px solid #f39c12; }} .card-medium .num {{ color: #f39c12; }}
|
|
105
|
+
.card-low {{ border-top: 4px solid #3498db; }} .card-low .num {{ color: #3498db; }}
|
|
106
|
+
.card-total {{ border-top: 4px solid #2ecc71; }} .card-total .num {{ color: #2ecc71; }}
|
|
107
|
+
section {{ background: white; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
|
108
|
+
margin-bottom: 20px; overflow: hidden; }}
|
|
109
|
+
section h2 {{ padding: 16px 20px; font-size: 18px; border-bottom: 1px solid #eee; }}
|
|
110
|
+
.issue-item {{ padding: 14px 20px; border-bottom: 1px solid #f0f0f0; }}
|
|
111
|
+
.issue-item:last-child {{ border-bottom: none; }}
|
|
112
|
+
.issue-item .file {{ font-family: 'SF Mono', Consolas, monospace; font-size: 13px;
|
|
113
|
+
color: #e74c3c; }}
|
|
114
|
+
.issue-item .desc {{ margin-top: 4px; font-size: 14px; }}
|
|
115
|
+
.issue-item .meta {{ margin-top: 6px; font-size: 12px; color: #999; }}
|
|
116
|
+
.badge {{ display: inline-block; padding: 2px 8px; border-radius: 4px;
|
|
117
|
+
font-size: 11px; font-weight: 600; text-transform: uppercase; }}
|
|
118
|
+
.badge-high {{ background: #fde8e8; color: #e74c3c; }}
|
|
119
|
+
.badge-medium {{ background: #fef3e2; color: #f39c12; }}
|
|
120
|
+
.badge-low {{ background: #e8f4fd; color: #3498db; }}
|
|
121
|
+
code {{ background: #f5f5f5; padding: 1px 5px; border-radius: 3px;
|
|
122
|
+
font-family: 'SF Mono', Consolas, monospace; font-size: 13px; }}
|
|
123
|
+
pre {{ background: #1a1a2e; color: #f8f8f2; padding: 14px 18px; border-radius: 8px;
|
|
124
|
+
overflow-x: auto; font-size: 13px; margin: 8px 20px 14px; }}
|
|
125
|
+
.footer {{ text-align: center; color: #999; font-size: 13px; margin-top: 30px; }}
|
|
126
|
+
.genome-badge {{ display: inline-block; padding: 4px 12px; border-radius: 20px;
|
|
127
|
+
font-size: 12px; font-weight: 600; margin-left: 10px; }}
|
|
128
|
+
.genome-on {{ background: #d4edda; color: #155724; }}
|
|
129
|
+
.genome-off {{ background: #f8d7da; color: #721c24; }}
|
|
130
|
+
</style>
|
|
131
|
+
</head>
|
|
132
|
+
<body>
|
|
133
|
+
<div class="container">
|
|
134
|
+
<header>
|
|
135
|
+
<h1>🔍 Code Sentinel 安全报告</h1>
|
|
136
|
+
<p>生成时间: {timestamp} | 扫描目标: <code>{target}</code>
|
|
137
|
+
<span class="genome-badge genome-{genome_class}">Genome Core: {genome_core_status}</span></p>
|
|
138
|
+
</header>
|
|
139
|
+
|
|
140
|
+
<div class="summary">
|
|
141
|
+
<div class="summary-card card-total">
|
|
142
|
+
<div class="num">{total}</div>
|
|
143
|
+
<div class="label">总问题数</div>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="summary-card card-high">
|
|
146
|
+
<div class="num">{high}</div>
|
|
147
|
+
<div class="label">高危</div>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="summary-card card-medium">
|
|
150
|
+
<div class="num">{medium}</div>
|
|
151
|
+
<div class="label">中危</div>
|
|
152
|
+
</div>
|
|
153
|
+
<div class="summary-card card-low">
|
|
154
|
+
<div class="num">{low}</div>
|
|
155
|
+
<div class="label">低危</div>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="summary-card">
|
|
158
|
+
<div class="num">{scanned_files}</div>
|
|
159
|
+
<div class="label">扫描文件</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
{sections}
|
|
164
|
+
|
|
165
|
+
<div class="footer">
|
|
166
|
+
<p>报告由 Code Sentinel (genome-core 集成) 自动生成 — {timestamp}</p>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</body>
|
|
170
|
+
</html>"""
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class ReportGenerator:
|
|
174
|
+
"""Code Sentinel 报告生成器
|
|
175
|
+
|
|
176
|
+
支持 HTML 和 Markdown 两种格式,
|
|
177
|
+
集成 Genome Core 审计日志和问责机制。
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
def __init__(self, scan_results: dict, output_dir: str = "."):
|
|
181
|
+
self.results = scan_results
|
|
182
|
+
self.output_dir = Path(output_dir)
|
|
183
|
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
184
|
+
self.summary = scan_results.get("summary", {})
|
|
185
|
+
self.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
186
|
+
|
|
187
|
+
def generate_markdown(self, filename: str = "code_sentinel_report.md") -> str:
|
|
188
|
+
"""生成 Markdown 格式报告"""
|
|
189
|
+
# 安全表格
|
|
190
|
+
security_table = self._issues_to_md_table("security")
|
|
191
|
+
performance_table = self._issues_to_md_table("performance")
|
|
192
|
+
smell_table = self._smell_to_md_table()
|
|
193
|
+
dependency_table = self._dependency_to_md_table()
|
|
194
|
+
|
|
195
|
+
# 修复建议
|
|
196
|
+
fix_suggestions = self._generate_fix_suggestions()
|
|
197
|
+
|
|
198
|
+
genome_core_status = "✅ 已集成" if self.results.get("genome_core_integrated") else "❌ 未集成"
|
|
199
|
+
|
|
200
|
+
report = MARKDOWN_TEMPLATE.format(
|
|
201
|
+
timestamp=self.timestamp,
|
|
202
|
+
target=self.summary.get("target", "N/A"),
|
|
203
|
+
genome_core_status=genome_core_status,
|
|
204
|
+
total=self.summary.get("total", 0),
|
|
205
|
+
high=self.summary.get("high", 0),
|
|
206
|
+
medium=self.summary.get("medium", 0),
|
|
207
|
+
low=self.summary.get("low", 0),
|
|
208
|
+
scanned_files=self.summary.get("scanned_files", 0),
|
|
209
|
+
security_count=len(self.results.get("security", [])),
|
|
210
|
+
performance_count=len(self.results.get("performance", [])),
|
|
211
|
+
smell_count=len(self.results.get("smell", [])),
|
|
212
|
+
dependency_count=len(self.results.get("dependency", [])),
|
|
213
|
+
security_table=security_table or "无",
|
|
214
|
+
performance_table=performance_table or "无",
|
|
215
|
+
smell_table=smell_table or "无",
|
|
216
|
+
dependency_table=dependency_table or "无",
|
|
217
|
+
fix_suggestions=fix_suggestions,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
output_path = self.output_dir / filename
|
|
221
|
+
output_path.write_text(report, encoding="utf-8")
|
|
222
|
+
|
|
223
|
+
# 记录 Genome Core 审计日志
|
|
224
|
+
if HAS_GENOME_CORE:
|
|
225
|
+
audit_log("report", "户部", f"生成 Markdown 报告: {output_path}")
|
|
226
|
+
|
|
227
|
+
return str(output_path)
|
|
228
|
+
|
|
229
|
+
def generate_html(self, filename: str = "code_sentinel_report.html") -> str:
|
|
230
|
+
"""生成 HTML 格式报告"""
|
|
231
|
+
# 生成各分类的 HTML 区块
|
|
232
|
+
sections = []
|
|
233
|
+
|
|
234
|
+
for category, label, icon in [
|
|
235
|
+
("security", "安全风险", "🔒"),
|
|
236
|
+
("performance", "性能问题", "⚡"),
|
|
237
|
+
("smell", "代码异味", "👃"),
|
|
238
|
+
("dependency", "依赖风险", "📦"),
|
|
239
|
+
]:
|
|
240
|
+
issues = self.results.get(category, [])
|
|
241
|
+
if not issues:
|
|
242
|
+
sections.append(
|
|
243
|
+
f'<section><h2>{icon} {label} (0)</h2>'
|
|
244
|
+
f'<p style="padding:16px 20px;color:#999;">未发现 {label}。</p></section>'
|
|
245
|
+
)
|
|
246
|
+
continue
|
|
247
|
+
|
|
248
|
+
items_html = []
|
|
249
|
+
for iss in issues:
|
|
250
|
+
if category == "dependency":
|
|
251
|
+
file = iss.get("file", "")
|
|
252
|
+
line = iss.get("line", 0)
|
|
253
|
+
desc = iss.get("description", "")
|
|
254
|
+
pkg = iss.get("package", "")
|
|
255
|
+
ver = iss.get("version", "")
|
|
256
|
+
sev = iss.get("severity", "MEDIUM")
|
|
257
|
+
items_html.append(
|
|
258
|
+
f'<div class="issue-item">'
|
|
259
|
+
f'<span class="badge badge-{sev.lower()}">{sev}</span> '
|
|
260
|
+
f'<span class="file">{file}:{line}</span>'
|
|
261
|
+
f'<div class="desc">{desc}</div>'
|
|
262
|
+
f'<div class="meta">包: {pkg} | 版本: {ver}</div>'
|
|
263
|
+
f'</div>'
|
|
264
|
+
)
|
|
265
|
+
else:
|
|
266
|
+
file = iss.get("file", "")
|
|
267
|
+
line = iss.get("line", 0)
|
|
268
|
+
code = iss.get("code", "")
|
|
269
|
+
desc = iss.get("description", "")
|
|
270
|
+
sev = iss.get("severity", "LOW")
|
|
271
|
+
items_html.append(
|
|
272
|
+
f'<div class="issue-item">'
|
|
273
|
+
f'<span class="badge badge-{sev.lower()}">{sev}</span> '
|
|
274
|
+
f'<span class="file">{file}:{line}</span>'
|
|
275
|
+
f'<div class="desc">{desc}</div>'
|
|
276
|
+
f'<pre>{self._escape_html(code)}</pre>'
|
|
277
|
+
f'</div>'
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
sections.append(
|
|
281
|
+
f'<section><h2>{icon} {label} ({len(issues)})</h2>'
|
|
282
|
+
f'{"".join(items_html)}</section>'
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
genome_core_status = "已集成" if self.results.get("genome_core_integrated") else "未集成"
|
|
286
|
+
genome_class = "on" if self.results.get("genome_core_integrated") else "off"
|
|
287
|
+
|
|
288
|
+
html = HTML_TEMPLATE.format(
|
|
289
|
+
timestamp=self.timestamp,
|
|
290
|
+
target=self.summary.get("target", "N/A"),
|
|
291
|
+
genome_core_status=genome_core_status,
|
|
292
|
+
genome_class=genome_class,
|
|
293
|
+
total=self.summary.get("total", 0),
|
|
294
|
+
high=self.summary.get("high", 0),
|
|
295
|
+
medium=self.summary.get("medium", 0),
|
|
296
|
+
low=self.summary.get("low", 0),
|
|
297
|
+
scanned_files=self.summary.get("scanned_files", 0),
|
|
298
|
+
sections="\n".join(sections) if sections else "<p>无问题。</p>",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
output_path = self.output_dir / filename
|
|
302
|
+
output_path.write_text(html, encoding="utf-8")
|
|
303
|
+
|
|
304
|
+
if HAS_GENOME_CORE:
|
|
305
|
+
audit_log("report", "户部", f"生成 HTML 报告: {output_path}")
|
|
306
|
+
|
|
307
|
+
return str(output_path)
|
|
308
|
+
|
|
309
|
+
def generate_json(self, filename: str = "code_sentinel_report.json") -> str:
|
|
310
|
+
"""生成 JSON 格式报告"""
|
|
311
|
+
report = {
|
|
312
|
+
"generated_at": self.timestamp,
|
|
313
|
+
"target": self.summary.get("target", "N/A"),
|
|
314
|
+
"genome_core_integrated": self.results.get("genome_core_integrated", False),
|
|
315
|
+
**self.results,
|
|
316
|
+
}
|
|
317
|
+
output_path = self.output_dir / filename
|
|
318
|
+
output_path.write_text(
|
|
319
|
+
json.dumps(report, indent=2, ensure_ascii=False), encoding="utf-8"
|
|
320
|
+
)
|
|
321
|
+
return str(output_path)
|
|
322
|
+
|
|
323
|
+
# ── JSON Schema 格式报告 ──
|
|
324
|
+
|
|
325
|
+
def to_json_schema(self) -> dict:
|
|
326
|
+
"""返回符合 JSON Schema 格式的报告字典
|
|
327
|
+
|
|
328
|
+
包含版本声明、问题列表、统计摘要,与 generate_json() 输出结构不同避免冲突。
|
|
329
|
+
"""
|
|
330
|
+
issues = []
|
|
331
|
+
for category in ("security", "performance", "smell", "dependency"):
|
|
332
|
+
for iss in self.results.get(category, []):
|
|
333
|
+
item = {
|
|
334
|
+
"category": category,
|
|
335
|
+
"severity": iss.get("severity", "LOW"),
|
|
336
|
+
"file": iss.get("file", ""),
|
|
337
|
+
"line": iss.get("line", 0),
|
|
338
|
+
"description": iss.get("description", ""),
|
|
339
|
+
}
|
|
340
|
+
if category == "dependency":
|
|
341
|
+
item["package"] = iss.get("package", "")
|
|
342
|
+
item["version"] = iss.get("version", "")
|
|
343
|
+
else:
|
|
344
|
+
item["code"] = iss.get("code", "")
|
|
345
|
+
issues.append(item)
|
|
346
|
+
|
|
347
|
+
report = {
|
|
348
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
349
|
+
"version": "0.1.0",
|
|
350
|
+
"generated_at": self.timestamp,
|
|
351
|
+
"target": self.summary.get("target", "N/A"),
|
|
352
|
+
"genome_core_integrated": self.results.get("genome_core_integrated", False),
|
|
353
|
+
"summary": {
|
|
354
|
+
"total": self.summary.get("total", 0),
|
|
355
|
+
"high": self.summary.get("high", 0),
|
|
356
|
+
"medium": self.summary.get("medium", 0),
|
|
357
|
+
"low": self.summary.get("low", 0),
|
|
358
|
+
"scanned_files": self.summary.get("scanned_files", 0),
|
|
359
|
+
},
|
|
360
|
+
"issues": issues,
|
|
361
|
+
}
|
|
362
|
+
return report
|
|
363
|
+
|
|
364
|
+
def generate_json_schema(self, filename: str = "code_sentinel_report.schema.json") -> str:
|
|
365
|
+
"""生成 JSON Schema 格式报告文件"""
|
|
366
|
+
report = self.to_json_schema()
|
|
367
|
+
output_path = self.output_dir / filename
|
|
368
|
+
output_path.write_text(
|
|
369
|
+
json.dumps(report, indent=2, ensure_ascii=False), encoding="utf-8"
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
if HAS_GENOME_CORE:
|
|
373
|
+
audit_log("report", "户部", f"生成 JSON Schema 报告: {output_path}")
|
|
374
|
+
|
|
375
|
+
return str(output_path)
|
|
376
|
+
|
|
377
|
+
# ── 辅助方法 ──
|
|
378
|
+
|
|
379
|
+
@staticmethod
|
|
380
|
+
def _escape_html(text: str) -> str:
|
|
381
|
+
return text.replace("&", "&").replace("<", "<").replace(">", ">")
|
|
382
|
+
|
|
383
|
+
def _issues_to_md_table(self, category: str) -> str:
|
|
384
|
+
issues = self.results.get(category, [])
|
|
385
|
+
if not issues:
|
|
386
|
+
return "无"
|
|
387
|
+
|
|
388
|
+
rows = ["| 文件 | 行 | 严重度 | 描述 |", "|------|----|--------|------|"]
|
|
389
|
+
for iss in issues:
|
|
390
|
+
file = iss.get("file", "")
|
|
391
|
+
line = iss.get("line", 0)
|
|
392
|
+
sev = iss.get("severity", "")
|
|
393
|
+
desc = iss.get("description", "")
|
|
394
|
+
rows.append(f"| {file} | {line} | {sev} | {desc} |")
|
|
395
|
+
|
|
396
|
+
return "\n".join(rows)
|
|
397
|
+
|
|
398
|
+
def _smell_to_md_table(self) -> str:
|
|
399
|
+
return self._issues_to_md_table("smell")
|
|
400
|
+
|
|
401
|
+
def _dependency_to_md_table(self) -> str:
|
|
402
|
+
issues = self.results.get("dependency", [])
|
|
403
|
+
if not issues:
|
|
404
|
+
return "无"
|
|
405
|
+
|
|
406
|
+
rows = ["| 文件 | 行 | 包名 | 版本 | 描述 |", "|------|-----|------|------|------|"]
|
|
407
|
+
for iss in issues:
|
|
408
|
+
file = iss.get("file", "")
|
|
409
|
+
line = iss.get("line", 0)
|
|
410
|
+
pkg = iss.get("package", "")
|
|
411
|
+
ver = iss.get("version", "")
|
|
412
|
+
desc = iss.get("description", "")
|
|
413
|
+
rows.append(f"| {file} | {line} | {pkg} | {ver} | {desc} |")
|
|
414
|
+
|
|
415
|
+
return "\n".join(rows)
|
|
416
|
+
|
|
417
|
+
def _generate_fix_suggestions(self) -> str:
|
|
418
|
+
"""生成修复建议文本"""
|
|
419
|
+
suggestions = [
|
|
420
|
+
"### 安全修复",
|
|
421
|
+
"- 将 `eval()`/`exec()` 替换为 `ast.literal_eval()` 或 `json.loads()`",
|
|
422
|
+
"- 将 `os.system()` 替换为 `subprocess.run(..., check=True)`",
|
|
423
|
+
"- 对 subprocess 调用设置 `shell=False` 或严格过滤输入",
|
|
424
|
+
"",
|
|
425
|
+
"### 性能修复",
|
|
426
|
+
"- 使用 `itertools` 或手动展平嵌套循环",
|
|
427
|
+
"- 大文件使用 `for chunk in iter(lambda: f.read(8192), b''):` 分块读取",
|
|
428
|
+
"",
|
|
429
|
+
"### 代码异味修复",
|
|
430
|
+
"- 函数超过 50 行时考虑拆分为多个小函数",
|
|
431
|
+
"- 参数超过 5 个时使用 `*args`/`**kwargs` 或数据类",
|
|
432
|
+
"- 始终指定异常类型:`except Exception as e:`",
|
|
433
|
+
"",
|
|
434
|
+
"### 依赖修复",
|
|
435
|
+
"- 运行 `pip install --upgrade <package>` 升级有漏洞的依赖",
|
|
436
|
+
"- 在 requirements.txt 中锁定安全版本号",
|
|
437
|
+
]
|
|
438
|
+
return "\n".join(suggestions)
|