多会话运行时 I — 会话隔离与上下文不泄漏
多会话运行时 I — 会话隔离与上下文不泄漏
日期: 2026-09-28 阶段: Phase 4 - 自建 Agent 平台×求职冲刺 标签: #session-isolation #multi-tenant #context-leakage
核心问题
P2 建的 orchestrator/durable 都是「单跑一次」的形状——一个工作流从头跑到尾。但一个平台要同时服务很多并发会话:AML Copilot 上线后,张三在查案 A、李四在查案 B,同一个进程、同一个模型端点、同一份工具注册表。这就引出 P4 第一个硬问题——会话隔离:
- 怎么让会话 A 的状态绝对到不了会话 B? 不只是「别写串了」这种功能正确性,而是安全边界:会话 B 的检索结果、pinned facts、中间推理,绝不能渗进会话 A 的上下文窗口。
- 托管平台是怎么做的? 对照 Microsoft Foundry Agent Service(2026-03-16 GA)的 hosted agents——它把隔离做到了 per-session VM-isolated sandbox 级别。看它为什么要做到 VM 粒度,而不是进程内逻辑隔离。
- 怎么测出泄漏? 隔离是「不发生坏事」的负向属性,最难测。设计并发隔离测试。
这对 AML 是合规底线:多租户上下文泄漏意味着 A 行的客户尽调资料串进 B 行的 SAR——一次泄漏就是一次监管事故 + GDPR 跨主体数据披露。而它恰恰是 agent 平台最隐蔽的漏洞,因为功能测试全绿时它照样在漏。
关键内容
A. 会话级状态命名空间:隔离的三个层次
「隔离」不是一个开关,是三个递进的层次,每层堵的是不同的泄漏通道:
层次 1:逻辑命名空间(namespace)——所有会话态以 sessionId 为根 key 寻址。任何读写都必须带 sessionId,禁止全局可变单例缓存被多会话共享。这堵的是编码错误导致的串写(最常见、最易测)。
层次 2:内存/进程隔离——会话各自的堆内存不共享引用。即使逻辑 key 对了,若两个会话的 state 对象别名(aliasing)了同一块内存,改 A 会动到 B。P2 的 checkpointMachine.ts 已经在单跑层面用 deepClone 防别名(snapshot() 返回深拷贝),把它升到会话级就是「每个 sessionId 一棵独立的 checkpoint 树,互不别名」。
层次 3:执行环境隔离(sandbox/VM)——会话各自的文件系统、临时文件、子进程不共享。这堵的是侧信道:会话 A 写了个临时文件 /tmp/case.json,会话 B 的代码解释器恰好读到了。也包括下文 D 节的 KV-cache 前缀共享侧信道。
一个会话态访问的伪代码,关键是入口强制带 tenant+session 双键,且把它做成类型系统不可绕过:
// 会话态以 (tenantId, sessionId) 双键命名空间寻址——单键不够
type SessionKey = { readonly tenantId: TenantId; readonly sessionId: SessionId }
interface SessionStore<S> {
// 读写都必须出示 SessionKey;没有「全局读」API,从类型上杜绝越界
load(key: SessionKey): Checkpoint<S> | null
save(key: SessionKey, cp: Checkpoint<S>): void
// 危险操作:列出某 tenant 下所有 session——必须带 tenantId,且不可跨 tenant
listSessions(tenant: TenantId): SessionId[]
}
// 取上下文时,断言返回的每一条都属于当前会话——纵深防御
function buildContext(key: SessionKey, store: SessionStore<S>): Context {
const cp = store.load(key)
assert(cp !== null && cp.ownerTenant === key.tenantId, 'cross-tenant read blocked')
return cp.state.context // 只可能是本会话的
}
反直觉洞察①(逻辑隔离对,不代表没泄漏):层次 1 的逻辑命名空间是「必要非充分」。
sessionIdkey 全对、单测全绿,仍可能因为模型服务层的 KV-cache 前缀共享而跨会话泄漏(D 节)——这条侧信道在应用代码里完全不可见。这就是为什么 Foundry 要把隔离推到 VM-per-session:应用层做不到的隔离,靠基础设施层兜底。功能正确 ≠ 安全隔离。
B. 对照 Foundry Agent Service:为什么是 VM-per-session
Microsoft Foundry Agent Service 于 2026-03-16 GA("Foundry Agent Service is GA: private networking, Voice Live, and enterprise-grade evaluations",Microsoft Foundry Blog 2026-03),运行时建在 OpenAI Responses API("the same agentic wire protocol developers are already building on")之上。其 hosted agents(2026-06 仍为 preview)的隔离模型,官方文档(learn.microsoft.com,ms.date 2026-03-05)原话:
"Hosted agents run in per-session VM-isolated sandboxes. Each session gets a dedicated sandbox with a persistent filesystem (
$HOMEand/files), enabling scale-to-zero with stateful resume and predictable cold starts. Sessions are isolated from each other, and state is automatically restored when a session resumes after going idle."
把它拆开看三个设计决策:
- VM 级而非容器/进程级——每会话一个 VM-isolated sandbox。为什么不惜成本做到 VM?因为 agent 会执行不可信的工具调用和代码(Code Interpreter),进程级隔离挡不住内核侧信道;VM 才有硬边界。这正好印证 A 节层次 3。
- scale-to-zero + stateful resume——会话空闲 15 分钟后 compute 被回收,但
$HOME//files状态持久化;同sessionId再来时重新 provision 并恢复状态(这是 Day 107 durable 会话的主题,今天先记其隔离含义:回收/恢复不能让两个会话的状态错配)。 - per-session 计费——"scaling per session, not per replica",cpu/memory 值描述的是单个会话而非聚合。"oversizing multiplies cost by your concurrency"——隔离的代价直接进了成本模型(Day 108 主题)。
Foundry 隔离的关键量化参数(官方文档 2026-03):
| 参数 | 值 | 含义 |
|---|---|---|
| 会话空闲超时 | 15 分钟 | 超时回收 compute,持久化 $HOME//files |
| 会话最长生命周期 | 30 天 | 30 天无活动后永久删除 |
| 单会话磁盘预算 | ≤ 20 GiB(1 vCPU+),~20% 系统保留 | 会话间不共享磁盘 |
| 并发活跃会话上限 | 默认 50/订阅/区域(可申请上调) | 隔离边界的容量上限 |
| Sandbox 规格 | 0.5/1/2 vCPU,1/2/4 GiB | 单会话粒度,非聚合 |
| 多租户命名空间 | isolation keys + Entra RBAC | namespace 末端用户会话 |
注意 isolation keys 与 Entra RBAC 是两层:isolation key 做数据面命名空间(namespace 末端用户的 session),Entra RBAC 做控制面授权(谁能 create/invoke/manage agent)。这与 A 节「双键 + 授权断言」同构。
C. 隔离测试:负向属性怎么测
隔离是「坏事不发生」,没有正向断言可写。工程做法是构造对抗性并发,断言无串扰:
[隔离测试状态机]
┌─────────────────────────────────────────────┐
│ 1. 起 N 个会话,各注入唯一"金丝雀"密文 │
│ sessionA.secret = "CANARY-A-7f3e" │
│ sessionB.secret = "CANARY-B-9a1d" │
└───────────────────┬─────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ 2. 交错并发推进:A.step → B.step → A.step ... │
│ (模拟真实多会话调度,最大化串扰窗口) │
└───────────────────┬─────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ 3. 断言:A 的任何输出/上下文/checkpoint │
│ 都【不含】"CANARY-B-*",反之亦然 │
│ + 断言 buildContext(A) 的每条都 owner==A │
└───────────────────┬─────────────────────────┘
▼
全部无串扰 → 通过;任一命中 → 泄漏,红
要点:(1) 金丝雀密文要全局唯一且高熵,才能精确归因泄漏来源;(2) 交错调度而非顺序跑,否则串扰窗口被掩盖;(3) 不只测最终输出,要测中间态(context、checkpoint、缓存)——泄漏常发生在中间环节而非最终 SAR。
各隔离层次的失效模式与测试方法对照:
| 隔离层次 | 典型失效 | 测试方法 | 能测出否 |
|---|---|---|---|
| 逻辑命名空间 | 全局单例缓存被多会话共享 | 金丝雀 + 交错并发 | ✅ 易 |
| 内存别名 | state 对象别名同块内存 | 改 A 后断言 B 不变(引用相等检查) | ✅ 易 |
| KV-cache 侧信道 | 前缀共享致 prompt 泄漏 | 需服务层测试,应用层测不到 | ⚠️ 难 |
| 文件/子进程 | /tmp 临时文件被另一会话读到 | sandbox 隔离 + 文件系统断言 | ⚠️ 需 sandbox |
D. KV-cache 前缀共享:应用层看不见的泄漏
最隐蔽的一条通道在模型服务层。现代 LLM 推理为省显存,会在前缀相同的请求间共享 KV-cache。多租户共享端点时,这构成侧信道:arXiv 论文《I Know What You Asked: Prompt Leakage via KV-Cache Sharing in Multi-Tenant LLM Serving》(2025) 描述了攻击者通过探测响应时延(命中缓存的请求更快)反推他人 prompt 前缀的攻击。更结构化的泄漏:另一篇 arXiv 预印本报告,在四租户的混合 RAG 语料中,"up to 95% of benign queries triggered cross-tenant leakage"——不是对抗攻击,是结构性串扰(Giskard《Cross Session Leak》2026 综述同口径)。
这对自建平台的含义:会话隔离不能只在应用层做。要么 (1) 每租户独立缓存(per-tenant cache with cryptographically strong isolation,研究界共识缓解);要么 (2) 不共享前缀 KV-cache;要么 (3) 用 sandbox/VM 把不可信执行圈住(Foundry 路线)。BAU(Burn-After-Use)机制更激进——会话上下文用完即焚(ephemeral context auto-destroyed),从根上杜绝跨会话推断(arXiv 2601.06627, 2026-01)。
反直觉洞察②(隔离的成本是反向的):直觉上「隔离 = 加点权限检查,几乎免费」。实际相反:真隔离要么牺牲性能(放弃 KV-cache 前缀共享、放弃缓存复用),要么牺牲成本(VM-per-session 比进程内贵一个数量级)。Foundry 的 "oversizing multiplies cost by your concurrency" 就是这句话的账单形态。隔离与效率是直接对立的设计张力,平台选型本质是在这条张力线上选点。
设计要点/决策表
| 要点 | 决策 | 理由 |
|---|---|---|
| 命名空间键 | (tenantId, sessionId) 双键,类型不可绕过 | 单 sessionId 不防跨租户;类型系统杜绝越界 API |
| 内存隔离 | 每会话独立 checkpoint 树,deepClone 防别名 | 复用 P2 checkpointMachine 的 deepClone 纪律 |
| 执行隔离 | 教学装置层不做真 VM;文档诚实标注「真隔离需 VM/sandbox」 | 对标 Foundry per-session VM,但浏览器内只能模拟语义 |
| 泄漏测试 | 金丝雀密文 + 交错并发 + 中间态断言 | 负向属性靠对抗性并发暴露串扰窗口 |
| KV-cache 风险 | 标注为服务层风险,应用层缓解(per-tenant cache) | 应用代码测不到,须在架构层声明边界 |
| 授权面 | 控制面 RBAC 与数据面命名空间分离 | 对标 Foundry isolation keys + Entra RBAC 两层 |
对本项目的落地
- 新建
src/agent/runtime/sessionStore.ts:定义 A 节SessionKey = { tenantId, sessionId }与SessionStore<S>接口,实现一个内存版InMemorySessionStore——内部以Map<string, Checkpoint<S>>(key =${tenantId}:${sessionId})寻址,load/save强制带SessionKey,不提供任何全局读 API。复用src/agent/durable/checkpointMachine.ts的deepClone保证会话间无别名。 - 新建
src/agent/runtime/__tests__/isolation.test.ts:实现 C 节隔离测试——起 3 个会话注入唯一金丝雀,交错调度machine.run(),断言任一会话的snapshot()与buildContext()输出都不含其他会话的金丝雀串。这是 P4 的「隔离 gate」,与 P1 的 eval gate、P3 的 SAR 质量 gate 并列进 CI。 - 诚实标注:
sessionStore.ts头注明确——本模块只做应用层逻辑隔离 + 内存别名隔离(A 节层次 1-2);层次 3(VM/sandbox 隔离)与 KV-cache 侧信道缓解需要真实后端,浏览器内教学装置不模拟,对标 Foundry per-session VM-isolated sandbox(2026-03 GA,hosted agents preview)。金额纪律:会话态若含金额一律整数分。 - agent-arch lab 升级预留:在
src/components/agent-arch/AgentArchLab.tsx的 TABS 预留一个sessiontab(Day 109 实装),可视化「N 个并发会话 + 金丝雀注入 + 串扰检测」,让作品集能演示隔离正确性而非只声称。
参考资料
- Microsoft Learn — Hosted agents in Foundry Agent Service (preview):per-session VM-isolated sandbox;$HOME/files 持久化;空闲 15min 回收、30 天删除;单会话 ≤20GiB 磁盘;并发 50 上限;scaling per session not per replica(ms.date 2026-03-05,updated 2026-06-05)
- Microsoft Foundry Blog — Foundry Agent Service is GA: private networking, Voice Live, and enterprise-grade evaluations:2026-03-16 GA;建于 OpenAI Responses API;BYO VNet 无公网出口;Evaluations GA (2026-03)
- arXiv — I Know What You Asked: Prompt Leakage via KV-Cache Sharing in Multi-Tenant LLM Serving:前缀共享 KV-cache 的时延侧信道致 prompt 泄漏 (2025)
- Giskard — Cross Session Leak: when your AI assistant becomes a data breach:跨会话泄漏的根因(cache key 碰撞/路由失败/共享 KV-cache/会话隔离不当)与检测 (2026)
- arXiv 2601.06627 — Burn-After-Use for Preventing Data Leakage through a Secure Multi-Tenant Architecture in Enterprise LLM:BAU 临时上下文用完即焚 (2026-01)
- 本仓库
src/agent/durable/checkpointMachine.ts(deepClone 防别名、snapshot 深拷贝模式参照)(2026-06)
SOTA 检查 (2026-06-11)
- per-session VM 隔离在 2026-06 是托管 agent 平台的事实标准:Foundry hosted agents(VM-per-session)、AWS AgentCore Runtime(microVM-per-session,借自 Lambda,调到 8h 会话)、Google Agent Engine(per-session 计费)三家殊途同归——都把隔离推到 VM/microVM 粒度而非进程内。本日 WebSearch 未见有主流托管平台退回进程级隔离。
- KV-cache 跨租户泄漏是 2025-2026 的活跃研究前沿:从时延侧信道(单点攻击)到混合 RAG 结构性串扰(95% benign query 泄漏)均有近 12 个月内论文;说明「共享缓存提效」与「多租户隔离」的张力尚无银弹,per-tenant cache / BAU / 不共享前缀是当前缓解三选项。执行当周应重查 arXiv 是否有新缓解方案。
- Foundry hosted agents 仍为 preview(2026-06 口径):per-session VM 隔离的 GA 时间未定,本笔记参数(15min/30 天/20GiB/50 并发)为 2026-03~06 preview 口径,执行当周须重新确认是否转 GA 及参数变化。
- 过时认知警示:「多会话隔离 = 加 sessionId 过滤」过时——A 节三层次与 D 节 KV-cache 侧信道证明逻辑过滤只堵了最浅一层;合规级隔离必须考虑内存别名 + 服务层缓存 + 执行环境三条通道。
- 待跟踪:Foundry isolation keys 的细粒度 API(如何 namespace 末端用户)官方文档 2026-06 仍偏概念,待 hosted agents 转 GA 后补细节,回填 B 节决策表。