feat(security): 新增工具沙箱安全机制

为所有工具插件添加沙箱拦截层,防止危险的文件访问、Shell命令和Python代码执行。
包含配置文件、核心校验逻辑及31个单元测试。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 12:09:15 +00:00
parent a53ffebe0e
commit 80174acaae
9 changed files with 502 additions and 8 deletions
@@ -34,6 +34,13 @@ async def edit_file(
Returns:
操作结果描述
"""
from kilostar.utils.sandbox import validate_path, PathViolation
try:
file_path = validate_path(file_path, write=True)
except PathViolation as e:
return f"[Sandbox] {e}"
try:
if not os.path.exists(file_path):
return f"[Error] 文件不存在: {file_path}"
@@ -47,6 +47,13 @@ async def file_reader(file_path: str) -> str:
Returns:
文件内容文本,若文件不存在则返回错误信息
"""
from kilostar.utils.sandbox import validate_path, PathViolation
try:
file_path = validate_path(file_path, write=False)
except PathViolation as e:
return f"[Sandbox] {e}"
try:
with open(file_path, "r", encoding="utf-8") as f:
return f.read()
@@ -32,6 +32,16 @@ async def python_executor(code: str, timeout: int = 30) -> str:
Returns:
代码的标准输出 + 标准错误
"""
from kilostar.utils.sandbox import (
validate_python_code, CodeViolation, get_python_timeout,
)
try:
code = validate_python_code(code)
except CodeViolation as e:
return f"[Sandbox] {e}"
timeout = get_python_timeout(timeout)
tmp_file = None
try:
with tempfile.NamedTemporaryFile(
@@ -36,21 +36,36 @@ async def search_file(
Returns:
匹配的文件名和行内容
"""
from kilostar.utils.sandbox import validate_path, PathViolation
try:
cmd = (
f"grep -rn --include='{file_pattern}' "
f"-m {max_results} '{keyword}' '{directory}' 2>/dev/null "
f"| head -n {max_results}"
)
proc = await asyncio.create_subprocess_shell(
cmd,
directory = validate_path(directory, write=False)
except PathViolation as e:
return f"[Sandbox] {e}"
max_results = min(max_results, 100)
try:
grep_args = [
"grep", "-rn",
f"--include={file_pattern}",
"-m", str(max_results),
"--", keyword, directory,
]
proc = await asyncio.create_subprocess_exec(
*grep_args,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=30)
stdout, _ = await asyncio.wait_for(
proc.communicate(), timeout=30
)
output = stdout.decode("utf-8", errors="replace").strip()
if not output:
return f"未找到包含 '{keyword}' 的匹配项"
lines = output.split("\n")
if len(lines) > max_results:
output = "\n".join(lines[:max_results])
return output
except asyncio.TimeoutError:
return "[Error] 搜索超时"
@@ -29,6 +29,16 @@ async def shell_executor(command: str, timeout: int = 30) -> str:
Returns:
命令的 stdout + stderr 输出
"""
from kilostar.utils.sandbox import (
validate_shell_command, CommandViolation, get_shell_timeout,
)
try:
command = validate_shell_command(command)
except CommandViolation as e:
return f"[Sandbox] {e}"
timeout = get_shell_timeout(timeout)
try:
proc = await asyncio.create_subprocess_shell(
command,
@@ -29,6 +29,13 @@ async def write_file(file_path: str, content: str) -> str:
Returns:
操作结果描述
"""
from kilostar.utils.sandbox import validate_path, PathViolation
try:
file_path = validate_path(file_path, write=True)
except PathViolation as e:
return f"[Sandbox] {e}"
try:
dir_path = os.path.dirname(file_path)
if dir_path: