👁 78 次阅读
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的行 - 上述排序行为带来的后果
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,所以重写时间戳会重排历史,翻转状态会让消息从上下文静默消失,两者都不触发异常。
🔗 相关阅读(你可能还想看)
📄 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