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
code_sentinel/fixer.py
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""
|
|
2
|
+
自动修复引擎 — Code Sentinel 修复模块
|
|
3
|
+
======================================
|
|
4
|
+
对 scanner.py 检测到的问题生成修复建议和 diff。
|
|
5
|
+
|
|
6
|
+
集成 Genome Core 四层自审 (full_review) 对修复代码进行验证。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import difflib
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import List, Optional
|
|
14
|
+
|
|
15
|
+
# ── Genome Core 集成 ──
|
|
16
|
+
try:
|
|
17
|
+
from genome_core.security import full_review, check_dangerous
|
|
18
|
+
HAS_GENOME_CORE = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
HAS_GENOME_CORE = False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class FixAction:
|
|
25
|
+
"""单个修复操作"""
|
|
26
|
+
file: str
|
|
27
|
+
line: int
|
|
28
|
+
description: str
|
|
29
|
+
original_code: str
|
|
30
|
+
fixed_code: str
|
|
31
|
+
category: str # security / performance / smell / dependency
|
|
32
|
+
confidence: float = 0.0
|
|
33
|
+
|
|
34
|
+
def to_diff(self) -> str:
|
|
35
|
+
"""生成统一 diff 格式"""
|
|
36
|
+
original_lines = self.original_code.split("\n")
|
|
37
|
+
fixed_lines = self.fixed_code.split("\n")
|
|
38
|
+
diff = difflib.unified_diff(
|
|
39
|
+
original_lines,
|
|
40
|
+
fixed_lines,
|
|
41
|
+
fromfile=f"a/{self.file}",
|
|
42
|
+
tofile=f"b/{self.file}",
|
|
43
|
+
lineterm="",
|
|
44
|
+
)
|
|
45
|
+
return "\n".join(diff)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class FixResult:
|
|
50
|
+
"""修复结果"""
|
|
51
|
+
file: str
|
|
52
|
+
actions: List[FixAction]
|
|
53
|
+
genome_review_passed: bool = False
|
|
54
|
+
genome_review_details: dict = field(default_factory=dict)
|
|
55
|
+
|
|
56
|
+
def to_dict(self) -> dict:
|
|
57
|
+
return {
|
|
58
|
+
"file": self.file,
|
|
59
|
+
"actions": [
|
|
60
|
+
{
|
|
61
|
+
"file": a.file,
|
|
62
|
+
"line": a.line,
|
|
63
|
+
"description": a.description,
|
|
64
|
+
"category": a.category,
|
|
65
|
+
"confidence": a.confidence,
|
|
66
|
+
"diff": a.to_diff(),
|
|
67
|
+
}
|
|
68
|
+
for a in self.actions
|
|
69
|
+
],
|
|
70
|
+
"genome_review_passed": self.genome_review_passed,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ── 修复模式库 ──
|
|
75
|
+
|
|
76
|
+
FIX_TEMPLATES = {
|
|
77
|
+
"eval": {
|
|
78
|
+
"description": "将 eval() 替换为安全替代方案(ast.literal_eval 或 json.loads)",
|
|
79
|
+
"fix": "ast.literal_eval({arg})",
|
|
80
|
+
"import_add": "import ast",
|
|
81
|
+
},
|
|
82
|
+
"exec": {
|
|
83
|
+
"description": "避免使用 exec(),改用函数封装或字典映射",
|
|
84
|
+
"fix": "# TODO: 重构 exec() 调用 — 改用函数封装或字典映射\n# 原代码: {original}",
|
|
85
|
+
"import_add": None,
|
|
86
|
+
},
|
|
87
|
+
"os.system": {
|
|
88
|
+
"description": "将 os.system() 替换为 subprocess.run(..., check=True) 并过滤输入",
|
|
89
|
+
"fix": "subprocess.run({args!r}, shell=True, check=True)",
|
|
90
|
+
"import_add": "import subprocess",
|
|
91
|
+
},
|
|
92
|
+
"bare_except": {
|
|
93
|
+
"description": "为裸 except 指定具体的异常类型",
|
|
94
|
+
"fix": "except Exception as e:",
|
|
95
|
+
},
|
|
96
|
+
"file_read": {
|
|
97
|
+
"description": "对大文件使用分块读取模式",
|
|
98
|
+
"fix": "for chunk in iter(lambda: {fp}.read(8192), b''):\n process(chunk)",
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class CodeFixer:
|
|
104
|
+
"""Code Sentinel 修复引擎
|
|
105
|
+
|
|
106
|
+
根据扫描结果自动生成修复建议和 diff,
|
|
107
|
+
并通过 Genome Core 的四层自审对修复代码进行验证。
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(self):
|
|
111
|
+
self._results: List[FixResult] = []
|
|
112
|
+
|
|
113
|
+
def generate_fixes(self, scan_results: dict) -> List[FixResult]:
|
|
114
|
+
"""根据扫描结果生成所有修复"""
|
|
115
|
+
self._results.clear()
|
|
116
|
+
|
|
117
|
+
for category_key in ("security", "performance", "smell"):
|
|
118
|
+
for issue in scan_results.get(category_key, []):
|
|
119
|
+
fix = self._generate_single_fix(category_key, issue)
|
|
120
|
+
if fix:
|
|
121
|
+
self._results.append(fix)
|
|
122
|
+
|
|
123
|
+
# 依赖问题:生成 requirements.txt 修复
|
|
124
|
+
for dep_issue in scan_results.get("dependency", []):
|
|
125
|
+
fix = self._fix_dependency(dep_issue)
|
|
126
|
+
if fix:
|
|
127
|
+
self._results.append(fix)
|
|
128
|
+
|
|
129
|
+
return self._results
|
|
130
|
+
|
|
131
|
+
def _generate_single_fix(self, category: str, issue: dict) -> Optional[FixResult]:
|
|
132
|
+
"""为单个问题生成修复"""
|
|
133
|
+
filepath = issue.get("file", "")
|
|
134
|
+
line = issue.get("line", 0)
|
|
135
|
+
code = issue.get("code", "")
|
|
136
|
+
description = issue.get("description", "")
|
|
137
|
+
|
|
138
|
+
# 安全检查修复
|
|
139
|
+
if category == "security":
|
|
140
|
+
fix_action = self._fix_security_issue(issue)
|
|
141
|
+
if fix_action:
|
|
142
|
+
# 通过 Genome Core 四层自审验证修复
|
|
143
|
+
review_passed = True
|
|
144
|
+
review_details = {}
|
|
145
|
+
if HAS_GENOME_CORE:
|
|
146
|
+
review_passed, review_details = full_review(fix_action.fixed_code)
|
|
147
|
+
|
|
148
|
+
return FixResult(
|
|
149
|
+
file=filepath,
|
|
150
|
+
actions=[fix_action],
|
|
151
|
+
genome_review_passed=review_passed,
|
|
152
|
+
genome_review_details=review_details,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# 代码异味修复
|
|
156
|
+
if category == "smell":
|
|
157
|
+
fix_action = self._fix_smell_issue(issue)
|
|
158
|
+
if fix_action:
|
|
159
|
+
return FixResult(file=filepath, actions=[fix_action])
|
|
160
|
+
|
|
161
|
+
# 性能问题修复
|
|
162
|
+
if category == "performance":
|
|
163
|
+
fix_action = self._fix_performance_issue(issue)
|
|
164
|
+
if fix_action:
|
|
165
|
+
return FixResult(file=filepath, actions=[fix_action])
|
|
166
|
+
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
def _fix_security_issue(self, issue: dict) -> Optional[FixAction]:
|
|
170
|
+
"""生成安全修复"""
|
|
171
|
+
code = issue.get("code", "")
|
|
172
|
+
filepath = issue.get("file", "")
|
|
173
|
+
line = issue.get("line", 0)
|
|
174
|
+
|
|
175
|
+
# 检测危险函数类型
|
|
176
|
+
for keyword, template in FIX_TEMPLATES.items():
|
|
177
|
+
if keyword in code:
|
|
178
|
+
if keyword in ("eval", "exec"):
|
|
179
|
+
if keyword == "eval":
|
|
180
|
+
# 提取 eval 的参数
|
|
181
|
+
arg_match = code[code.find("(") + 1 : code.rfind(")")].strip()
|
|
182
|
+
if arg_match:
|
|
183
|
+
fixed = f"ast.literal_eval({arg_match})"
|
|
184
|
+
return FixAction(
|
|
185
|
+
file=filepath,
|
|
186
|
+
line=line,
|
|
187
|
+
description=template["description"],
|
|
188
|
+
original_code=code,
|
|
189
|
+
fixed_code=fixed,
|
|
190
|
+
category="security",
|
|
191
|
+
confidence=0.85,
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
# exec 无法直接替换,加注释提示
|
|
195
|
+
fixed = f"# FIXME: 避免使用 exec()\n# " + code
|
|
196
|
+
return FixAction(
|
|
197
|
+
file=filepath,
|
|
198
|
+
line=line,
|
|
199
|
+
description=template["description"],
|
|
200
|
+
original_code=code,
|
|
201
|
+
fixed_code=fixed,
|
|
202
|
+
category="security",
|
|
203
|
+
confidence=0.7,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
if "os.system" in code:
|
|
207
|
+
import re
|
|
208
|
+
args_match = re.search(r"os\.system\((.+)\)", code)
|
|
209
|
+
if args_match:
|
|
210
|
+
args = args_match.group(1)
|
|
211
|
+
fixed = f"subprocess.run({args}, shell=True, check=True)"
|
|
212
|
+
return FixAction(
|
|
213
|
+
file=filepath,
|
|
214
|
+
line=line,
|
|
215
|
+
description=template["description"],
|
|
216
|
+
original_code=code,
|
|
217
|
+
fixed_code=fixed,
|
|
218
|
+
category="security",
|
|
219
|
+
confidence=0.8,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
def _fix_smell_issue(self, issue: dict) -> Optional[FixAction]:
|
|
225
|
+
"""生成代码异味修复"""
|
|
226
|
+
description = issue.get("description", "")
|
|
227
|
+
filepath = issue.get("file", "")
|
|
228
|
+
line = issue.get("line", 0)
|
|
229
|
+
code = issue.get("code", "")
|
|
230
|
+
|
|
231
|
+
if "裸 except" in description:
|
|
232
|
+
# 修复裸 except
|
|
233
|
+
fixed = "except Exception as e:"
|
|
234
|
+
return FixAction(
|
|
235
|
+
file=filepath,
|
|
236
|
+
line=line,
|
|
237
|
+
description="为裸 except 指定具体的异常类型 Exception",
|
|
238
|
+
original_code=code,
|
|
239
|
+
fixed_code=fixed,
|
|
240
|
+
category="smell",
|
|
241
|
+
confidence=0.95,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
if "参数过多" in description:
|
|
245
|
+
fixed = code + "\n # TODO: 考虑使用 *args, **kwargs 或数据类重构签名"
|
|
246
|
+
return FixAction(
|
|
247
|
+
file=filepath,
|
|
248
|
+
line=line,
|
|
249
|
+
description="参数过多,建议使用数据类或 *args/**kwargs",
|
|
250
|
+
original_code=code,
|
|
251
|
+
fixed_code=fixed,
|
|
252
|
+
category="smell",
|
|
253
|
+
confidence=0.6,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
def _fix_performance_issue(self, issue: dict) -> Optional[FixAction]:
|
|
259
|
+
"""生成性能修复"""
|
|
260
|
+
description = issue.get("description", "")
|
|
261
|
+
filepath = issue.get("file", "")
|
|
262
|
+
line = issue.get("line", 0)
|
|
263
|
+
code = issue.get("code", "")
|
|
264
|
+
|
|
265
|
+
if "文件一次性读取" in code or "文件一次性读取" in description:
|
|
266
|
+
# 修复大文件读取
|
|
267
|
+
fixed = "# 使用分块读取替代:\nfor chunk in iter(lambda: f.read(8192), b''):\n process(chunk)"
|
|
268
|
+
return FixAction(
|
|
269
|
+
file=filepath,
|
|
270
|
+
line=line,
|
|
271
|
+
description="大文件应使用分块读取(chunked reading)",
|
|
272
|
+
original_code=code,
|
|
273
|
+
fixed_code=fixed,
|
|
274
|
+
category="performance",
|
|
275
|
+
confidence=0.8,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
def _fix_dependency(self, issue: dict) -> Optional[FixResult]:
|
|
281
|
+
"""生成依赖修复"""
|
|
282
|
+
filepath = issue.get("file", "")
|
|
283
|
+
line = issue.get("line", 0)
|
|
284
|
+
package = issue.get("package", "")
|
|
285
|
+
version = issue.get("version", "")
|
|
286
|
+
|
|
287
|
+
if not package:
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
# 生成修复后的版本行
|
|
291
|
+
safe_versions = {
|
|
292
|
+
"requests": ">=2.31.0",
|
|
293
|
+
"urllib3": ">=1.26.18",
|
|
294
|
+
"cryptography": ">=41.0.0",
|
|
295
|
+
"django": ">=4.2.7",
|
|
296
|
+
"flask": ">=2.3.3",
|
|
297
|
+
"pillow": ">=10.0.1",
|
|
298
|
+
"pyyaml": ">=6.0",
|
|
299
|
+
"jinja2": ">=3.1.3",
|
|
300
|
+
"werkzeug": ">=3.0.3",
|
|
301
|
+
"numpy": ">=1.26.0",
|
|
302
|
+
"protobuf": ">=3.18.3",
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
safe_ver = safe_versions.get(package.lower())
|
|
306
|
+
if not safe_ver:
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
original_line = f"{package}{version}" if version else package
|
|
310
|
+
fixed_line = f"{package}{safe_ver}"
|
|
311
|
+
|
|
312
|
+
action = FixAction(
|
|
313
|
+
file=filepath,
|
|
314
|
+
line=line,
|
|
315
|
+
description=f"升级 {package} 至安全版本({safe_ver})",
|
|
316
|
+
original_code=original_line,
|
|
317
|
+
fixed_code=fixed_line,
|
|
318
|
+
category="dependency",
|
|
319
|
+
confidence=0.9,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
return FixResult(
|
|
323
|
+
file=filepath,
|
|
324
|
+
actions=[action],
|
|
325
|
+
genome_review_passed=True,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
def get_results(self) -> List[FixResult]:
|
|
329
|
+
return self._results
|