Skip to content

Vanna AI vanna.ask 执行 Plotly 代码

一句话

据 NVD,Vanna AI vanna.ask 的可视化流程曾把 LLM 生成的 Plotly 代码交给 exec,属于 ToLO-Exec

怎么读这个案例

这个案例说明 ToLO 不只出现在 agent 或数学链里数据分析应用也可能让 LLM 生成代码:

  1. 先生成 SQL 查数据(ToLO-SQL 候选)
  2. 拿到 dataframe
  3. 再生成 Plotly 代码画图(ToLO-Exec 候选)
  4. exec 执行 plotly 代码

用户看到的是”自动可视化”,程序内部却可能执行模型生成的 Python 代码

这个案例揭示了 ToLO 的一个普遍现象:一个产品功能可能涉及多个 ToLO 子类,审计时必须按”每条 source-to-sink 路径”为单位,而不是”每个功能”。

影响组件

影响点是 src/vanna/base/base.pyvanna.askvisualize 路径

公开来源确认:

  • CVE 编号 CVE-2024-5826
  • 分配 CWE-94(Code Injection)
  • 涉及 execgenerate_plotly_code

未给出可闭合的受影响版本范围;GitHub v0.6.2 release 也未声明安全修复。所以 affected_versionsfixed_in 都保持 unknown

ToLO 路径

整个 vanna.ask 路径上其实有两个 ToLO 候选:

路径 1: ToLO-SQL(text-to-SQL 部分)

内容
SourceS_LLM^frameworkgenerate_sql 返回的 SQL
Transform框架直接拿 SQL 去查数据库
Sinkconnection.execute(sql) 或类似
Guard 缺失取决于数据库账号权限是否受限

(详细 ToLO-SQLGraphCypherQAChain 案例。)

路径 2: ToLO-Exec(可视化部分,本 CVE 主体)

内容
SourceS_LLM^frameworkgenerate_plotly_code 从 LLM 返回 Python/Plotly 代码
Transformask 在生成 SQL、查询并得到 df 后,把代码传给绘图 helper
Sinkget_plotly_figure 中的 exec(plotly_code, ...)(ToLO-Exec / CWE-94)
Guard 缺失缺少与代码执行匹配的 sandbox、C_SAFE^allowlistC_SAFE^capability

这条路径和数学链案例相似,但业务语义不同:LLM 不是生成数学表达式,而是生成可视化代码。ToLO 视角下,两者都属于”模型输出被提升为可执行程序”

教学骨架

# Vanna AI ask 简化骨架(修复前)
def ask(question: str):
# Step 1: 生成 SQL
sql = generate_sql(question) # ← S_LLM (路径 1)
df = connection.execute(sql).fetchall()
# Step 2: 拿 df 给 LLM 生成 plotly 代码
plotly_code = generate_plotly_code(df, question) # ← S_LLM (路径 2)
# Step 3: 执行 plotly 代码
fig = get_plotly_figure(plotly_code, df)
# 内部:
# local_vars = {"df": df, "px": plotly.express, "go": plotly.graph_objects}
# exec(plotly_code, local_vars) # ← ToLO-Exec sink
# return local_vars["fig"]
return fig

如果 prompt 被攻击者诱导,plotly_code 可能包含 __import__('os').system(...) 等任意 Python。

修复方式

公开 release 与 issue 只显示 hardening guide 线索,未核验到明确的 fixed version 或移除 exec 的补丁

因此本站暂不写入修复结论,fixed_in 保持 unknown

在版本和补丁未核验前,本站只记录公开来源确认的路径与风险,不推断上游已经彻底修复。后续若找到明确 commit,应补充修复属于:

  • 移除 sink(完全不让 LLM 生成代码,改用图表类型 schema)
  • 沙箱执行(exec 包在 docker / RestrictedPython 里)
  • 能力门控(allow_dangerous_code=False 默认)
  • 配置提示(只警告,不强制)中的哪一种。

一个理想化的修复方向

把 LLM 的输出从”任意 plotly 代码”收窄到结构化”图表规格”:

from pydantic import BaseModel
from typing import Literal
class ChartSpec(BaseModel):
chart_type: Literal["line", "bar", "scatter", "pie"]
x_column: str
y_column: str
title: str
def generate_chart_spec(df, question) -> ChartSpec:
# 让 LLM 输出 schema 化的 spec
return llm.with_structured_output(ChartSpec).invoke(...)
def render(df, spec: ChartSpec):
# 应用代码自己根据 spec 构造图表
if spec.x_column not in df.columns or spec.y_column not in df.columns:
raise ValueError("invalid column")
if spec.chart_type == "line":
return px.line(df, x=spec.x_column, y=spec.y_column, title=spec.title)
...

这样没有任何 exec,LLM 只决定”画哪种图、用哪列”,而怎么画由开发者代码硬编码。这就是 C_SAFE^schema + C_SAFE^allowlist 组合的典型应用。

对 ToLO 分类的启发

该案例支持 ToLO-Exec taxonomy:危险点不是传统 HTTP 参数直接进入 exec,而是应用把 LLM 生成的”内部可视化代码”当成可信程序片段执行无需新增 sink 子类

它还说明 ToLO 不局限于”agent 会调用 shell”这类显眼功能数据分析、BI、text-to-SQL、自动制图等看似高层的生产力功能,也可能在内部把 LLM 输出送进执行器。

推论:任何 BI / 数据分析产品都应该被静态扫描。流行项目如 PandasAI、Vanna、DataLine、Code Interpreter 类自动可视化都属于高风险面。

学习者要带走什么

  1. “生成图表代码”也是代码执行,不因为业务场景是可视化就安全。
  2. 如果修复来源不清楚,案例页应保留 pending,不要推断 fixed version。这是研究诚信。
  3. 数据分析工具常同时涉及 SQL sink 和 code execution sink,应按路径分别分析,不要混在一起。
  4. 理想修复是把 LLM 从”写代码”降级到”填 spec”,这是 C_SAFE^schema + allowlist 的经典组合。

读完检查

如果一个 BI 工具让 LLM 生成 Plotly / Python 代码,你会建议哪些防御?至少包括:

  • 禁用任意 exec,改用结构化 chart spec(C_SAFE^schema)
  • 列名、表名 allowlist(C_SAFE^allowlist)
  • 如果一定要执行代码,用受限图表 DSL(自定义 mini 求值器)而非 Python
  • 隔离执行环境(C_SAFE^capability:容器、subprocess、RestrictedPython)
  • 限制可访问变量、禁用文件 / 网络访问
  • 记录人工确认(对高敏感图表)

至少前三条是工程上能做的最小集。

公开来源

下一步阅读