A 客户登录后看到 B 客户的专属价格、愿望单或账户字段,这是安全事件,不是普通缓存过期。第一步应立即让所有带 Authorization、客户 Cookie 或私有字段的 GraphQL 请求绕过共享缓存,并 purge 已生成对象。
确认泄露发生在哪一层
用两个专门测试账号和匿名会话发送相同 query,比较响应头中的 Age、cache status、Vary、Cache-Control 与 request ID。若 CDN HIT 返回相同 body,重点在边缘规则;CDN bypass 仍串数据,查应用缓存和 Resolver 静态状态。
公共 Query 与私有 Query 必须分开
商品目录匿名查询可缓存,但只要结果受 customer group、共享目录、币种、store、购物车或客户 ID 影响,缓存键就必须包含正确上下文,或者完全禁止共享缓存。把 Authorization header 从 key 中剥离后 cache everything 是高危配置。
Identity 不是客户隔离的万能键
Cache identity 用于失效标签,不能替代访问控制。Resolver 仍需从受验证的 context 取得 user id,并确认请求实体属于该用户。绝不能信任客户端传入 customer_id 后直接加载。
请求内缓存也会写错
Data loader 若用 product_id 作为唯一键,却忽略客户组、网站或币种,同一请求中也可能返回错误价格。全局静态数组在长驻 PHP/Consumer 环境更危险,应把缓存限制在请求生命周期并覆盖全部业务维度。
事件响应与回归
保留脱敏证据、影响时间和缓存规则版本,按公司的安全与隐私流程评估通知。修复后在不同 POP、账号、客户组和 query 变量间交叉测试;公共目录仍可命中,任何私有结果都不得从另一身份缓存复用。
立即止损后的缓存清理
只修改规则不够,已缓存对象仍可能存活到 TTL。根据 CDN/FPC 的 purge 能力清除 GraphQL endpoint 对象,并轮换可能暴露的会话/token。不要为了省事清空所有基础设施缓存而破坏取证;先记录 key 规则、命中时间和受影响范围。
查询持久化与 GET 请求
Persisted query 或把 variables 放进 GET 时,CDN 更容易缓存响应。私有查询即使 operation name 相同,variables 与身份不同也不能共享。检查 POST 缓存规则、query hash 与 Authorization bypass,确保“匿名目录缓存”不会泛化到全部 GraphQL。
服务端授权单测
为 Resolver 写测试:匿名访问拒绝、客户 A 只能读取 A、管理员/Integration 按 ACL、伪造 customer_id 无效。缓存关闭和开启两套场景都要跑,因为权限正确但缓存错误仍会泄露。
日志本身也要按敏感数据处理
调试 GraphQL 时不要完整记录 Authorization、客户地址、邮箱、购物车 token 或响应 body。用查询哈希、字段名、客户测试账号代号和 cache key 摘要定位。事件结束后清理临时日志并缩短保留期,避免二次泄露。
重新开放缓存要分阶段
先仅对明确匿名、无客户上下文的目录 query 开启,观察命中与隔离测试,再逐步增加范围。每条规则都应有负面测试:带 token、客户组价、购物车、wishlist 的请求必须 bypass。不要一次恢复全局 cache everything。

