LlamaIndex Memory 为何不校验存储行完整性

👁 77 次阅读

LlamaIndex Memory 为何不校验存储行完整性

来源:https://github.com/run-llama/llama_index/issues/23246

现象描述

在 llama-index-core 0.14.24、SQLite via aiosqlite、macOS arm64、Python 3.12 环境下,使用公开 MIT 测试套件 agmi(https://github.com/tech4biz-yasha/agmi ,行名 llamaindex-memory-sqlite)进行测量。方法是通过 Memory.aput_messages() 写入一个会话,然后直接对 llama_index_memory 表执行一次编辑(不使用任何密钥),再在同一数据库上打开全新的 Memory,通过 Memory.aget() 读取并记录库的行为。 结果是八种存储层编辑全部被接受,也就是说 aget() 返回时不抛出异常,被编辑过的历史直接成为 agent 的上下文:
  • T1:某条消息 data 内的文本被修改
  • T2:最新消息被删除,agent 从更早的状态继续
  • T3:中间某条消息被移除
  • T4:两条消息被交换顺序
  • T5:攻击者插入一条新消息
  • T6:另一个会话的消息被复制到本会话的行上,key 保持不变(用户 B 的消息被当作 用户 A 的消息提供)
  • T7:同一会话中较旧的消息被复制到最新位置
  • T8:行的 timestamp 被重写,文本未动
另外,重写 key 会把一条消息移入另一个会话的上下文,把 status 设为 archived 会让它从上下文中静默消失;两者都不会抛出异常。复现与固定测试见 https://github.com/tech4biz-yasha/agmi/blob/main/tests/test_llamaindex_memory.py ,每种编辑的解释见 https://agentmemoryintegrity.org/edits.html 。

背景原因

问题提出者认为,存储层可能假设数据库可信,但对那些记忆数据库被共享、被备份或可被其他服务访问的用户来说,有两件事有帮助:一是在 Memory 文档中注明行不带完整性保护;二是可选地对每行做基于密钥的 MAC,覆盖 key + position + status + data,并在 aget() 中校验。 其理由是:任何能写入记忆数据库的人(共享数据库、备份恢复、其他服务)都能修改、删除、重排、伪造或在会话间移动消息,而 agent 会把结果当作自己的历史。当前文档中没有任何地方说明该存储不带完整性保护,因此用户会误以为有。 维护者 logan-markewich 对此表达了不同意见:任何写入数据库的东西都遵循这一常见事实,所有数据库数据都是如此。问题提出者回应,测量所问的并非“写入权限意味着数据可被改动”,而是“数据被改动时,agent 在据此行动之前是否察觉”。这是防篡改证据(tamper evidence),不是防篡改(tamper prevention),和 git 提交、签名审计日志、证书透明日志在同样可写的存储上提供的性质一样。对 agent 记忆而言,这比普通数据更重要,因为这些行构成 agent 的上下文。 参与者 sattyamjjain 认为双方都有道理:MAC 无法阻止有写权限的人,而非恶意的情形确实存在,例如备份恢复把历史回滚,或错误的会话键把消息移到另一个用户。今天这些都没有被发现。因此文档说明是成本低、清晰的收益:行不带完整性保护,读取时按原样信任表。 讨论中还补充了一个排序细节:读取按 (timestamp, id) 且 status == ACTIVE,这正是重写 timestamp 会重排上下文、翻转 status 会让消息静默消失的原因。该参与者指出这与其此前并发修复所追查的是同一类问题。

排查或量化方法

据 issue 讨论,定位这一问题可复用公开测试套件的方式:
  • 通过 Memory.aput_messages() 播种一个会话
  • 直接对 llama_index_memory 表执行编辑,不使用任何密钥
  • 在同一数据库上打开全新的 Memory
  • 通过 Memory.aget() 读取,记录返回值或异常
判断标准是 aget() 是否在返回前发现问题。按测量结果,八种编辑都不会触发异常。 据 issue 讨论,读取路径的关键行为是:行以纯数据形式存储,aget() 按原样信任表;读取返回 status == ACTIVE 且按 (timestamp, id) 排序的行。因此重写 timestamp 会重排历史,翻转 status 会让消息从上下文消失。

完整解决方案

该 issue 当前状态为 open。剔除噪音后共有 10 条实质评论,官方尚未合入完整性校验方案,因此以下内容为社区进展与变通路径,而非官方已发布能力。 讨论落地的方向是:文档说明放在核心(core),任何 MAC/校验作为核心之外的 wrapper 实现。据 issue 讨论,贡献者 manav8826 计划在 Memory 指南的 “Remote Memory” 章节(docs/.../deploying/agents/memory.mdx)添加简短说明,覆盖三点:
  • 行以纯数据形式存储,不带完整性保护;aget() 按原样信任表
  • 读取返回 status == ACTIVE 的行
  • 上述排序行为带来的后果
该 PR 为 #23312(据 issue 讨论)。按 GitHub API 查询到的信息,issue 仍为 open,代码是否合入主干需以 PR #23312 的实际状态为准。 另一名参与者 chenhz01 在讨论中给出一个最小 sign_row/verify_row 思路,可在自定义 aget 中调用,在篡改行成为上下文之前将其拒绝:对 key+position+status+data 做逐行密钥 MAC,并且 SECRET 应按会话派生而非硬编码。据 issue 讨论,问题提出者认可这一形态,并提示一个缺口:逐行 MAC 只能证明存活的行是真实的,无法发现已被删除的行。agmi 参考存储还提交前一条记录的 tag,这正是捕获尾部截断(T2)和中间删除的方式。 需要强调,MAC 方案本身有限:它不阻止有写权限的人,只提供被改动后的可见性。

参数或对比表格

编辑类型操作内容aget() 是否抛异常后果
T1修改某条消息 data 内文本否编辑后的历史成为上下文
T2删除最新消息否agent 从更早状态继续
T3移除中间一条消息否历史出现缺口而不自知
T4交换两条消息顺序否上下文顺序被改写
T5插入一条新消息否伪造内容进入上下文
T6复制另一会话消息,key 不变否用户 B 的消息当作 用户 A 的消息
T7将较旧消息复制到最新位置否回滚式重放被接受
T8重写 timestamp,文本不动否因按 (timestamp, id) 排序而重排历史
重写 key移动消息到另一会话否消息进入其他会话上下文
设 status 为 archived归档某行否该消息从上下文静默消失
表中的“是否抛异常”一列对应测量结果:全部为否。

常见错误排查

以下排查点仅依据 issue 讨论中已出现的描述,官方尚无实质确认:
  • 以为记忆库自带完整性保护。文档此前未说明行不带保护,用户默认它会校验。当前应在应用中假设 aget() 对表内容没有任何验证。
  • 以为 MAC 能阻止篡改。据 issue 讨论,MAC 不阻止有写权限的人,只提供被改动后的检测;把它当作防篡改手段会误判威胁模型。
  • 以为逐行 MAC 足以覆盖所有情况。据 issue 讨论,逐行校验无法发现已被删除的行,需要额外提交前一条记录的 tag 才能捕获尾部截断和中间删除。
  • 遇到历史顺序异常或消息消失时,先检查 timestamp 与 status。读取按 (timestamp, id) 且 status == ACTIVE,重写时间戳或翻转状态就会改变上下文,而不会报错。
  • 会话串数据时检查 key。重写 key 会把消息移入另一会话,aget() 不会因此报错。

实践建议

  • 对记忆库被共享、备份或可被其他服务访问的部署,不要把数据库内容当作可信历史。
  • 在文档说明落地前,按 issue 讨论的做法,在应用层或基础设施层处理完整性:可参考 agmi 参考存储(agmi/adapters/reference_atrest.py)的最小实现;据 issue 讨论,该实现对八种篡改类型全部拒绝。
  • 若自行实现逐行校验,SECRET 按会话派生,不要硬编码;覆盖字段为 key+position+status+data。
  • 若需要覆盖删除类篡改,除逐行 MAC 外,还需提交前一条记录的 tag,以捕获尾部截断和中间删除。
  • 关注 PR #23312 的状态以确认文档说明是否合入;在合入前,可先按上述三点撰写内部说明。
  • 数据库写权限本身带来的风险超出本 issue 范围,例如非恶意备份回滚、错误会话键导致消息串用户,这些情形当前同样不会被发现。

FAQ

问:LlamaIndex Memory 为什么不校验存储行完整性? 据 issue 讨论,存储层可能假设数据库可信。维护者认为写权限意味着数据可被改动,这是数据库的常见事实;问题提出者区分了防篡改与防篡改证据,主张读取时应能察觉改动。该 issue 仍为 open,官方尚未合入校验方案。 问:哪些编辑类型会被 aget() 接受而不报错? 测量结果显示八种编辑类型全部被接受:改文本、删最新、删中间、交换顺序、插入新消息、跨会话复制、旧消息覆盖最新、重写 timestamp。另外重写 key 和设置 status 为 archived 也不会报错。 问:逐行 MAC 是完整解法吗? 据 issue 讨论,不是。逐行 MAC 能证明存活行是真实的,但看不见已被删除的行。需要额外提交前一条记录的 tag 才能捕获尾部截断(T2)和中间删除。 问:官方目前的处理进展是什么? 据 issue 讨论,方向是文档说明放在核心,MAC/校验作为核心之外的 wrapper。贡献者计划在 Memory 指南的 “Remote Memory” 章节添加说明,PR 为 #23312。issue 状态为 open,实际合入情况以 GitHub API 查询到的 PR 状态为准。 问:读取路径为什么会对 timestamp 和 status 的改动无感知? 据 issue 讨论,读取按 (timestamp, id) 且 status == ACTIVE,所以重写时间戳会重排历史,翻转状态会让消息从上下文静默消失,两者都不触发异常。

订阅 AI 百事知

每周精选 AI 工具评测、行业资讯与使用教程,第一时间送达。

🔗 相关阅读(你可能还想看)

📄 Outlook 节点创建草稿添加附件为何报 UnableToDeserializePostBody?
📄 Flowise 点击市场或聊天页为何整页白屏?
📄 如何解决 Azure AI Search 向量存储的 $filter 递归深度超限错误?
📄 如何解决 shadcn/ui Sidebar 与 Header 重叠的问题?
📄 Next.js 开发服务器为何会 JavaScript heap out of memory?
📄 Nuxt 偶发 Cannot read properties of undefined 报错是怎么回事
📄 如何解决「Visual Studio Code for ipad」相关的问题?
📄 如何在AutoGen中实现企业级代理治理的加密行动收据(AAR)?
📄 记忆内容未清洗直接注入系统提示词,如何防范间接提示注入?
crewAI LiteAgent 将记忆内容直接拼接到系统提示词,存在间接提示注入风险。社区提出分层防御方案,但 iss
🤖 想直接和 AI 对话?
免费体验 DeepSeek V4 等顶级模型,无需注册,随问随答
🎯 立即体验 AI 工具

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

🤖 嘟嘟客服
滚动至顶部
🤖 嘟嘟客服
🤖 嘟嘟客服
';i.value='';m.innerHTML+='
...
';fetch('/dify-chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({query:q,user:'w'+Date.now()})}).then(function(r){return r.json()}).then(function(d){var a=d.answer||'暂时无法回复';m.removeChild(m.lastChild);m.innerHTML+='
'+a+'
'}).catch(function(){m.removeChild(m.lastChild);m.innerHTML+='
网络异常
'})">发送