返回提交历史
Modified
etc/unittest/mcp.py
+2
-2
Modified
g4f/mcp/pa_provider.py
+23
-2
Modified
g4f/mcp/tools.py
+6
-1
XFEstudio/gpt4free
Address code review: improve type hints, security docs, FileListTool skipped count, dynamic _TOOL_COUNT
Agent-Logs-Url: https://github.com/xtekky/gpt4free/sessions/1b3f481e-143f-4c30-9982-1063c0338ec3 Co-authored-by: hlohaus <983577+hlohaus@users.noreply.github.com>
0501738d
代码差异
3 个文件
+31
-5
@@ -19,8 +19,8 @@ try:
19
19
except ImportError:
20
20
has_requirements = False
21
21
22
# Total number of tools registered in MCPServer
23
_TOOL_COUNT = 10
22
# Total number of tools registered in MCPServer (derived at import time)
23
_TOOL_COUNT = len(MCPServer().tools)
24
24
25
25
26
26
class TestMCPServer(unittest.IsolatedAsyncioTestCase):
@@ -11,6 +11,27 @@ A ``.pa.py`` file is a plain Python file that is executed inside a sandbox.
11
11
Inside that sandbox the code may only import from the *whitelisted* module set
12
12
and may only access the file-system through a workspace-scoped ``open()``.
13
13
14
Security model
15
--------------
16
The sandbox mitigates the following vectors:
17
18
* **Arbitrary module imports** — only modules in :data:`SAFE_MODULES` may be
19
imported. The built-in ``__import__`` is replaced with a wrapper that raises
20
``ImportError`` for any top-level name not in the allowlist. Relative imports
21
are unconditionally blocked.
22
* **Filesystem escape** — ``open()`` is replaced with a workspace-scoped version
23
that resolves symlinks and checks that the canonical path starts with the
24
workspace root. Direct ``os``/``pathlib`` access is blocked because those
25
modules are not in the allowlist.
26
* **Code injection** — ``exec``, ``eval``, ``compile``, and ``input`` are removed
27
from the sandbox built-ins so code in the sandbox cannot spawn secondary
28
execution contexts.
29
30
Known limitations: the sandbox does not enforce CPU/memory limits or wall-clock
31
timeouts. Callers that need to bound execution time should wrap
32
:func:`execute_safe_code` with a ``asyncio.wait_for`` or ``concurrent.futures``
33
timeout.
34
14
35
Typical layout of a ``.pa.py`` file::
15
36
16
37
from aiohttp import ClientSession
@@ -40,7 +61,7 @@ import contextlib
40
61
import traceback
41
62
import builtins as _builtins
42
63
from pathlib import Path
43
from typing import Any, Dict, FrozenSet, List, Optional
64
from typing import Any, Dict, FrozenSet, List, Optional, Type
44
65
45
66
# ---------------------------------------------------------------------------
46
67
# Workspace directory
@@ -251,7 +272,7 @@ def execute_safe_code(
251
272
# .pa.py provider loader
252
273
# ---------------------------------------------------------------------------
253
274
254
def load_pa_provider(file_path: "str | Path") -> Optional[Any]:
275
def load_pa_provider(file_path: "str | Path") -> Optional[Type]:
255
276
"""Load a ``.pa.py`` file and return the provider class it defines.
256
277
257
278
The file is executed inside the safe sandbox. The module is expected to
@@ -689,6 +689,7 @@ class FileListTool(MCPTool):
689
689
return {"error": f"Path is not a directory: {rel_path}"}
690
690
691
691
entries = []
692
skipped = 0
692
693
iterator = target.rglob("*") if recursive else target.iterdir()
693
694
for entry in sorted(iterator):
694
695
try:
@@ -701,14 +702,18 @@ class FileListTool(MCPTool):
701
702
info["size"] = entry.stat().st_size
702
703
entries.append(info)
703
704
except Exception:
705
skipped += 1
704
706
continue
705
707
706
return {
708
result: Dict[str, Any] = {
707
709
"workspace": str(workspace),
708
710
"path": rel_path or "/",
709
711
"entries": entries,
710
712
"count": len(entries),
711
713
}
714
if skipped:
715
result["skipped"] = skipped
716
return result
712
717
except Exception as exc:
713
718
return {"error": f"List failed: {exc}"}
714
719