MenuPilot 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.
@@ -0,0 +1,216 @@
1
+ """
2
+ Python 代码沙箱执行器 — 在受限环境中执行 LLM 生成的 Python 代码。
3
+
4
+ 限制:
5
+ - 白名单 import(pandas/openpyxl/numpy/json/csv/re/collections)
6
+ - 禁止危险 builtins(exec/eval/compile/open/__import__)
7
+ - 禁止文件系统写操作在工作目录外
8
+ - 禁止网络/子进程
9
+
10
+ 用途:Agent 模式下 LLM 可调用此工具做数据的增删改查,
11
+ 匹配/填充逻辑必须走预注册的 pipeline tool,不得自行生成。
12
+ """
13
+
14
+ import builtins as _builtins_module
15
+ import os
16
+ from typing import Any, Dict, Optional
17
+
18
+ # ── 白名单 ────────────────────────────────────────────────────────
19
+
20
+ # 只读模式:禁止 openpyxl(防止 sandbox 内写入 Excel)。
21
+ # 所有写操作必须通过 pipeline 工具(run_sop_matching / run_option_expansion)。
22
+ ALLOWED_IMPORTS = {
23
+ "pandas", "numpy", "json", "csv", "re", "collections",
24
+ "math", "itertools", "functools", "datetime", "pathlib",
25
+ }
26
+
27
+ BLOCKED_BUILTINS = {
28
+ "__import__", "exec", "eval", "compile", "open",
29
+ "input", "breakpoint",
30
+ }
31
+
32
+ # 安全的 builtins
33
+ SAFE_BUILTINS = {
34
+ k: v for k, v in _builtins_module.__dict__.items()
35
+ if k not in BLOCKED_BUILTINS
36
+ }
37
+ # 补充 print / len / range 等常用函数
38
+ SAFE_BUILTINS["print"] = print
39
+ SAFE_BUILTINS["len"] = len
40
+
41
+
42
+ def _safe_import(name, globals=None, locals=None, fromlist=(), level=0):
43
+ """受控的 __import__ 替代:仅允许白名单模块。"""
44
+ root = name.split(".")[0]
45
+ if root not in ALLOWED_IMPORTS:
46
+ raise ImportError(
47
+ f"沙箱禁止导入模块: {name}。仅允许: {sorted(ALLOWED_IMPORTS)}"
48
+ )
49
+ return _builtins_module.__import__(name, globals, locals, fromlist, level)
50
+
51
+
52
+ def execute(code: str, context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
53
+ """在受限环境中执行 Python 代码。
54
+
55
+ 代码只能使用白名单模块和安全 builtins。
56
+ 执行结果通过 local_vars 字典返回。
57
+
58
+ Args:
59
+ code: 要执行的 Python 代码字符串。
60
+ context: 注入到执行环境的额外变量(如 {'df': some_dataframe})。
61
+
62
+ Returns:
63
+ {"result": dict, "stdout": str} — result 包含代码中赋值的局部变量。
64
+
65
+ Raises:
66
+ SyntaxError: 代码语法错误。
67
+ ImportError: 尝试导入非白名单模块。
68
+ Exception: 代码运行时错误。
69
+ """
70
+ # 编译检查(在执行前捕获语法错误)
71
+ try:
72
+ compiled = compile(code, "<sandbox>", "exec")
73
+ except SyntaxError as e:
74
+ return {"error": f"语法错误: {e}", "result": {}, "stdout": ""}
75
+
76
+ # 构建执行环境
77
+ exec_globals = {
78
+ "__builtins__": {
79
+ **SAFE_BUILTINS,
80
+ "__import__": _safe_import,
81
+ },
82
+ }
83
+ if context:
84
+ exec_globals.update(context)
85
+
86
+ exec_locals = {}
87
+
88
+ # 捕获 stdout
89
+ import io
90
+ stdout_buf = io.StringIO()
91
+
92
+ try:
93
+ # 重定向 print 输出
94
+ exec_globals["__builtins__"]["print"] = lambda *a, **kw: print(
95
+ *a, **{**kw, "file": stdout_buf}
96
+ ) if True else None
97
+ exec(compiled, exec_globals, exec_locals)
98
+ return {
99
+ "result": {k: v for k, v in exec_locals.items() if not k.startswith("_")},
100
+ "stdout": stdout_buf.getvalue(),
101
+ }
102
+ except Exception as e:
103
+ return {
104
+ "error": f"{type(e).__name__}: {e}",
105
+ "result": exec_locals,
106
+ "stdout": stdout_buf.getvalue(),
107
+ }
108
+
109
+
110
+ # ── 自测 ──────────────────────────────────────────────────────────
111
+
112
+ if __name__ == "__main__":
113
+ passed = 0
114
+ failed = 0
115
+
116
+ def check(condition, msg):
117
+ global passed, failed
118
+ if condition:
119
+ passed += 1
120
+ print(f" PASS {msg}")
121
+ else:
122
+ failed += 1
123
+ print(f" FAIL {msg}")
124
+
125
+ print("=== Sandbox 自测 ===\n")
126
+
127
+ # ── 1. 基本执行 ──
128
+ print("1. 基本执行")
129
+ r = execute("x = 1 + 2")
130
+ check(r["result"]["x"] == 3, f"x = 3(实际 {r['result']})")
131
+ check("error" not in r, "无错误")
132
+ print()
133
+
134
+ # ── 2. 白名单 import ──
135
+ print("2. 白名单 import(pandas)")
136
+ r = execute("import pandas as pd; df = pd.DataFrame({'a': [1,2]})")
137
+ check("error" not in r, f"import pandas 成功")
138
+ check("df" in r["result"], "df 变量可访问")
139
+ print()
140
+
141
+ # ── 3. 禁止 import os ──
142
+ print("3. 禁止 import os")
143
+ r = execute("import os")
144
+ check("error" in r, f"ImportError 被捕获(实际 {r.get('error', 'none')})")
145
+ print()
146
+
147
+ # ── 4. 禁止 import subprocess ──
148
+ print("4. 禁止 import subprocess")
149
+ r = execute("import subprocess as sp")
150
+ check("error" in r, "subprocess 被阻止")
151
+ print()
152
+
153
+ # ── 5. 禁止 exec() ──
154
+ print("5. 禁止 exec() 嵌套调用")
155
+ r = execute("exec('x = 1')")
156
+ check("error" in r, f"exec 被阻止(实际 {r.get('error', 'none')})")
157
+ print()
158
+
159
+ # ── 6. 禁止 eval() ──
160
+ print("6. 禁止 eval()")
161
+ r = execute("x = eval('1+1')")
162
+ check("error" in r, "eval 被阻止")
163
+ print()
164
+
165
+ # ── 7. 禁止 open() ──
166
+ print("7. 禁止 open()")
167
+ r = execute("open('/etc/passwd')")
168
+ check("error" in r, "open 被阻止")
169
+ print()
170
+
171
+ # ── 8. 语法错误捕获 ──
172
+ print("8. 语法错误捕获")
173
+ r = execute("x = ;")
174
+ check("error" in r and "语法错误" in r["error"], f"语法错误正确捕获(实际 {r})")
175
+ print()
176
+
177
+ # ── 9. 运行时错误传递 ──
178
+ print("9. 运行时错误传递")
179
+ r = execute("x = 1 / 0")
180
+ check("error" in r and "ZeroDivisionError" in r["error"],
181
+ f"运行时错误正确传递(实际 {r.get('error')})")
182
+ print()
183
+
184
+ # ── 10. context 注入 ──
185
+ print("10. context 变量注入")
186
+ r = execute("y = my_var * 2", context={"my_var": 21})
187
+ check(r["result"]["y"] == 42, f"y = 42(实际 {r['result']})")
188
+ print()
189
+
190
+ # ── 11. 多行代码 ──
191
+ print("11. 多行代码执行")
192
+ r = execute("a = [];\nfor i in range(3):\n a.append(i*i)")
193
+ check(r["result"]["a"] == [0, 1, 4], f"a = [0,1,4](实际 {r['result']})")
194
+ print()
195
+
196
+ # ── 12. stdout 捕获 ──
197
+ print("12. print 输出捕获")
198
+ r = execute("print('hello sandbox')")
199
+ check("hello sandbox" in r.get("stdout", ""), f"stdout 捕获成功(实际 {r.get('stdout')!r})")
200
+ print()
201
+
202
+ # ── 13. 空代码 ──
203
+ print("13. 空代码")
204
+ r = execute("")
205
+ check("error" not in r, "空代码无错误")
206
+ check(r["result"] == {}, "result 为空")
207
+ print()
208
+
209
+ # ── 14. 禁止 __import__ 绕过 ──
210
+ print("14. 禁止 __import__ 直接调用")
211
+ r = execute("m = __import__('os')")
212
+ check("error" in r, "__import__ 被阻止")
213
+ print()
214
+
215
+ # ── 汇总 ──
216
+ print(f"=== 结果: {passed} passed, {failed} failed ===")