jarvis-ai-assistant 0.5.0__py3-none-any.whl → 0.6.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.
Files changed (41) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +114 -6
  3. jarvis/jarvis_agent/agent_manager.py +3 -0
  4. jarvis/jarvis_agent/jarvis.py +45 -9
  5. jarvis/jarvis_agent/run_loop.py +6 -1
  6. jarvis/jarvis_agent/task_planner.py +219 -0
  7. jarvis/jarvis_c2rust/__init__.py +13 -0
  8. jarvis/jarvis_c2rust/cli.py +405 -0
  9. jarvis/jarvis_c2rust/collector.py +209 -0
  10. jarvis/jarvis_c2rust/library_replacer.py +933 -0
  11. jarvis/jarvis_c2rust/llm_module_agent.py +1265 -0
  12. jarvis/jarvis_c2rust/scanner.py +1671 -0
  13. jarvis/jarvis_c2rust/transpiler.py +1236 -0
  14. jarvis/jarvis_code_agent/code_agent.py +151 -18
  15. jarvis/jarvis_data/config_schema.json +13 -3
  16. jarvis/jarvis_sec/README.md +180 -0
  17. jarvis/jarvis_sec/__init__.py +674 -0
  18. jarvis/jarvis_sec/checkers/__init__.py +33 -0
  19. jarvis/jarvis_sec/checkers/c_checker.py +1269 -0
  20. jarvis/jarvis_sec/checkers/rust_checker.py +367 -0
  21. jarvis/jarvis_sec/cli.py +110 -0
  22. jarvis/jarvis_sec/prompts.py +324 -0
  23. jarvis/jarvis_sec/report.py +260 -0
  24. jarvis/jarvis_sec/types.py +20 -0
  25. jarvis/jarvis_sec/workflow.py +513 -0
  26. jarvis/jarvis_tools/cli/main.py +1 -0
  27. jarvis/jarvis_tools/execute_script.py +1 -1
  28. jarvis/jarvis_tools/read_code.py +11 -1
  29. jarvis/jarvis_tools/read_symbols.py +129 -0
  30. jarvis/jarvis_tools/registry.py +9 -1
  31. jarvis/jarvis_tools/sub_agent.py +4 -3
  32. jarvis/jarvis_tools/sub_code_agent.py +3 -3
  33. jarvis/jarvis_utils/config.py +28 -6
  34. jarvis/jarvis_utils/git_utils.py +39 -0
  35. jarvis/jarvis_utils/utils.py +150 -7
  36. {jarvis_ai_assistant-0.5.0.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/METADATA +13 -1
  37. {jarvis_ai_assistant-0.5.0.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/RECORD +41 -22
  38. {jarvis_ai_assistant-0.5.0.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/entry_points.txt +4 -0
  39. {jarvis_ai_assistant-0.5.0.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/WHEEL +0 -0
  40. {jarvis_ai_assistant-0.5.0.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/licenses/LICENSE +0 -0
  41. {jarvis_ai_assistant-0.5.0.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,367 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ OpenHarmony 安全演进多Agent套件 —— Rust 启发式安全检查器(阶段一)
4
+
5
+ 目标与范围:
6
+ - 聚焦 unsafe 使用、原始指针、错误处理、并发与 FFI 等基础安全问题。
7
+ - 提供可解释的启发式检测与置信度评估,面向 .rs 源文件。
8
+
9
+ 输出约定:
10
+ - 返回 jarvis.jarvis_sec.workflow.Issue 列表(结构化,便于聚合评分与报告生成)。
11
+ - 置信度区间 [0,1];严重性(severity)分为 high/medium/low。
12
+
13
+ 使用方式:
14
+ - from jarvis.jarvis_sec.checkers.rust_checker import analyze_files
15
+ - issues = analyze_files("./repo", ["src/lib.rs", "src/foo.rs"])
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import re
21
+ from pathlib import Path
22
+ from typing import Iterable, List, Sequence, Tuple
23
+
24
+ from jarvis.jarvis_sec.types import Issue
25
+
26
+
27
+ # ---------------------------
28
+ # 规则库(正则表达式)
29
+ # ---------------------------
30
+
31
+ RE_UNSAFE = re.compile(r"\bunsafe\b")
32
+ RE_RAW_PTR = re.compile(r"\*(?:mut|const)\s+[A-Za-z_]\w*") # 类型处的原始指针
33
+ RE_FORGET = re.compile(r"\bmem::forget\b")
34
+ RE_TRANSMUTE = re.compile(r"\bmem::transmute\b")
35
+ RE_MAYBE_UNINIT = re.compile(r"\bMaybeUninit\b")
36
+ RE_ASSUME_INIT = re.compile(r"\bassume_init\s*\(")
37
+
38
+ RE_UNWRAP = re.compile(r"\bunwrap\s*\(", re.IGNORECASE)
39
+ RE_EXPECT = re.compile(r"\bexpect\s*\(", re.IGNORECASE)
40
+ RE_EXTERN_C = re.compile(r'extern\s+"C"')
41
+ RE_UNSAFE_IMPL = re.compile(r"\bunsafe\s+impl\s+(?:Send|Sync)\b|\bimpl\s+unsafe\s+(?:Send|Sync)\b", re.IGNORECASE)
42
+
43
+ # 结果忽略/下划线绑定(可能忽略错误)
44
+ RE_LET_UNDERSCORE = re.compile(r"\blet\s+_+\s*=\s*.+;")
45
+ RE_MATCH_IGNORE_ERR = re.compile(r"\.ok\s*\(\s*\)|\.ok\?\s*;|\._?\s*=\s*.+\.err\(\s*\)", re.IGNORECASE) # 粗略
46
+
47
+
48
+ # ---------------------------
49
+ # 公共工具
50
+ # ---------------------------
51
+
52
+ def _safe_line(lines: Sequence[str], idx: int) -> str:
53
+ if 1 <= idx <= len(lines):
54
+ return lines[idx - 1]
55
+ return ""
56
+
57
+
58
+ def _strip_line(s: str, max_len: int = 200) -> str:
59
+ s = s.strip().replace("\t", " ")
60
+ return s if len(s) <= max_len else s[: max_len - 3] + "..."
61
+
62
+
63
+ def _window(lines: Sequence[str], center: int, before: int = 3, after: int = 3) -> List[Tuple[int, str]]:
64
+ start = max(1, center - before)
65
+ end = min(len(lines), center + after)
66
+ return [(i, _safe_line(lines, i)) for i in range(start, end + 1)]
67
+
68
+
69
+ def _has_safety_comment_around(lines: Sequence[str], line_no: int, radius: int = 5) -> bool:
70
+ """
71
+ Rust 社区约定在 unsafe 附近写 SAFETY: 注释说明前置条件。
72
+ 如存在,适当降低置信度。
73
+ """
74
+ for _, s in _window(lines, line_no, before=radius, after=radius):
75
+ if "SAFETY:" in s or "Safety:" in s or "safety:" in s:
76
+ return True
77
+ return False
78
+
79
+
80
+ def _in_test_context(lines: Sequence[str], line_no: int, radius: int = 20) -> bool:
81
+ """
82
+ 近邻出现 #[test] 或 mod tests { ... } 等,可能处于测试上下文,适度降低严重度。
83
+ """
84
+ for _, s in _window(lines, line_no, before=radius, after=radius):
85
+ if "#[test]" in s or re.search(r"\bmod\s+tests\b", s):
86
+ return True
87
+ return False
88
+
89
+
90
+ def _severity_from_confidence(conf: float) -> str:
91
+ if conf >= 0.8:
92
+ return "high"
93
+ if conf >= 0.6:
94
+ return "medium"
95
+ return "low"
96
+
97
+
98
+ # ---------------------------
99
+ # 规则实现
100
+ # ---------------------------
101
+
102
+ def _rule_unsafe(lines: Sequence[str], relpath: str) -> List[Issue]:
103
+ issues: List[Issue] = []
104
+ for idx, s in enumerate(lines, start=1):
105
+ if not RE_UNSAFE.search(s):
106
+ continue
107
+ conf = 0.8
108
+ if _has_safety_comment_around(lines, idx, radius=5):
109
+ conf -= 0.1
110
+ if _in_test_context(lines, idx):
111
+ conf -= 0.05
112
+ conf = max(0.5, min(0.95, conf))
113
+ issues.append(
114
+ Issue(
115
+ language="rust",
116
+ category="unsafe_usage",
117
+ pattern="unsafe",
118
+ file=relpath,
119
+ line=idx,
120
+ evidence=_strip_line(s),
121
+ description="存在 unsafe 代码块/标识,需证明内存/别名/生命周期安全性。",
122
+ suggestion="将不安全操作封装在最小作用域内,并提供 SAFETY 注释说明前置条件与不变式。",
123
+ confidence=conf,
124
+ severity=_severity_from_confidence(conf),
125
+ )
126
+ )
127
+ return issues
128
+
129
+
130
+ def _rule_raw_pointer(lines: Sequence[str], relpath: str) -> List[Issue]:
131
+ issues: List[Issue] = []
132
+ for idx, s in enumerate(lines, start=1):
133
+ if not RE_RAW_PTR.search(s):
134
+ continue
135
+ conf = 0.75
136
+ if _has_safety_comment_around(lines, idx):
137
+ conf -= 0.1
138
+ if _in_test_context(lines, idx):
139
+ conf -= 0.05
140
+ conf = max(0.5, min(0.9, conf))
141
+ issues.append(
142
+ Issue(
143
+ language="rust",
144
+ category="unsafe_usage",
145
+ pattern="raw_pointer",
146
+ file=relpath,
147
+ line=idx,
148
+ evidence=_strip_line(s),
149
+ description="出现原始指针(*mut/*const),可能绕过借用/生命周期检查,带来未定义行为风险。",
150
+ suggestion="优先使用引用/智能指针;必须使用原始指针时,严格证明无别名、对齐与生命周期安全。",
151
+ confidence=conf,
152
+ severity=_severity_from_confidence(conf),
153
+ )
154
+ )
155
+ return issues
156
+
157
+
158
+ def _rule_transmute(lines: Sequence[str], relpath: str) -> List[Issue]:
159
+ issues: List[Issue] = []
160
+ for idx, s in enumerate(lines, start=1):
161
+ if not RE_TRANSMUTE.search(s):
162
+ continue
163
+ conf = 0.85
164
+ if _has_safety_comment_around(lines, idx):
165
+ conf -= 0.1
166
+ conf = max(0.6, min(0.95, conf))
167
+ issues.append(
168
+ Issue(
169
+ language="rust",
170
+ category="unsafe_usage",
171
+ pattern="mem::transmute",
172
+ file=relpath,
173
+ line=idx,
174
+ evidence=_strip_line(s),
175
+ description="使用 mem::transmute 进行类型转换,若未严格保证布局/对齐/生命周期,将导致未定义行为。",
176
+ suggestion="避免使用 transmute,优先采用安全转换或 bytemuck 等受审计抽象;必须使用时严格注明不变式。",
177
+ confidence=conf,
178
+ severity=_severity_from_confidence(conf),
179
+ )
180
+ )
181
+ return issues
182
+
183
+
184
+ def _rule_maybe_uninit(lines: Sequence[str], relpath: str) -> List[Issue]:
185
+ """
186
+ MaybeUninit + assume_init 组合常见于优化/FFI,需特别小心初始化与有效性。
187
+ """
188
+ issues: List[Issue] = []
189
+ for idx, s in enumerate(lines, start=1):
190
+ if not (RE_MAYBE_UNINIT.search(s) or RE_ASSUME_INIT.search(s)):
191
+ continue
192
+ conf = 0.7
193
+ # 若在邻近几行同时出现 MaybeUninit 与 assume_init,风险更高
194
+ win_text = " ".join(t for _, t in _window(lines, idx, before=3, after=3))
195
+ if RE_MAYBE_UNINIT.search(win_text) and RE_ASSUME_INIT.search(win_text):
196
+ conf += 0.1
197
+ if _has_safety_comment_around(lines, idx):
198
+ conf -= 0.05
199
+ conf = max(0.5, min(0.9, conf))
200
+ issues.append(
201
+ Issue(
202
+ language="rust",
203
+ category="unsafe_usage",
204
+ pattern="MaybeUninit/assume_init",
205
+ file=relpath,
206
+ line=idx,
207
+ evidence=_strip_line(s),
208
+ description="使用 MaybeUninit/assume_init 需保证正确初始化与读取顺序,否则可能导致未定义行为。",
209
+ suggestion="确保初始化前不读取;使用更安全的构造函数;在 SAFETY 注释中说明前置条件。",
210
+ confidence=conf,
211
+ severity=_severity_from_confidence(conf),
212
+ )
213
+ )
214
+ return issues
215
+
216
+
217
+ def _rule_unwrap_expect(lines: Sequence[str], relpath: str) -> List[Issue]:
218
+ issues: List[Issue] = []
219
+ for idx, s in enumerate(lines, start=1):
220
+ if not (RE_UNWRAP.search(s) or RE_EXPECT.search(s)):
221
+ continue
222
+ conf = 0.65
223
+ if _in_test_context(lines, idx):
224
+ conf -= 0.1
225
+ conf = max(0.45, min(0.8, conf))
226
+ issues.append(
227
+ Issue(
228
+ language="rust",
229
+ category="error_handling",
230
+ pattern="unwrap/expect",
231
+ file=relpath,
232
+ line=idx,
233
+ evidence=_strip_line(s),
234
+ description="直接 unwrap/expect 可能在错误条件下 panic,缺少健壮的错误处理路径。",
235
+ suggestion="使用 ? 传播错误或 match 显式处理;为关键路径提供错误上下文与恢复策略。",
236
+ confidence=conf,
237
+ severity=_severity_from_confidence(conf),
238
+ )
239
+ )
240
+ return issues
241
+
242
+
243
+ def _rule_extern_c(lines: Sequence[str], relpath: str) -> List[Issue]:
244
+ issues: List[Issue] = []
245
+ for idx, s in enumerate(lines, start=1):
246
+ if not RE_EXTERN_C.search(s):
247
+ continue
248
+ conf = 0.7
249
+ if _has_safety_comment_around(lines, idx):
250
+ conf -= 0.05
251
+ conf = max(0.5, min(0.85, conf))
252
+ issues.append(
253
+ Issue(
254
+ language="rust",
255
+ category="ffi",
256
+ pattern='extern "C"',
257
+ file=relpath,
258
+ line=idx,
259
+ evidence=_strip_line(s),
260
+ description="FFI 边界需要确保指针有效性、长度/对齐、生命周期、线程安全等约束,否则可能产生未定义行为。",
261
+ suggestion="在 FFI 边界进行严格的参数校验与安全封装;在 SAFETY 注释中记录不变式与约束。",
262
+ confidence=conf,
263
+ severity=_severity_from_confidence(conf),
264
+ )
265
+ )
266
+ return issues
267
+
268
+
269
+ def _rule_unsafe_impl(lines: Sequence[str], relpath: str) -> List[Issue]:
270
+ issues: List[Issue] = []
271
+ for idx, s in enumerate(lines, start=1):
272
+ if not RE_UNSAFE_IMPL.search(s):
273
+ continue
274
+ conf = 0.8
275
+ if _has_safety_comment_around(lines, idx):
276
+ conf -= 0.1
277
+ conf = max(0.6, min(0.95, conf))
278
+ issues.append(
279
+ Issue(
280
+ language="rust",
281
+ category="concurrency",
282
+ pattern="unsafe_impl_Send_or_Sync",
283
+ file=relpath,
284
+ line=idx,
285
+ evidence=_strip_line(s),
286
+ description="手写 unsafe impl Send/Sync 可能破坏并发内存模型保证,带来数据竞争风险。",
287
+ suggestion="避免手写 unsafe impl;必要时严格证明线程安全前置条件并最小化不安全区域。",
288
+ confidence=conf,
289
+ severity=_severity_from_confidence(conf),
290
+ )
291
+ )
292
+ return issues
293
+
294
+
295
+ def _rule_ignore_result(lines: Sequence[str], relpath: str) -> List[Issue]:
296
+ """
297
+ 启发式:使用 let _ = xxx; 或 .ok() 等可能忽略错误。
298
+ 该规则误报可能较高,因此置信度较低。
299
+ """
300
+ issues: List[Issue] = []
301
+ for idx, s in enumerate(lines, start=1):
302
+ if not (RE_LET_UNDERSCORE.search(s) or RE_MATCH_IGNORE_ERR.search(s)):
303
+ continue
304
+ conf = 0.55
305
+ if _in_test_context(lines, idx):
306
+ conf -= 0.1
307
+ conf = max(0.4, min(0.7, conf))
308
+ issues.append(
309
+ Issue(
310
+ language="rust",
311
+ category="error_handling",
312
+ pattern="ignored_result",
313
+ file=relpath,
314
+ line=idx,
315
+ evidence=_strip_line(s),
316
+ description="可能忽略了返回的错误结果,导致失败未被处理。",
317
+ suggestion="显式处理 Result(? 传播或 match),确保错误路径涵盖资源回收与日志记录。",
318
+ confidence=conf,
319
+ severity=_severity_from_confidence(conf),
320
+ )
321
+ )
322
+ return issues
323
+
324
+
325
+ # ---------------------------
326
+ # 对外主入口
327
+ # ---------------------------
328
+
329
+ def analyze_rust_text(relpath: str, text: str) -> List[Issue]:
330
+ """
331
+ 基于提供的文本进行 Rust 启发式分析。
332
+ """
333
+ lines = text.splitlines()
334
+ issues: List[Issue] = []
335
+ issues.extend(_rule_unsafe(lines, relpath))
336
+ issues.extend(_rule_raw_pointer(lines, relpath))
337
+ issues.extend(_rule_transmute(lines, relpath))
338
+ issues.extend(_rule_maybe_uninit(lines, relpath))
339
+ issues.extend(_rule_unwrap_expect(lines, relpath))
340
+ issues.extend(_rule_extern_c(lines, relpath))
341
+ issues.extend(_rule_unsafe_impl(lines, relpath))
342
+ issues.extend(_rule_ignore_result(lines, relpath))
343
+ return issues
344
+
345
+
346
+ def analyze_rust_file(base: Path, relpath: Path) -> List[Issue]:
347
+ """
348
+ 从磁盘读取文件进行分析。
349
+ """
350
+ try:
351
+ text = (base / relpath).read_text(errors="ignore")
352
+ except Exception:
353
+ return []
354
+ return analyze_rust_text(str(relpath), text)
355
+
356
+
357
+ def analyze_files(base_path: str, files: Iterable[str]) -> List[Issue]:
358
+ """
359
+ 批量分析文件,相对路径相对于 base_path。
360
+ """
361
+ base = Path(base_path).resolve()
362
+ out: List[Issue] = []
363
+ for f in files:
364
+ p = Path(f)
365
+ if p.suffix.lower() == ".rs":
366
+ out.extend(analyze_rust_file(base, p))
367
+ return out
@@ -0,0 +1,110 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ OpenHarmony 安全演进套件 —— 命令行入口(Typer 版本)
4
+
5
+ 用法示例:
6
+ - Agent模式(单Agent,逐条子任务分析)
7
+ python -m jarvis.jarvis_sec.cli agent --path ./target_project
8
+
9
+ 可选参数:
10
+
11
+ - --output: 最终Markdown报告输出路径(默认 ./report.md)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import sys
17
+ from pathlib import Path
18
+ from typing import Optional
19
+
20
+ import typer
21
+ from jarvis.jarvis_utils.utils import init_env
22
+ # removed: set_config import(避免全局覆盖模型组配置)
23
+ from jarvis.jarvis_sec.workflow import run_with_multi_agent, run_security_analysis_fast
24
+
25
+ app = typer.Typer(
26
+ add_completion=False,
27
+ no_args_is_help=True,
28
+ help="OpenHarmony 安全演进套件(单Agent逐条子任务分析)",
29
+ )
30
+
31
+
32
+
33
+
34
+ @app.command("agent", help="Agent模式(单Agent逐条子任务分析)")
35
+ def agent(
36
+ path: str = typer.Option(..., "--path", "-p", help="待分析的根目录"),
37
+
38
+ llm_group: Optional[str] = typer.Option(
39
+ None, "--llm-group", "-g", help="使用的模型组(仅对本次运行生效,不修改全局配置)"
40
+ ),
41
+ output: Optional[str] = typer.Option(
42
+ "report.md", "--output", "-o", help="最终Markdown报告输出路径(默认 ./report.md)"
43
+ ),
44
+ ) -> None:
45
+ # 初始化环境,确保平台/模型等全局配置就绪(避免 NoneType 平台)
46
+ try:
47
+ init_env("欢迎使用 Jarvis-OpenHarmony 安全套件!", None)
48
+ except Exception:
49
+ # 环境初始化失败不应阻塞CLI基础功能,继续后续流程
50
+ pass
51
+
52
+ # 若指定了模型组:仅对本次运行生效,透传给 Agent;不修改全局配置(无需 set_config)
53
+
54
+
55
+ text: Optional[str] = None
56
+ try:
57
+ text = run_with_multi_agent(
58
+ path,
59
+ llm_group=llm_group,
60
+ )
61
+ except Exception as e:
62
+ try:
63
+ typer.secho(f"[jarvis_sec] Agent 分析过程出错,将回退到直扫基线(fast):{e}", fg=typer.colors.YELLOW, err=True)
64
+ except Exception:
65
+ pass
66
+ text = None
67
+
68
+ if not text or not str(text).strip():
69
+ try:
70
+ typer.secho("[jarvis_sec] Agent 无输出,回退到直扫基线(fast)。", fg=typer.colors.YELLOW, err=True)
71
+ except Exception:
72
+ pass
73
+ text = run_security_analysis_fast(path)
74
+
75
+ if output:
76
+ try:
77
+ md_text = text or ""
78
+ try:
79
+ lines = (text or "").splitlines()
80
+ idx = -1
81
+ for i, ln in enumerate(lines):
82
+ if ln.strip().startswith("# OpenHarmony 安全问题分析报告"):
83
+ idx = i
84
+ break
85
+ if idx >= 0:
86
+ md_text = "\n".join(lines[idx:])
87
+ except Exception:
88
+ md_text = text or ""
89
+ p = Path(output)
90
+ p.parent.mkdir(parents=True, exist_ok=True)
91
+ p.write_text(md_text, encoding="utf-8")
92
+ try:
93
+ typer.secho(f"[jarvis_sec] Markdown 报告已写入: {p}", fg=typer.colors.GREEN)
94
+ except Exception:
95
+ pass
96
+ except Exception as e:
97
+ try:
98
+ typer.secho(f"[jarvis_sec] 写入Markdown报告失败: {e}", fg=typer.colors.RED, err=True)
99
+ except Exception:
100
+ pass
101
+ typer.echo(text)
102
+
103
+
104
+ def main() -> int:
105
+ app()
106
+ return 0
107
+
108
+
109
+ if __name__ == "__main__":
110
+ sys.exit(main())