[控场AI]
· 7 分钟阅读· 3,954 字

LangChain默认配置存10.4倍放大风险,公共聊天机器人如何自救?

LangChain默认配置存10.4倍放大风险,公共聊天机器人如何自救?

LangChain默认配置存在存储放大风险,10.4倍且O(n²)增长,需主动防护。

文章揭示LangChain的create_agent与SqliteSaver默认组合会将不可信输入在磁盘上放大10.4倍,且增长呈二次方。攻击者仅需发送未认证HTTP请求,输入在推理前就被检查点保存,即使LLM失败也会写入。删除会话无法回收空间,只有VACUUM加wal_checkpoint才能释放;磁盘写满会导致SQLITE_FULL崩溃循环。文章提出五条缓解措施:限制请求体大小、修剪消息列表、设置max_page_count和配额、离线VACUUM、监控每线程增长。强调这是设计级风险而非CVE,默认示例无防护,公共聊天机器人应主动配置。

引言:一个被忽视的设计级风险

如果你正在用 LangChain 的 create_agent 和 SqliteSaver 搭建聊天机器人,并且直接照搬了官方文档的示例,那么你的应用可能正暴露在一个严重的存储放大风险之下。一位红队研究员在 Reddit 上发布了一份 PSA(公共安全公告),指出这种默认配置会将不可信输入在磁盘上放大 10.4 倍,而且增长呈二次方(O(n²))——这意味着对话越深,磁盘占用越恐怖。

这不是一个 CVE,也不是代码 bug,而是设计层面的资源耗尽风险。每个允许用户控制消息大小的公共聊天机器人都会继承这个风险,除非你主动采取缓解措施。

reddit source: PSA: the docs-default create_agent + SqliteSaver pattern amplifies untrusted input 10.4× on disk. No

实验复现:10.4倍放大的真相

研究员在本地搭建了受害者应用,使用 LangChain 1.4.7、langchain-core 1.7.1、langgraph 1.2.18、langgraph-checkpoint-sqlite 3.1.2 和 Python 3.14.7,完全按照文档中的模式连接。攻击模拟非常简单:发送 6 个会话,每个会话 5 条消息,每条消息 600 KB 的输入,总计 18.4 MB 的输入数据。结果磁盘上写入了 192.4 MB——放大 10.4 倍。

更糟糕的是增长模式。因为检查点器(checkpointer)在每次超级步(superstep)都会写入整个对话状态,所以第 k 条消息会持久化大约 k 倍于自身负载的数据。在一个 10 条消息、每条 600 KB 的会话中,每条消息的磁盘增量从约 4 MB 增长到约 13 MB。这种二次方增长意味着,攻击者只需发送更长的对话,就能以指数级消耗磁盘。

攻击成本极低:每条消息只需一个未认证的 HTTP 请求。输入在推理之前就被检查点保存,因此无论 LLM 是否回答、超时或报错,持久化都会发生。这意味着即使你的 LLM 服务不可用,攻击者仍然可以持续写入磁盘。

检查点器与超级步机制:LangGraph 的 checkpointer 负责在每次状态变更时持久化整个图状态。超级步(superstep)是图执行的基本单位,每个超级步结束时都会生成一个检查点。SqliteSaver 将每个检查点作为新行插入,而不是覆盖旧状态,因此历史状态全部保留。这意味着随着对话轮次增加,每个新检查点都包含到目前为止的完整消息列表,导致磁盘写入量随消息数量线性累积,而总存储量呈二次方增长。这种设计是为了支持时间旅行和故障恢复,但在多用户场景下会放大存储开销。

为什么删除会话也救不了你?

你可能会想:只要定期删除旧会话(比如 TTL 线程删除器)就能控制磁盘。但实验显示,DELETE 操作回收 0 字节。freelist_count 上升,但 page_count 不变,SQLite 文件不会缩小。只有执行 VACUUM 并配合 PRAGMA wal_checkpoint(TRUNCATE) 才能真正释放空间(实验室中回收了 37.9 MB)。

如果你运行 TTL 删除器,却以为磁盘会被释放,那么监控会欺骗你。更危险的是,当磁盘被写满时,SQLite 返回 SQLITE_FULL 错误,会导致永久性的崩溃循环,且无法通过你自己的 API 恢复。

SQLite 的 freelist 与 VACUUM 机制:SQLite 删除行时,并不会立即缩小数据库文件,而是将释放的页加入 freelist 供后续插入复用。freelist_count 表示空闲页数量,page_count 是总页数。删除操作只增加 freelist_count,page_count 不变,因此文件大小不缩减。VACUUM 命令会重建数据库文件,回收空闲页并整理碎片,从而真正减小文件。在 WAL 模式下,还需执行 PRAGMA wal_checkpoint(TRUNCATE) 将 WAL 文件内容写回主数据库并截断 WAL,否则空间仍被占用。监控文件大小时必须在这两步之后测量,否则会误判。

未复现的风险:好消息与可信度

研究员也验证了从之前 n8n 实验中继承的某些攻击向量是否生效。在 n8n 中,加载所有被淹没会话的完整状态曾导致堆内存溢出(heap-OOM)。但在 LangGraph 中,加载约 24.6 MB 的攻击者文本,经过 3 轮测试,VmHWM 稳定在 235 MB,没有发生内存放大。LangGraph 的 msgpack/pydantic 解析没有像 n8n 的 includeData=true 那样放大内存。

另一个未复现的是 n8n 的“空成功”模式(LLM 将所有 token 消耗在推理上,返回 content='' 但标记为成功)。在设置 max_tokens=4096 时,实验得到的是干净的 LLM 超时(2–16 KB),而不是虚假成功。这些未复现的风险反而增加了实验的可信度,说明研究员没有夸大其词。

VmHWM 与内存放大:VmHWM(Virtual Memory High Water Mark)是进程峰值常驻内存集(RSS)的指标,记录进程生命周期内达到的最大物理内存使用量。在评估内存放大风险时,VmHWM 比当前 RSS 更能反映最坏情况。实验中加载 24.6 MB 攻击者文本后 VmHWM 稳定在 235 MB,说明 LangGraph 的 msgpack/pydantic 解析没有像 n8n 的 includeData=true 那样将数据膨胀为多个副本。msgpack 是一种紧凑的二进制序列化格式,pydantic 用于数据验证,两者结合通常不会导致显著内存放大,这与 n8n 的 JSON 深拷贝行为形成对比。

缓解措施:默认配置下无一生效

研究员列出了五条真正有效的缓解措施,并强调这些在默认配置下都不存在:

  1. 在代理或应用层限制请求体大小——这是最有效的控制。检查点器会存储请求携带的任何内容,所以限制输入大小直接切断攻击。
  2. 在调用前修剪消息列表(滚动窗口)——将 O(n²) 转化为 O(n)。只保留最近若干条消息,避免历史累积。
  3. 设置 PRAGMA max_page_count 作为磁盘保险丝,并对状态目录施加文件系统配额。
  4. 安排离线 VACUUM(注意 WAL 陷阱:必须在 wal_checkpoint(TRUNCATE) 之后测量,否则监控会撒谎)。
  5. 监控每个线程的数据库增长与请求数的对比——二次方增长非常容易触发警报。

这些措施都是配置或架构级别的,不需要修改 LangChain 源码。但问题在于,默认文档示例没有包含任何一条。

PRAGMA max_page_count 与文件系统配额:PRAGMA max_page_count 设置 SQLite 数据库允许的最大页数,超过限制时写入操作会返回 SQLITE_FULL 错误,从而防止磁盘被无限占用。但该限制只作用于单个数据库文件,攻击者仍可能通过创建多个数据库或利用 WAL 文件绕过。因此需要结合文件系统配额(如 Linux 的 quota 或 cgroup 磁盘限制)对状态目录施加硬性上限。此外,离线 VACUUM 应安排在低峰期,并确保在 wal_checkpoint(TRUNCATE) 之后进行,否则 WAL 文件可能继续增长。监控每个线程的数据库增长与请求数对比,可以及早发现异常模式。

这不是 CVE,但每个公共机器人都该警惕

研究员明确表示,这不是 CVE,也不是代码漏洞。全状态检查点是检查点器的预期设计。问题在于,当不可信用户控制消息大小时,部署会继承这种设计级资源耗尽风险。

对于任何面向公众的聊天机器人,尤其是那些允许用户发送长消息或上传大段文本的应用,这个风险是真实存在的。攻击者不需要认证,只需发送 HTTP 请求就能逐渐耗尽磁盘。而磁盘写满后的崩溃循环可能让服务彻底不可用。

目前,社区正在讨论如何在生产环境中限制检查点增长,比如使用滚动窗口检查点器、PostgreSQL + 配额,或者新版 LangGraph 中是否有内置方案。如果你有相关经验,不妨参与讨论。

结语:设计安全需要主动配置

这个案例提醒我们,框架的默认配置往往为了演示方便而牺牲了安全性。作为开发者,我们需要理解底层机制,主动添加防护措施。存储放大只是众多资源耗尽风险中的一种,但它的二次方增长特性和低攻击成本使其尤为危险。检查你的聊天机器人,限制输入大小,修剪消息历史,监控磁盘增长——这些简单的步骤可能避免一场灾难。

reddit source: PSA: the docs-default create_agent + SqliteSaver pattern amplifies untrusted input 10.4× on disk. No

#LangChain安全#SqliteSaver风险#存储放大攻击#AI代理资源耗尽#聊天机器人安全#LangGraph检查点#设计级漏洞
分享:

相关推荐