返回 AIPA 笔记
AIPA Day 106

多会话运行时 I — 会话隔离与上下文不泄漏

多会话运行时 I — 会话隔离与上下文不泄漏

2026-09-28
session-isolationmulti-tenantcontext-leakage

日期: 2026-09-28 阶段: Phase 4 - 自建 Agent 平台×求职冲刺 标签: #session-isolation #multi-tenant #context-leakage

核心问题

P2 建的 orchestrator/durable 都是「单跑一次」的形状——一个工作流从头跑到尾。但一个平台要同时服务很多并发会话:AML Copilot 上线后,张三在查案 A、李四在查案 B,同一个进程、同一个模型端点、同一份工具注册表。这就引出 P4 第一个硬问题——会话隔离

  1. 怎么让会话 A 的状态绝对到不了会话 B? 不只是「别写串了」这种功能正确性,而是安全边界:会话 B 的检索结果、pinned facts、中间推理,绝不能渗进会话 A 的上下文窗口。
  2. 托管平台是怎么做的? 对照 Microsoft Foundry Agent Service(2026-03-16 GA)的 hosted agents——它把隔离做到了 per-session VM-isolated sandbox 级别。看它为什么要做到 VM 粒度,而不是进程内逻辑隔离。
  3. 怎么测出泄漏? 隔离是「不发生坏事」的负向属性,最难测。设计并发隔离测试。

这对 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 的逻辑命名空间是「必要非充分」。sessionId key 全对、单测全绿,仍可能因为模型服务层的 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 ($HOME and /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."

把它拆开看三个设计决策:

  1. VM 级而非容器/进程级——每会话一个 VM-isolated sandbox。为什么不惜成本做到 VM?因为 agent 会执行不可信的工具调用和代码(Code Interpreter),进程级隔离挡不住内核侧信道;VM 才有硬边界。这正好印证 A 节层次 3。
  2. scale-to-zero + stateful resume——会话空闲 15 分钟后 compute 被回收,但 $HOME//files 状态持久化;同 sessionId 再来时重新 provision 并恢复状态(这是 Day 107 durable 会话的主题,今天先记其隔离含义:回收/恢复不能让两个会话的状态错配)。
  3. 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 RBACnamespace 末端用户会话

注意 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.tsdeepClone 保证会话间无别名。
  • 新建 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 预留一个 session tab(Day 109 实装),可视化「N 个并发会话 + 金丝雀注入 + 串扰检测」,让作品集能演示隔离正确性而非只声称。

参考资料

  1. 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)
  2. 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)
  3. arXiv — I Know What You Asked: Prompt Leakage via KV-Cache Sharing in Multi-Tenant LLM Serving:前缀共享 KV-cache 的时延侧信道致 prompt 泄漏 (2025)
  4. Giskard — Cross Session Leak: when your AI assistant becomes a data breach:跨会话泄漏的根因(cache key 碰撞/路由失败/共享 KV-cache/会话隔离不当)与检测 (2026)
  5. arXiv 2601.06627 — Burn-After-Use for Preventing Data Leakage through a Secure Multi-Tenant Architecture in Enterprise LLM:BAU 临时上下文用完即焚 (2026-01)
  6. 本仓库 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 节决策表。