新闻详情

新闻详情

首页 / 资讯中心 / 详情

Data Formulator 服务端路径安全开发规范:ConfinedDir 路径约束原语与全链路防护实践

发布时间:2026/9/14 0:01:17来源:尧图网络
Data Formulator 服务端路径安全开发规范:ConfinedDir 路径约束原语与全链路防护实践
Data Formulator 服务端路径安全开发规范ConfinedDir 路径约束原语与全链路防护实践【免费下载链接】data-formulator Data Formulator is an interactive AI-powered data analysis system makes it easy to connect, explore and visualize data.项目地址: https://gitcode.com/GitHub_Trending/da/data-formulator本文基于docs/dev-guides/8-path-safety.mdData Formulator 核心团队维护最后更新 2026-04-28整理并扩展结合仓库源码与测试用例深入讲解服务端路径安全的设计与落地。导读Data Formulator 是交互式 AI 数据分析系统后端同时面向用户上传、LLM 生成的 Agent 工具参数、外部存储与 HTTP 请求等多元输入。任何把不可信路径片段拼到服务端根目录上的代码都必须先经过统一路径约束。本文是面向后端开发者的路径安全规范系统讲解ConfinedDir路径约束原语的设计与四层防护、文件名清洗的两层防线、文件下载/上传 Route、Agent 工具、Data Loader 与 Sandbox 部署的安全要求以及对应的测试与迁移清单。读完本文你将掌握 Data Formulator 中统一的路径安全接入方式能够正确使用ConfinedDir避免手写resolve() relative_to()这类易出错的反模式并懂得如何为新增模块做路径安全检查。1. 核心原则所有路径操作必须经过统一约束Data Formulator 的路径安全体系建立在一条铁律之上任何把不可信路径片段拼到服务端根目录上的代码都必须先经过统一路径约束。这里的不可信路径片段来源包括用户输入上传文件名、URL、表单参数LLM 生成的 Agent 工具参数LLM 输入又来自用户因此同样不可信外部存储返回的对象名如 Blob 路径HTTP body / query / path 参数。下表汇总了各类场景对应的标准做法即应当怎么做场景规范Workspace data 文件Workspace.get_file_path()内部使用ConfinedDirWorkspace 根/data/scratch 子目录workspace.confined_root/confined_data/confined_scratch属性Agent 读文件/列目录工具workspace.confined_root/workspace.confined_scratch传入各工具方法文件下载 routeworkspace.confined_scratch.resolve(filename)后传给send_file()文件上传 routesecure_filename()清洗 workspace.confined_scratch.resolve()二次校验任意 root relative pathConfinedDir(root).resolve(relative)知识库文件读写通过KnowledgeStore内部的ConfinedDir推理日志写入通过ReasoningLogger内部的ConfinedDir读取宿主文件系统的 Loader必须注册多用户部署禁用规则Sandbox 部署多用户模式不得使用not_a_sandbox同时明确两条绝对禁止禁止手写resolve() relative_to()或resolve() is_relative_to()路径检查模式。这些逻辑已统一封装在ConfinedDir.resolve()中手写会导致逻辑重复、不一致和遗漏风险禁止把用户、LLM、外部存储、HTTP 参数直接用于裸路径拼接例如Path(root) / user_input、root / filename。local_folder_data_loader.py是全仓库首个采用ConfinedDir的实现即原始采用者后续的迁移都以此为标准逐步推进。2. 路径约束原语ConfinedDir 的四层防护ConfinedDir是服务端路径约束的默认原语位于 path_safety.py。它实现了一个目录监狱directory jail所有路径解析都通过这一个唯一关口single chokepoint进行一旦解析结果逃出 root立即抛出ValueError。2.1 基本用法from data_formulator.security.path_safety import ConfinedDir jail ConfinedDir(tmp_path, mkdirFalse) target jail.resolve(data/report.csv) jail.write(scratch/output.csv, bcontent)从源码看path_safety.py构造时ConfinedDir会先对 root 做Path(root).resolve()归一化mkdirTrue默认时还会自动创建目录树。实例在构造后不可变使用__slots__仅存_root因此是线程安全的——文档注释明确说明Path.resolve()与is_relative_to()属于 OS 层操作天然适合并发使用。2.2 resolve() 的四层防护ConfinedDir.resolve()的防护层次见 path_safety.py拒绝空路径relative为空直接抛ValueError(Empty relative path)拒绝绝对路径rel.is_absolute() or rel.root为真即拒绝防止Path(root) / /etc/passwd之类拼接意外覆盖 root拒绝..路径段显式检查Path(relative).parts中是否存在..在拼接之前就把路径穿越段挡下符号链接逃逸检查(self._root / relative).resolve()展开所有符号链接后用Path.is_relative_to()确认规范化结果仍在 root 内——这一步专门防 symlink escape。捕获ValueError时调用方应返回应用层错误或跳过不可信外部对象不要继续使用原始路径。2.3 扩展 API除resolve()与write()外ConfinedDir还提供了一组扩展方法供推理日志ReasoningLogger、知识库KnowledgeStore等场景复用所有操作都经由resolve()继承路径穿越防护方法用途关键行为read_text(relative, encodingutf-8)读文本文件默认 UTF-8write_text(relative, content, encodingutf-8)写文本文件自动创建父目录exists(relative)判断存在性穿越路径返回False而非抛错调用方可视为不存在iterdir(relative)列目录空字符串表示 root 本身rglob(pattern, relative)递归 glob起始点同样受约束unlink(relative)删除文件穿越路径抛ValueError__truediv__运算符重载jail / sub/path等价于jail.resolve(sub/path)对应行为均有测试覆盖见 test_confined_dir_extended.py例如read_text(../etc/passwd)抛ValueErrorexists(../../../etc/passwd)返回Falsewrite_text(../../evil.txt, ...)抛ValueErrorjail / x.txt返回与(tmp_path / x.txt).resolve()相等的路径。3. 文件名清洗两层独立防线路径约束和文件名清洗是两层不同防线职责各异、缺一不可API用途safe_data_filename()Workspace 数据文件名保留 Unicode中文、日文、韩文等去掉目录组件和控制字符secure_filename()identity、URL/上传临时文件名等需要 ASCII 安全名的场景ConfinedDir.resolve()校验清洗后的相对路径不会逃出 rootsafe_data_filename()实现在 parquet_utils.py通过Path(filename).name提取 basename 从而去掉所有目录组件再用正则[\x00-\x1f]剥离控制字符最后拒绝空名、.与..。它之所以保留 Unicode是因为werkzeug.secure_filename会直接剥离非 ASCII 字符不适合中文/日文文件名场景而secure_filename()则用于 identity 目录名见下文、URL、上传临时文件等需要纯 ASCII 安全名的场景。关键实践使用Workspace.get_file_path(filename)的场景不需要手动调用ConfinedDir。因为该方法内部已经实现了两层防御见 workspace.pydef get_file_path(self, filename: str) - Path: basename safe_data_filename(filename) # 第一层清洗 try: return self._confined_data.resolve(basename) # 第二层约束 except ValueError: raise ValueError(fPath traversal detected: {filename!r})回归测试 test_confined_dir_migration.py 的test_get_file_path_traversal_sanitized验证了这一协作机制get_file_path(../../etc/passwd)经safe_data_filename清洗为passwd第一层再经ConfinedDir校验第二层最终结果安全落在data/子目录内。identity 目录名同样需要清洗Workspace.__init__通过sanitize_identity_dirname()workspace.py用secure_filename产出安全的单段目录名长度超过 256 或清洗结果为空时抛ValueError随后再用ConfinedDir(self._root, mkdirFalse).resolve(self._safe_id)二次确认构造出的路径没有逃出根目录workspace.py。4. 文件下载 Route安全检查与实际发送必须共用同一路径下载接口必须让安全检查和实际发送使用同一个 resolved path。Data Formulator 的 scratch 下载路由scratch_serveroutes/agents.py示范了标准写法from flask import send_file scratch_jail workspace.confined_scratch try: target scratch_jail.resolve(filename) except ValueError: return jsonify(statuserror, messageAccess denied) return send_file(target)源码中实际实现是通过AppError(ErrorCode.ACCESS_DENIED, Access denied)返回统一错误对应前端错误码体系随后检查target.exists()不存在时抛TABLE_NOT_FOUND。❌ 禁止手写 resolve relative_to 检查已弃用模式# BAD — 手写检查已弃用 target (scratch_dir / filename).resolve() if not target.is_relative_to(scratch_dir.resolve()): return jsonify(statuserror, messageAccess denied)禁止在用户路径上使用send_from_directory(dir, filename)。它会在 Flask 内部再次解析原始filename容易和前置安全检查形成 TOCTOU检查时间与使用时间不一致漏洞——两次解析之间文件名指向可能已经改变。如果文件名来自用户输入并写入Content-Disposition不得直接插值原始字符串。新代码应先建立统一 helper去除 CR/LF、引号和目录组件并为非 ASCII 名称提供安全 fallback 或filename*RFC 5987编码。上传路由的清洗 二次校验组合scratch 上传路由scratch_uploadroutes/agents.py体现了双层防线先用werkzeug.secure_filename清洗原始文件名再拼接内容 hash 生成{base}_{file_hash}{ext}形式的最终文件名最后用scratch_jail.resolve(final_name)二次校验。测试test_scratch_upload_traversal_sanitizedtest_confined_dir_migration.py验证上传名为../../etc/passwd的文件最终写入路径仍位于 scratch 目录内。5. Agent 工具LLM 生成的路径参数一律视为不可信Agent 工具参数由 LLM 生成LLM 输入又来自用户因此路径参数一律视为不可信。入口函数应使用Workspace.confined_*属性获取ConfinedDir实例传入各工具方法见 agent_data_loading_chat.pydef _execute_tool(self, name, args): workspace_jail self.workspace.confined_root scratch_jail self.workspace.confined_scratch if name read_file: return self._tool_read_file(args, workspace_jail) elif name write_file: return self._tool_write_file(args, scratch_jail) elif name list_directory: return self._tool_list_directory(args, workspace_jail) elif name execute_python: return self._tool_execute_python(args) elif name fetch_url: return self._tool_fetch_url(args, scratch_jail) ...具体工具通过ConfinedDir.resolve()获取安全路径def _tool_read_file(self, args, workspace_jail): rel_path args.get(path, ) try: target workspace_jail.resolve(rel_path) except ValueError: return {error: Access denied: path outside workspace} ...从源码看各工具的 jail 分配逻辑agent_data_loading_chat.py_tool_read_file/_tool_list_directory使用workspace_jailconfined_root可读 workspace 内任意文件_tool_write_file使用scratch_jailconfined_scratch并先经_secure_filename清洗_tool_execute_pythonDataFrame 自动保存到scratch_jail.resolve(f{safe_name}.csv)_preview_scratch_files使用workspace.confined_root.resolve(file_path)读取 scratch CSV 构建预览 action。❌ 禁止在工具方法中手写resolve() relative_to()已弃用模式# BAD — 手写检查已弃用 target (workspace_path / rel_path).resolve() try: target.relative_to(workspace_path) except ValueError: return {error: Access denied: path outside workspace}性能与一致性约定不要在工具函数内部反复创建ConfinedDir或反复调用resolve()。在_execute_tool入口创建一次所有工具方法复用同一个实例。对应测试见 test_confined_dir_migration.pyTestAgentToolsUseConfinedProperties_tool_read_file对../../etc/passwd返回含 Access denied 的 error_tool_write_file对../../evil.txt的写入被重定向到 scratch 目录内。6. Data Loader 与宿主文件系统多用户模式必须禁用如果 Loader 的构造参数包含用户可控的本机路径例如root_dir它只能在本地单用户模式使用。原因很直接这类 Loader 直接读取服务器宿主文件系统多用户场景下会成为任意文件读取的后门。必须在 data_loader/init.py 的_enforce_deployment_restrictions()中注册禁用规则def _enforce_deployment_restrictions(): backend os.environ.get(WORKSPACE_BACKEND, local) if backend ! local and your_local_loader in DATA_LOADERS: del DATA_LOADERS[your_local_loader] DISABLED_LOADERS[your_local_loader] ( your_local_loader connector is disabled in multi-user mode (WORKSPACE_BACKEND ! local) )仓库中的参考实现正是如此local_folderLoaderlocal_folder_data_loader.py即规范中提到的当前参考实现与ConfinedDir原始采用者在WORKSPACE_BACKEND ! local时从DATA_LOADERS删除并写入DISABLED_LOADERS附上人类可读的禁用原因data_loader/init.py。该模块顶层同时维护DATA_LOADERS成功导入的 Loader与DISABLED_LOADERS失败或禁用项及提示前端可据此向用户解释缺失原因。相关背景该模块的插件扫描机制同样受此约束——DF_ALLOW_PLUGINS1显式 opt-in 之前只有WORKSPACE_BACKENDlocal的本地单用户模式才允许扫描执行插件目录插件是任意 Python 代码风险更高见 data_loader/init.py。Data Loader 通用开发规范见 3-data-loader-development.md。7. Sandbox 部署多用户模式禁止 not_a_sandbox多用户或云部署中not_a_sandbox会让 LLM 生成的 Python 代码在宿主进程直接执行可能绕过所有路径检查。Data Formulator 的沙箱目录sandbox/包含三种实现docker_sandbox最大隔离、local_sandbox隔离子进程 audit hooks与not_a_sandbox。部署要求WORKSPACE_BACKEND local时允许桌面单用户模式使用not_a_sandboxWORKSPACE_BACKEND ! local时必须使用SANDBOXdocker或SANDBOXlocal新部署模板应默认选择隔离沙箱。应用启动时已有安全检查app.py中的_safety_checks()app.py在检测到multi_user且sandbox not_a_sandbox时输出 critical 级别告警日志提示LLM-generated code can read/write arbitrary files on the server. Set SANDBOXdocker or SANDBOXlocal for production deployments.。注意当前实现是告警而非硬阻断不会阻止启动因此生产部署还必须在部署配置或启动脚本层强制SANDBOXlocal/SANDBOXdocker。CLI 启动参数层面--sandbox的合法取值就是[local, docker]app.py默认值取自环境变量SANDBOX默认local也就是说通过 CLI 参数本身就无法显式选择not_a_sandbox——它只在内部默认分支下出现。8. 测试要求路径安全必须有回归测试兜底新增路径相关代码时至少覆盖以下用例正常相对路径可以访问../、绝对路径、空路径被拒绝symlink escape 被拒绝适用时用真实文件系统测试文件下载 route 使用send_file(resolved_path)多用户部署下宿主文件系统 Loader 被禁用。仓库已有的参考测试均可直接阅读与复用测试文件覆盖内容test_local_folder_loader.py宿主文件系统 Loader 的路径安全test_scratch_serve.pyscratch 下载/上传路由安全test_tool_path_safety.pyAgent 工具路径安全test_local_folder_deployment.py多用户部署禁用规则test_startup_safety.py启动期安全检查含 not_a_sandbox 告警test_confined_dir_extended.pyConfinedDir扩展 APIread_text/write_text/exists/iterdir/rglob/unlinktest_confined_dir_migration.py迁移回归Workspace.confined_*属性、Agent 工具、scratch 路由值得注意的测试细节TestScratchRoutesConfinedMigrationtest_confined_dir_migration.py通过 Flask test client 真实请求/api/agent/workspace/scratch/../../../etc/passwd断言返回 403 与ACCESS_DENIED错误码属于端到端级别的路径穿越验证TestWorkspaceConfinedProperties则逐项断言confined_root/confined_data/confined_scratch分别指向 workspace 根、data/、scratch/子目录并对各自目录发起穿越探测。9. New Module Checklist新增模块自检清单任何新模块在合入前请逐项核对以下清单新模块是否接收用户、LLM、外部存储或 HTTP 传入的路径片段是否使用了ConfinedDir或Workspace.get_file_path()是否避免了Path(root) / user_input裸拼接是否避免了手写resolve() relative_to()/is_relative_to()模式必须用ConfinedDir下载 route 是否用ConfinedDir.resolve()做检查并传给send_file()上传 route 是否同时使用secure_filename()和ConfinedDir.resolve()读取宿主文件系统的 Loader 是否注册了多用户禁用规则多用户部署是否启用了docker或localsandbox10. 已迁移清单手写检查的历史清理规范发布时以下位置已从手写resolve() relative_to()迁移到ConfinedDir可作为新代码的参考范例文件方法迁移方式datalake/workspace.py__init__创建_confined_root/_confined_data/_confined_scratch暴露confined_root/confined_data/confined_scratch属性见 workspace.pydatalake/workspace.pyget_file_pathself._confined_data.resolve(basename)datalake/workspace.py__init__(legacy root)ConfinedDir(root).resolve(safe_id)agent_data_loading_chat.py_execute_toolworkspace.confined_rootworkspace.confined_scratchagent_data_loading_chat.py_tool_read_fileworkspace_jail.resolve(rel_path)agent_data_loading_chat.py_tool_list_directoryworkspace_jail.resolve(rel_path)agent_data_loading_chat.py_tool_write_filescratch_jail.resolve(filename)agent_data_loading_chat.py_tool_execute_pythonworkspace.confined_scratch.resolve(safe_name .csv)agent_data_loading_chat.py_preview_scratch_filesworkspace.confined_root.resolve(file_path)routes/agents.pyscratch_serveworkspace.confined_scratch.resolve(filename)routes/agents.pyscratch_uploadworkspace.confined_scratch.resolve(final_name)cached_azure_blob_workspace.py_cache_pathself._cache_jail.resolve(filename)knowledge/store.pyCRUDConfinedDir(user_home / knowledge / category)agents/reasoning_log.pylogConfinedDir(DATA_FORMULATOR_HOME / agent-logs / date / safe_identity_id)local_folder_data_loader.py全文件已使用ConfinedDir原始采用者补充两处迁移后的实际形态帮助理解复用而非重复创建的原则知识库KnowledgeStore的所有文件 I/O 都经由ConfinedDirknowledge/store.py且对目录深度有额外约束——rules类别只允许平铺文件1 层路径workflows允许一层子目录最多 2 层路径将用户可控的目录结构限制在极小的攻击面上推理日志ReasoningLogger按DATA_FORMULATOR_HOME/agent-logs/date/safe_identity_id/session_id-agent_type.jsonl组织日志reasoning_log.py同样通过ConfinedDir(...).resolve(self._filename, mkdir_parentsTrue)落盘。总结Data Formulator 的路径安全体系可以概括为一句话一个原语ConfinedDir、两层防线文件名清洗 路径约束、三类场景Route / Agent 工具 / 存储与日志、一个自检清单。ConfinedDir将空路径、绝对路径、..段、symlink 逃逸四类攻击统一拦截在resolve()这一唯一关口safe_data_filename与secure_filename分别面向 Unicode 数据文件名与 ASCII 安全名场景完成前置清洗Workspace 通过confined_root/confined_data/confined_scratch三个属性向 Agent 与路由暴露受控的访问入口。对于新的后端代码遵循入口创建一次 jail、统一 resolve、不做裸拼接、不手写 relative_to即可平稳接入这套体系并用仓库中现成的测试文件验证正确性。【免费下载链接】data-formulator Data Formulator is an interactive AI-powered data analysis system makes it easy to connect, explore and visualize data.项目地址: https://gitcode.com/GitHub_Trending/da/data-formulator创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Python运维相关的笔试题及答案 2026/9/14 0:52:22

Python运维相关的笔试题及答案

笔试题及答案项目代码本文档是一套笔试题库, 其中包含详细答案, 题型包含选择题, 解答题以及编程题, 全面覆盖了基础知识点。2023年《网络建设与运维》国赛脚本文件及导出答案视频需要参赛的人员要对最少一种脚本语言做到熟悉, 并且能够领会脚本里和网络有关的指令, 从而迅速地…

阅读更多 →
示波器八大灵魂问题:接地、触发、带宽与采样率深度解析 2026/9/14 0:49:22

示波器八大灵魂问题:接地、触发、带宽与采样率深度解析

1. 这不是说明书,是八个真正能让你“看懂波形”的灵魂拷问示波器不是万用表,它不告诉你“电压是多少”,而是告诉你“电压是怎么变的”。很多人买了示波器,接上探头,屏幕亮了,波形跳了,然后——就…

阅读更多 →
高分辨率示波器如何提升信号完整性分析能力 2026/9/14 0:49:22

高分辨率示波器如何提升信号完整性分析能力

1. 这不是普通示波器,而是工程师口袋里的“信号显微镜”你有没有遇到过这样的场景:调试一个开关电源,纹波看起来不大,但系统偏偏在特定负载下偶发重启;或者排查一段高速SPI通信,逻辑分析仪显示时序“完全正…

阅读更多 →
中小企业 AI 落地平台评测:4 个真实案例 2026/9/14 0:49:22

中小企业 AI 落地平台评测:4 个真实案例

中小企业 AI 落地平台评测:4 个真实案例⚠️ 本文客户案例均做脱敏说明,不指代具体客户。“中小企业 AI 落地平台哪家好?”——这是 5-50 人中小企业老板最常问的问题。 但"好不好"是相对的。有人觉得好用,有人觉得一般…

阅读更多 →
工业 AI 落地:3 个工厂的真实路径 2026/9/14 0:49:22

工业 AI 落地:3 个工厂的真实路径

工业 AI 落地:3 个工厂的真实路径⚠️ 本文客户案例均做脱敏说明,不指代具体客户。“工业 AI 落地”——这是 2026 年制造业老板最关心的话题,没有之一。 但"工业 AI"是个特别容易被夸大的词。真正的工业 AI,不是"…

阅读更多 →
智能体可视化设计用哪家:3 类工具对比 2026/9/14 0:49:22

智能体可视化设计用哪家:3 类工具对比

智能体可视化设计用哪家:3 类工具对比⚠️ 以下对比基于公开资料整理,不构成选型建议。“智能体可视化设计用哪家?”——这是 2026 年中小企业老板 产品经理最常问的问题之一。 "智能体"和"可视化设计"这两个词都很热&a…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞