بنقرة واحدة
path-safety
服务端路径安全与文件访问编码规范。在编写文件下载路由、Agent 工具(文件读取/目录列出)、数据连接器/Loader、Workspace 路径操作、沙箱配置时使用。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
服务端路径安全与文件访问编码规范。在编写文件下载路由、Agent 工具(文件读取/目录列出)、数据连接器/Loader、Workspace 路径操作、沙箱配置时使用。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
统一错误处理系统。在添加 API 端点、修改错误处理、添加前端 API 调用、编写错误相关测试时使用。
Greeting-triggered self-check — recognise greetings, check Edition version against the upstream tag, scan AI-Memory announcements, and report inside the greeting reply
LLM Agent 多语言注入规范。在修改 Agent 提示词、添加新的 Agent 端点、处理用户可见的后端消息(message_code)时使用。
Coordinates generic project Agency CLI, MCP authorization, M365 consent boundaries, Azure tooling, and Microsoft internal plugin adoption. Use when setting up or auditing Agency profiles, MCP servers, WorkIQ/M365 access, or curated plugins for a project.
Guides on-need adoption of an Agency MCP or Microsoft Foundry capability from the starter's capability catalog. Use when a project asks 'can we use X', 'should we adopt X', or needs to pull a specific tool/profile/Foundry capability without loading the whole surface.
Coordinates Microsoft Fabric MCP access, agents, skills, and curated marketplace plugins for a Fabric-heavy project. Use when setting up or auditing Fabric profiles, notebook-generation plugins, semantic-model review, or Fabric data-security checks.
| name | path-safety |
| description | 服务端路径安全与文件访问编码规范。在编写文件下载路由、Agent 工具(文件读取/目录列出)、数据连接器/Loader、Workspace 路径操作、沙箱配置时使用。 |
| lastReviewed | "2026-07-09T00:00:00.000Z" |
来源:
docs/dev-guides/8-path-safety.md(正式开发规范)+design-docs/issues/002-arbitrary-file-read-audit.md(安全审计复核)。 本文档提炼了 6 条必须遵守的编码规范。违反任一条即可能引入路径穿越(LFI)漏洞。
ConfinedDir.resolve() + send_file,禁用 send_from_directory原因:send_from_directory(dir, user_input) 内部会对 user_input 二次解析路径,与前置安全检查形成 TOCTOU 不一致。
# ❌ BAD — 安全检查用 resolved target,发送用原始 filename,两次解析不一致
target = (scratch_dir / filename).resolve()
target.relative_to(scratch_dir.resolve()) # 检查通过
return send_from_directory(str(scratch_dir), filename) # 再次解析
# ✅ GOOD — 检查和发送用同一个 resolved path
scratch_jail = workspace.confined_scratch
target = scratch_jail.resolve(filename)
return send_file(target) # 直接用已验证的路径
send_file(Path) 会根据扩展名自动推断 MIME type,无需额外处理。
ConfinedDir,禁止 str.startswith原因:str(path).startswith(str(root)) 存在前缀碰撞缺陷(如 /workspace vs /workspace_evil)。
# ❌ BAD
if not str(resolved).startswith(str(root_resolved) + os.sep):
raise ValueError("escape")
# ✅ GOOD — 统一走 ConfinedDir,内部使用 Path.is_relative_to()
jail = ConfinedDir(root_resolved, mkdir=False)
target = jail.resolve(user_input)
Workspace.confined_*原因:Agent 工具参数由 LLM 生成,必须视为间接用户输入。不要在工具内手写 Path(root) / rel_path 或 resolve() + relative_to();入口处复用 Workspace 暴露的 ConfinedDir。
# ❌ BAD — 手写路径拼接和校验
def _tool_read_file(self, args, workspace_path):
target = (workspace_path / rel_path).resolve()
target.relative_to(workspace_path)
# ✅ GOOD — 入口拿到 ConfinedDir,工具只调用 jail.resolve()
def _execute_tool(self, name, args):
workspace_jail = self.workspace.confined_root
scratch_jail = self.workspace.confined_scratch
return self._tool_read_file(args, workspace_jail)
def _tool_read_file(self, args, workspace_jail):
target = workspace_jail.resolve(args.get("path", ""))
ConfinedDir,禁止裸路径拼接原因:Path(root) / user_input 是路径穿越的高频入口。ConfinedDir 封装了三层防御(拒绝绝对路径 → 拒绝 .. 段 → resolve + is_relative_to)。
from data_formulator.security.path_safety import ConfinedDir
# ❌ BAD — 手动拼接 + 手动校验,容易遗漏
local_file = tmp_path / blob_relative_name
local_file.parent.mkdir(parents=True, exist_ok=True)
local_file.write_bytes(data)
# ✅ GOOD — ConfinedDir 自动校验 + 创建父目录
jail = ConfinedDir(tmp_path, mkdir=False)
jail.write(blob_relative_name, data) # 自动校验 + 写入
用户输入(filename / relative_path / blob key)
│
▼
safe_data_filename() / secure_filename() ← 第一层:输入清洗
│
▼
ConfinedDir.resolve() ← 第二层:路径约束
│
▼
安全的 Path 对象
使用 Workspace.get_file_path() 的场景不需要手动调用 ConfinedDir,因为它内部已包含等价的校验。
原因:桌面单用户模式允许访问本机文件系统(预期行为),但多用户/云部署下等于开放服务器读权限。
规范:任何新的 Loader / Connector 如果涉及直接读取宿主文件系统(不通过 Workspace API),必须:
data_loader/__init__.py 的 _enforce_deployment_restrictions() 中注册禁用规则create_connector() 会拒绝已禁用的类型# data_loader/__init__.py — 参考 local_folder 的处理方式
def _enforce_deployment_restrictions():
backend = os.environ.get("WORKSPACE_BACKEND", "local")
if backend != "local":
for key in ("local_folder", "your_new_local_loader"):
if key in DATA_LOADERS:
del DATA_LOADERS[key]
DISABLED_LOADERS[key] = f"{key} disabled in multi-user mode"
判断标准:如果 Loader 的构造函数接受一个用户可控的本机路径(如 root_dir),它就需要部署守卫。
原因:not_a_sandbox 模式下 LLM 生成的代码在宿主进程直接执行,可绕过所有路径检查。
app.py 已在启动时检测此配置并输出 logger.critical 警告。新增的沙箱模式或部署脚本应确保:
WORKSPACE_BACKEND != "local" 时,SANDBOX 必须为 docker 或 localSANDBOX=docker| 场景 | 必须做的事 |
|---|---|
| 新增文件下载路由 | 用 ConfinedDir.resolve() 得到路径,再 send_file(resolved_path);不用 send_from_directory |
| 新增 Agent 工具(读文件/列目录) | 入口复用 workspace.confined_root / workspace.confined_scratch,工具内只调用 jail.resolve() |
| 路径包含判断 | 用 ConfinedDir.resolve(),不要手写 Path.is_relative_to() 或 str.startswith() |
Path(root) / variable 模式 | 改用 ConfinedDir 或 Workspace.get_file_path() |
| 新增本机文件系统 Loader | 在 _enforce_deployment_restrictions() 中注册多用户禁用 |
| 部署配置 | 多用户模式必须 SANDBOX=docker 或 SANDBOX=local |
docs/dev-guides/8-path-safety.md — 服务端路径安全开发规范design-docs/6-path-safety-confined-dir.md — 剩余未完成实现项状态页design-docs/issues/002-arbitrary-file-read-audit.md — 安全审计复核报告py-src/data_formulator/security/path_safety.py — ConfinedDir 源码