Path Safety
服务端路径安全与文件访问编码规范。在编写文件下载路由、Agent 工具(文件读取/目录列出)、数据连接器/Loader、Workspace 路径操作、沙箱配置时使用。
Install
npx skills add microsoft/data-formulator --skill path-safety -g -a claude-code -y- Repository
- microsoft/data-formulator17.6k
- Category
- AI and agents
- Updated
- 5d ago
- License
- MIT
- Context cost
- Lightabout 1,172 tokens
- Runs scripts
- No, instructions only
What it does
服务端路径安全与文件访问编码规范。在编写文件下载路由、Agent 工具(文件读取/目录列出)、数据连接器/Loader、Workspace 路径操作、沙箱配置时使用。
Path Safety, 服务端安全编码规范
来源:
docs/dev-guides/8-path-safety.md(正式开发规范)+design-docs/issues/002-arbitrary-file-read-audit.md(安全审计复核)。 本文档提炼了 6 条必须遵守的编码规范。违反任一条即可能引入路径穿越(LFI)漏洞。
R1. 文件下载:用 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,无需额外处理。
R2. 路径安全检查:用 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)
R3. Agent 工具复用 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", ""))

