Skip to content

AI 编排平台的跨阶段代码执行风险 ​

摘要 ​

Peyton Kennedy 对 NocoBase、Flowise、Langflow、Dify、Activepieces、Kestra 和 Apache Airflow 七款平台开展源码审查,报告 13 项安全发现。问题分布在四类机制:表达式或模板隔离可被绕过;LLM 输出直接进入 eval 或解释器;沙箱只覆盖验证阶段却没有覆盖实际运行阶段;产品按设计允许工作流执行宿主命令,却缺少与外部触发器相匹配的授权控制。

材料包含 NocoBase、Flowise、Langflow、Activepieces 和 Airflow 的公开 CVE/GHSA,也包含 Dify、Kestra 等项目的设计争议或修复。本文不把 13 项问题都称为沙箱逃逸:部分是认证后模板注入,部分是未认证数据流,另一些需要管理员主动启用代码节点。共同风险是平台在不同阶段对同一段代码采用了不同信任假设。

审计方法:先找输入,再追到真正执行点 ​

作者没有从“搜索 eval”开始,而是对七个平台重复执行五步审计:

  1. 列出 webhook、API、表单、LLM 输出、变量和触发器等输入面。
  2. 沿模板、动态编译、ProcessBuilder、subprocess、解释器启动和沙箱引导代码追踪到执行点,尤其检查执行顺序。
  3. 对比文档中的威胁模型与代码中的实际信任边界。
  4. 确认能够到达执行点的最低权限主体,不能只看工作流作者权限。
  5. 检查官方文档是否把危险拼接模式当作正常示例传播。

七个平台跨越 Java、Python、TypeScript/Node 和 Go,沙箱方案也从 SES Compartment、Pyodide/WASM、seccomp/chroot 到 V8 isolate 不等。共同问题不是某个语言函数,而是低权限输入与高权限执行阶段之间缺少同一套控制。

13 项发现总表 ​

平台发现最低权限/入口材料中的标识或处置
NocoBasevariables:resolve 绕过 SES 并到达 SQL/系统任意已认证用户GHSA-42wx-r3jw-6c5h,9.9
NocoBasecompileTemplate 存储型 XSSbuilder 写入、其他用户触发8.7,CWE-79/95
NocoBasecheckSQL 漏掉 update 路径collection 管理权限CVE-2026-41641,7.2
NocoBase递归 CTE 主键拼接注入可在树表创建记录CVE-2026-41640,7.5
FlowisePython 校验器绕过并执行代码未认证 prediction 入口GHSA-w7x8-q2gp-5cgg,9.3
LangflowSmart Transform/Lambda Filter 使用 eval认证后;旧配置可放宽GHSA-9fpm-3445-2vx4
Langflow自定义组件验证阶段 exec认证后;旧配置可放宽GHSA-8xrc-2jr4-78j7
LangflowMCP Server 配置命令注入已认证GHSA-w794-rj3p-xv45
Difypreload 在 seccomp/setuid 前以 root 运行有效 API 密钥且启用 preload项目按设计处置,后默认关闭
ActivepiecesimportFresh 在 V8 隔离前执行顶层代码已认证代码步骤作者GHSA-gr3h-c2j7-r52g
Activepieces代码步骤名称进入 shell已认证GHSA-3pfv-m69p-5fv5
Kestrainterpreter/beforeCommands 进入宿主命令作者;可被未认证 webhook 触发两项 9.8,维护者认为按设计
Apache Airflowdag_run.conf 经 Jinja2 进入 BashDAG 触发权限CVE-2026-30898,8.8

这个表不能当作实时受影响版本清单。它保留了前置权限和处置差异,以避免把“设计上允许代码执行”和“未经授权远程代码执行”写成同一件事。

NocoBase:SES 隔离外泄的是活对象能力 ​

variables:resolve 的表面过滤 ​

NocoBase 允许客户端解析模板变量。研究版本把表达式放入 SES Compartment,同时用字符串匹配拒绝 ctx. 和 ctx[ 等访问。作者通过给上下文起别名、解构或间接属性访问绕过词法检查。这里不是 SES 自身被破解,而是应用先把高权限对象放进了隔离域,再用容易绕过的字符串规则限制访问。

Proxy 没有消除对象能力 ​

应用把内部 koaCtx 包装为 Proxy,使用 Reflect.has 和 Reflect.get 暴露属性。TypeScript 的 private 只影响编译期类型检查,运行时对象仍可沿 ctx.app.db 等路径访问 Sequelize 实例。材料还指出 SES 的 lockdown() 在相关代码中被注释,进一步扩大可见的 JavaScript 内建能力。

受控链条为:低权限登录用户调用 /api/variables:resolve,获得数据库运行时对象,调用 Sequelize 查询用户和密码哈希;若 PostgreSQL 以高权限运行,还可能通过数据库功能进一步执行系统命令。本站不提供原始模板表达式或数据库命令。

三项相邻问题 ​

  • compileTemplate 通过 new Function('$root', ...) 编译表达式,攻击者可把内容存入标题,其他用户渲染后触发脚本,从而窃取令牌或继续写入恶意内容。
  • SQL 校验在 create/execute 路径生效,却漏掉 update;攻击者可通过更新查询配置读取不应访问的数据。
  • 递归树查询把字符串主键拼入 WHERE id IN (...),形成 SQL 注入。若数据库以 PostgreSQL 超级用户运行,影响会进一步扩大。

四项 NocoBase 发现说明:表达式、模板和 SQL 是不同解释器,不能由一套字符串规则统一保证安全。

Flowise:38 条正则没有覆盖预导入对象的能力 ​

Flowise 的 Python 执行节点使用约 38 条正则阻止 import、eval、os 等危险内容。旧版本曾修复 import 别名绕过,但 3.1.0–3.1.2 的执行作用域已经预导入 pandas 和 NumPy。即使用户代码不出现 import,仍可通过这些对象的模块和属性图取得强大运行时能力;字符串也可由字符编码和动态属性访问在运行时重新组合。

材料把该问题与默认可达的 /api/v1/prediction/<uuid> 连接起来:外部请求影响提示词,LLM 输出 Python,正则判定通过后在服务进程可访问的环境中运行。幻灯片把方案标为 Pyodide/WASM,但作者的结论是执行环境仍能触及服务器文件、网络或数据集,不能把运行器名称本身当作安全隔离证明。

这里的修复目标不是继续增加正则,而是把 LLM 输出编译为受限操作,或让真正的生产执行进程具备可验证的系统调用隔离。

Langflow:三个不同的动态执行入口 ​

Smart Transform 与 Lambda Filter ​

验证器只确认文本以 lambda 开头且包含冒号,随后对整段表达式执行 eval。一个自然语言消息可以让 LLM 生成语法合法、能力却远超数据转换的 lambda。只检查外形不能限制函数体访问的对象和副作用。

自定义组件验证就是执行 ​

POST /api/v1/custom_component 接收 Python 组件代码。validate.py 在验证类结构时执行 exec(compiled_class, ...);类定义、装饰器或初始化相关代码因此能在“验证”阶段运行。将接口命名为 validate 不会改变它的执行语义。

MCP Server 配置进入 shell ​

MCP 配置将 command 和 args 用空格连接,再构造 bash -c "exec ..."。参数一旦来自 /api/v2/mcp/servers/{name} 等配置接口,数组边界就会被丢失,shell 元字符重新获得解释能力。正确实现应直接使用参数数组启动进程,不经过 shell 字符串。

Dify:沙箱本身有效,但启用顺序错误 ​

Dify Sandbox 的用户代码阶段包含 seccomp、chroot、降权和 capability 限制。问题出现在 prescript.py 的启动顺序:预加载模板占位符 preload 在调用 DifySeccomp(uid, gid, enable_network) 之前运行,此时进程仍是 root。相关镜像为 dify-sandbox:0.2.15,API 可在启用 preload 时接收预加载内容。

研究者在自有环境中读取了 root 才能访问的文件,并指出共享路径可用于跨运行持久化:若预加载阶段写入 Python 每次都会加载的 .so,后续任务即使进入沙箱仍会加载已污染库。材料还显示网络代理是可选控制;若允许网络,原始 TCP 可能绕过预期代理访问内部 API 和插件服务。

维护者的立场是 preload 只用于可信代码、需要操作员主动配置,且此前已知;后续 PR 将 preload 默认关闭。本文因此将它记录为高风险配置与执行顺序缺陷,不称为所有默认部署均可未认证逃逸。

Activepieces:隔离发生在模块加载之后 ​

Activepieces 声明 SANDBOX_CODE_ONLY 时,开发者预期用户代码进入 V8 isolate。实际路径先调用 importFresh()/require 编译并加载 index.js,模块顶层代码在宿主 Node/Bun 环境中执行;只有导出的异步函数随后进入隔离。攻击者把副作用放在顶层即可读取宿主 Secret,例如加密密钥和 JWT 配置。

另一项问题来自代码步骤名称。名称仅用 z.string() 验证,随后拼入 bun build ${path}/index.ts 并交给 /bin/sh -c。文本“只是名称”并不意味着它不会进入命令解释器。

Kestra:当平台按设计执行命令,触发权限必须等同代码权限 ​

Kestra 的 interpreter 是动态 Pebble 属性,最终交给 ProcessBuilder.command();beforeCommands 则把模板渲染后的多项内容连接成一个字符串,再由 /bin/sh -c 执行。维护者将两项报告关闭为“预期功能”,因为工作流作者本来就可以执行代码。

研究者随后演示了信任边界错配:作者发布一个包含 webhook 触发器和 beforeCommands 模板的工作流,webhook 默认可以未认证调用。外部请求体中的字段被拼入 shell,于是“有权触发工作流”的主体继承了作者的宿主代码执行权限。问题不一定要通过删除命令节点修复,更直接的控制是让触发器认证、参数类型和运行权限与代码能力一致。

Apache Airflow:危险文档示例成为供应链 ​

受影响示例把 Jinja2 表达式 dag_run.conf['msg'] 直接放入 BashOperator.bash_command。Jinja2 先展开用户可控配置,subprocess 再把结果交给 shell。拥有 DAG 触发权限但没有编辑权限的用户因此可以注入命令。

文档在不同位置给出矛盾信号:Operator 文档存在警告,但核心示例和 docstring 仍展示危险拼接。CVE-2026-30898 的修复在 Airflow 3.2.0 中把用户值放入环境变量,再让固定命令引用该变量,使数据不再成为 shell 语法。

五种重复出现的失效模式 ​

研究将 13 项发现归并为一个从偶发到设计性更强的谱系:

  1. 开发速度超过安全审查,代表 NocoBase 多条相邻路径。
  2. LLM 输出被直接视为代码,代表 Flowise 与 Langflow。
  3. 隔离顺序错误,代表 Dify preload 与 Activepieces 顶层加载。
  4. 文档或产品假定“可信作者”,但触发器权限更低,代表 Kestra 和 Airflow。
  5. 文档把危险模式当作标准用法传播,代表 Airflow 示例。

去武器化 PoC:跨阶段控制一致性夹具 ​

以下测试不针对真实产品,也不包含 shell、反射、SQL 或沙箱逃逸语句:

text
fixture = {
  "source": "unauthenticated_mock_webhook",
  "value": "DRY_RUN_LITERAL",
  "expected_type": "string"
}

preview = preview_with_policy(fixture, policy="v3")
saved = save_without_interpreting(fixture)
production = run_with_record_only_executor(saved, policy="v3")

assert preview.policy_digest == production.policy_digest
assert production.argv == ["mock-tool", "DRY_RUN_LITERAL"]
assert production.used_shell == false
assert production.host_side_effects == []

对每个平台还应生成一张来源到执行点清单:输入主体、认证方式、每次模板渲染、实际解释器、沙箱启用时刻、子进程、网络、共享路径和 Secret。只检查 HTTP 返回成功或预览页面没有报错,无法证明生产 worker 安全。

防护措施与验证方法 ​

  • 把代码视为独立制品:生成、预览、保存和生产执行必须使用同一解析器、策略摘要和隔离配置。
  • LLM 输出不能直接进入 eval、exec、shell、动态导入或模板编译;允许操作应映射到窄接口和类型化参数。
  • 沙箱必须在解析或加载不可信模块之前启用,并覆盖实际进程、顶层代码和所有子进程。
  • 保留参数数组边界,禁止把 command + args 拼成 shell 字符串;变量进入 SQL 时使用参数绑定。
  • webhook、工作流编辑、运行、代码节点和 Secret 读取分别授权。对含任意代码能力的工作流,触发权限等同代码执行权限。
  • 共享运行路径必须按租户和任务隔离,运行前校验解释器、动态库和启动脚本完整性。
  • 把官方示例纳入安全测试。文档中的每段模板、Bash 或 webhook 示例都应通过静态和动态负向用例。

关键结果与实际影响 ​

七个平台都至少出现一种“声明控制与实际执行路径不一致”的情况,但前置权限差异很大:Flowise 演示可从未认证 prediction 入口到达执行;Dify 需要启用 preload;Kestra 两项被视为预期代码能力;Airflow 要求已有 DAG 的触发权限。判断部署风险时必须结合版本、节点类型、触发器认证、worker 身份、网络和共享文件系统,不能只看 CVSS。

局限与待验证问题 ​

证据来自一个研究团队的会议材料,13 项发现的补丁状态、默认配置和厂商定级并不一致。平台更新频繁,本文不是实时漏洞清单。仍需按每个修复版本复测生产 worker,并评估统一隔离、强制认证和关闭代码节点对现有工作流兼容性的影响。

参考链接 ​