# 16 · 安全与多租户架构:把安全当结构,而非补丁 > 一句话点题:**安全不是上线前加一道扫描、出事后打一个补丁;它是你画第一张架构图时就该问的问题——「谁会怎么攻击我的每一条数据流、每一道信任边界?一次失误最多能炸多大?」** 安全做对了,是隐形的;做错了,是致命的。而在 AI 替你写代码、agent 替你动手的当下,攻击面正以前所未有的速度变宽——安全被从「功能」逼成了「结构」。 --- > **🧭 进阶篇第 7 章。** [06 · 质量属性与取舍](06-质量属性与取舍.md) 里,安全只是七个质量属性之一,我们点到为止:不信任输入、最小权限、纵深防御。这一章把它**单独拎出来挖深**——因为安全和别的属性有个根本不同:**性能差一点是体验问题,安全差一点是生死问题。** 一次数据泄露能让公司关门,一个被投毒的依赖能波及全网。 > > 而 AI 时代让这一章空前重要。AI 几秒能写出一段「能跑」的鉴权代码,但「这条数据流谁能碰、这次入侵能波及多大、这个 agent 该不该有 shell 权限」——是**判断题**,代价由你的业务、你的用户、你的合规承担。**实现越来越廉价,而「想清楚谁会怎么攻破你」的判断,越来越值钱。** --- ## 一、安全是结构,不是补丁:先学会「系统性地想坏事」 新人做安全,靠的是「想起来就加一道」:加个登录、加个 HTTPS、上线前跑个扫描。这是**补丁思维**——零散、被动、哪疼治哪。架构师做安全,靠的是**先把整个系统摊开,系统性地问每一处「这里会被怎么攻破」**。这套方法有个名字:**威胁建模(Threat Modeling)**。 最经典的框架是微软在 1999 年提出的 **STRIDE**(由 Praerit Garg 和 Loren Kohnfelder 创立),它给「会出哪些坏事」编了个六字口诀,逼你对着**数据流图(DFD)**逐条排查——每一个进程、每一个数据存储、每一条数据流、每一道**信任边界(trust boundary)**,都问一遍这六个问题: ``` STRIDE —— 把"会出什么坏事"拆成六类,对着数据流图逐条问 ─────────────────────────────────────────────────────────── S Spoofing 假冒身份 → 它破坏「认证」(你真是你吗?) T Tampering 篡改数据 → 它破坏「完整性」(数据被人改过吗?) R Repudiation 抵赖 → 它破坏「不可否认」(事后能赖账吗?) I Information 信息泄露 → 它破坏「机密性」(不该看的看到了吗?) D Denial of Service 拒绝服务 → 它破坏「可用性」(能被打挂吗?) E Elevation of Priv 提权 → 它破坏「授权」(普通人能变管理员吗?) ``` **威胁建模的灵魂,是「信任边界」这个概念。** 一条数据从浏览器进到你的网关、从网关进到内部服务、从服务进到数据库——每一次「跨越一道边界」,信任级别都在变。**坏事几乎总发生在边界上**:外部输入跨进内部时(注入)、低权限组件跨向高权限资源时(提权)、敏感数据跨出系统时(泄露)。把边界画清楚,你就知道该在哪里设防。 举个具体的:就拿 [02](02-架构师的思考框架.md)/[03](03-读懂与画好架构图.md) 里画过的那种「用户 → 网关 → 订单服务 → 数据库」数据流,用 STRIDE 走一遍信任边界: ``` 用户 ══╗(信任边界①:公网→系统) 订单服务 ══╗(信任边界②:服务→数据) ▼ ▼ [API 网关] ──────────────▶ [订单服务] ──────────────▶ [数据库] S 假冒别人的 token? E 越权改别人的订单? T 绕过应用直接改库? D 被刷爆打挂? T 篡改传入的金额? I 拖库导致全量泄露? I 错误信息泄露内部结构? R 改了订单事后赖账? I 备份/日志里有明文? ``` **关键不在于背熟六个字母,而在于养成「对着每一道边界,逐条想坏事」的反射。** 你会发现:网关那道边界主要防假冒(S)和拒绝服务(D);服务那道边界主要防越权(E)和篡改(T);数据那道边界主要防拖库泄露(I)。**防御资源,就该按这张图精准投放,而不是均匀地撒。** > **架构智慧**:补丁思维问「我加了哪些安全措施?」,结构思维问「我的每一条数据流、每一道信任边界,都被谁、用 STRIDE 的哪一条攻击过了吗?」。**前者是清单,后者是地图。** 安全不是往系统上「贴」东西,而是在画架构时就把「攻击者会怎么想」织进结构里——这正是 [06 章](06-质量属性与取舍.md) 那句「不信任输入」的系统化版本。**而它和别的质量属性最大的不同是:性能、可用性可以「先上线、再优化」,安全的结构性漏洞却往往「上线即定型、事后补不上」。** --- ## 二、纵深防御 + 零信任:别因为「在内网」就信任任何人 老一代的安全模型是**城堡与护城河**:筑一道厚墙(防火墙),墙外是危险的公网,墙内是可信的内网——**只要进了墙,基本就畅通无阻**。这个模型有个致命假设:**「内网 = 可信」**。但现实一次次打脸:攻击者一旦突破边界(钓鱼、漏洞、内鬼),就在「可信内网」里横着走,这叫**横向移动(lateral movement)**——SolarWinds、无数勒索软件,都是靠这一招把单点突破放大成全军覆没。 更要命的是,**那道「墙」本身在今天已经塌了**:远程办公让员工从家里的网络访问系统,「墙内墙外」失去了物理边界;上云让你的服务跑在别人的数据中心;微服务让东西向(服务间)流量爆炸式增长,远超南北向(进出系统)流量。**当「内部」已经无处不在,「内网可信」就成了一句空话。** 修正它的是两个互补的思想: **① 纵深防御(Defense in Depth)**:别指望一道墙挡住所有人。**层层设防,攻破一层还有下一层。** ``` 单层防御(脆弱): 攻击者 ──突破唯一一道墙──▶ 长驱直入,全盘皆输 ↑ 一旦破防 = 满盘皆输 纵深防御(韧性): 攻击者 ─▶[WAF]─▶[鉴权]─▶[授权]─▶[加密]─▶[隔离]─▶[审计] ↑ 每一层都独立设防,破一层还有下一层兜底 ``` **② 零信任(Zero Trust)**:干脆**取消「内网可信」这个假设**。核心信条是一句话——**「从不信任,始终验证(never trust, always verify)」**:不管请求来自公网还是内网,**每一次访问都要重新验证身份、检查授权、加密传输**;信任不再绑在「网络位置」上,而是绑在「这个用户 + 这台设备 + 这次请求的上下文」上。 最有名的落地是 Google 的 **BeyondCorp**。它起于一个大胆的决定:**让全体员工不用 VPN、从任何不可信网络都能安全办公**。做法是把所有应用都部署到公网,在每个请求前面架一个**访问代理**,根据「你是谁、你的设备状态、你要访问的资源有多敏感」动态决策放不放行——**每个请求都被当成来自不可信网络,直到它被认证、授权、验证过为止。** 网络边界这道墙,被「身份 + 设备 + 上下文」的动态判定取代了。 贯穿两者的,是 [06 章](06-质量属性与取舍.md) 那条**最小权限原则(Principle of Least Privilege)**:每个组件、每个账号,只拿它干活**必需的那一点点**权限,多一分都不给。这条原则是下一节「爆炸半径」的地基——你给得越少,出事时能炸的就越小。 > **架构智慧**:「在内网所以安全」是已经过时的奢侈假设。**零信任的本质,是把「信任」从一个静态的网络位置,改造成一个每次都要重新挣得的动态判定。** 它不省事——每次访问都要验证,有摩擦、有开销——但它换来的是:**单点被突破,不再等于全盘沦陷。** --- ## 三、爆炸半径与隔离:把「一次失误的代价」关进小格子 安全做到极致也无法保证「永不被攻破」。**所以成熟的安全观,从「绝不出事」转向了一个更现实的问题:一旦出事,能波及多大?** 这个「多大」,就是**爆炸半径(blast radius)**。 这正是 [12 · 为失败而设计](12-为失败而设计.md) 那套「隔离思想」在安全语境下的同一句话:**隔离,就是把「一次失误的代价」关进一个小格子,不让它溢出来淹掉全局。** 故障隔离防的是「一个组件挂拖垮全局」,安全隔离防的是「一处被攻破波及全局」——本质是同一种结构智慧。 ``` 无隔离(共命运): 一把万能钥匙 / 一个大权限 / 一个大库 一处被攻破 ──▶ 全部数据、全部租户、全部系统,一锅端 ↑ 爆炸半径 = 整个公司 有隔离(分舱): [舱1][舱2][舱3][舱4] ← 各自独立的权限/网络/数据/密钥 一处被攻破 ──▶ 只炸这一个舱,其余安然无恙 ↑ 爆炸半径 = 一个舱 ``` 缩小爆炸半径的手段,几乎都是「把大权限拆成小权限、把大边界拆成小边界」:**最小权限**(给得越少,炸得越小)、**网络分段**(服务间只开必要的端口,而非内网全互通)、**凭证隔离**(每个服务一套自己的密钥,而非全公司共用一把)、**短时凭证**(令牌几分钟就过期,偷到也用不了多久)。 这里有个和 [12 章](12-为失败而设计.md) 完全同构的洞察:**故障隔离和安全隔离用的是同一套结构武器**。舱壁(bulkhead)、限流、熔断这些防「故障扩散」的手段,换个视角看也在防「攻击扩散」——一个被攻陷、开始疯狂发请求的组件,和一个发生故障、开始疯狂重试的组件,对系统的伤害形态是一样的,而隔离对两者一视同仁地兜底。**所以你为可用性建的那些「舱」,顺带也在为安全服务**——这就是好结构的复利:一处设计,多重收益。 > **架构智慧**:别再只问「怎么才能不被攻破」(你做不到 100%),要同时问「**被攻破之后,这次入侵最多能碰到什么?**」。**一个设计良好的系统,任何单点被拿下,损失都被关在一个小盒子里——攻击者拿到一个服务的权限,够不到别的服务的数据;偷到一个租户的口令,看不见别的租户。** 把爆炸半径当成一等的设计目标,而不是出事后才追悔的指标。 --- ## 四、多租户隔离谱系:租户串扰是头号事故 **多租户(multi-tenancy)**——一套系统服务一大群互不相识的客户(租户)——是几乎所有 SaaS 的基本盘。它最大的好处是**成本**:大家共用一套基础设施,边际成本极低。但它也带来一个**头号噩梦:租户串扰(cross-tenant leakage)**——A 公司的数据,被 B 公司看到了。在多租户系统里,这是最严重、最容易上头条、也最容易让客户立刻流失的事故。 隔离不是「有 / 没有」的开关,而是一道**谱系**。AWS 的 SaaS 隔离白皮书把它总结成三档,核心取舍是**成本 vs 隔离强度**: ``` 隔离弱 / 成本低 ◀──────────────────────────────────▶ 隔离强 / 成本高 池化 (Pooled) 桥接 (Bridge) 竖井 (Silo) 所有租户共享一套资源 部分共享、部分独立 每个租户一套独立资源 (一个库/一套服务) (如:网关共享、库独立) (独立库/独立部署/独立账号) ↑ 成本最低、最易扩 ↑ 折中 ↑ 隔离最硬、合规最稳 ↑ 但隔离全靠"代码纪律" 按租户重要性分档 ↑ 但成本/运维随租户数线性涨 ``` 而在「数据怎么隔离」这个最关键的维度上,又是从软到硬的一道谱系: ``` 软(省钱、易串)◀──────────────────────────────▶ 硬(贵、难串) 行级隔离 Schema 级 库级 物理级 同一张表,靠 同一个库, 不同的库 不同的物理机/账号 tenant_id 列区分 不同的 schema ↑ 全靠"每条 SQL 都 ↑ 隔离稍强 ↑ 隔离强、 ↑ 隔离最强、 带对 tenant_id" 连接绑定 schema 可独立备份/迁移 合规最稳、最贵 ``` **行级隔离是串扰事故的头号温床。** 整个隔离只靠一件事撑着:**每一条查询都正确地带上了 `WHERE tenant_id = ?`**。只要有一处遗漏——某个新人写的接口忘了加这个条件、某个缓存键没带租户前缀、某个后台任务跑了全表——A 租户就能看到 B 租户的数据。它不是「被黑客攻破」,而是「自己代码的一个疏忽」,却造成同等灾难。串扰的入口比想象中多得多: ``` 租户串扰的常见漏点(全都是"忘了带 tenant_id"的变体) ────────────────────────────────────────────────────────── ① 新接口的某条 SQL 忘加 WHERE tenant_id ── A 直接查到 B 的数据 ② 缓存键写成 user:123 而非 tenant:7:user:123 ── A 命中了 B 缓存里的结果 ③ 对象存储/文件路径用了可猜的自增 ID ── 改个 URL 数字就读到别家文件 ④ 后台批处理/导出任务图省事跑了全表 ── 一份报表混进了所有租户 ⑤ 越权:接口只校验"登录了",没校验"这条归你" ── 改个 id 参数就改别人的单(IDOR) ``` 这些漏点的共同点是:**它们都不报错、都能跑通、测试也常常发现不了**(因为开发自测时往往只有一个租户的数据)。等到第二个租户进来、或某个客户偶然看到别家数据截图发上社媒,才东窗事发——而那时信任已经崩了。 最经典的池化多租户范本是 **Salesforce**:它用**一套共享的关系库**承载单个实例上 8000+ 个客户组织,每个租户叫一个「org」,每条记录都打上 **OrgID** 标签,**每一次查询都隐式按 OrgID 过滤**,让每个客户只看见共享存储里属于自己的那个「虚拟数据库」;再叠加行级安全(RLS)兜底。它把「行级隔离」这条最省钱、也最危险的路,用**平台级强制过滤 + 元数据驱动**做到了工业级可靠——关键在于:**隔离不靠每个开发者自觉,而靠平台在底层强制注入。** > **架构智慧**:多租户的核心判断是**「这份业务,租户串扰的代价有多大」**。串扰代价低(如公开内容平台),池化省钱即可;串扰代价高(医疗、金融、政企),就该往竖井走,哪怕贵几倍。**而无论哪一档,千万别把租户隔离这条命脉,交给「每个开发者都记得加 tenant_id」——人一定会忘。把它做成结构性强制**:平台层自动注入租户条件、ORM 层全局过滤、每条查询默认带租户、用自动化测试专门验证「跨租户读不到」。**隔离强度可以按成本谈,但「隔离必须由结构保证、而非靠人自觉」不可谈。** --- ## 五、密钥与凭证管理:secrets 绝不进代码库、不进日志 系统里最值钱的东西,是**密钥与凭证(secrets)**:数据库密码、API key、加密私钥、第三方令牌。它们是「打开一切的钥匙」——爆炸半径最大的那类东西。围绕它们,只有三条铁律:**集中保管、定期轮换、最小暴露。** ``` ❌ 反模式(到处都是钥匙) ✅ 结构化管理(钥匙锁进保险箱) ───────────────────────── ───────────────────────────── 硬编码进代码、提交进 Git 集中存进专用的密钥管理服务(Vault/KMS/ 写在配置文件里随仓库分发 Secrets Manager),代码只在运行时按需取 打进日志、错误信息、监控 密钥绝不进代码库 / 日志 / 报错信息 一把 key 全公司共用、永不更换 每服务独立密钥 + 定期自动轮换 + 短时令牌 ↑ 一旦仓库/日志泄露 = 钥匙全丢 ↑ 泄露面最小,且轮换让旧钥匙迅速失效 ``` 最容易犯、也最致命的一条是 **secrets 进了代码库**。一旦密钥被 `git commit`,它就**永远活在 Git 历史里**——你事后删掉文件,历史里还在;仓库一旦被克隆、被开源、被泄露,等于把钥匙抄送给了全世界。GitHub 上每年都有海量这类泄露,扫描机器人盯着新提交,几秒内就能薅走你刚误传的 AWS key 去挖矿。**正确做法是结构性的:密钥从不进代码,用密钥管理服务集中托管,运行时注入;CI 里加 secret 扫描,把误传挡在合并之前。** 另外两条铁律也都是「用结构降低爆炸半径」:**定期轮换**——密钥再保密,时间一长被截获、被内鬼带走的概率都在累积;定期(最好自动)换一把,等于给「万一已经泄露」上了个止损;更进一步是**短时凭证**(临时令牌几分钟到几小时就过期),偷到也用不了多久——这正是 Capital One 那种「偷到长期凭证、长驱直入」的反面药方。**最小暴露**——每把钥匙只开它该开的那一扇门(又是最小权限):数据库只读的服务,就别给它能写的密码;只调一个外部 API 的服务,就别把全套 key 都塞给它。**这样即使某把钥匙泄露,攻击者能打开的也只有一扇小门。** 这正呼应我们 Agent 模板里反复强调的「**密钥都在本地、明文即敏感**」那条教训。[Hermes](../templates/hermes/README.md) 的安全要点写得很直白:**密钥分散存储、不写日志,经透传机制注入到(隔离的)终端,而非散落在命令行 / 日志里**;[OpenClaw](../templates/openclaw/README.md) 更是把整个家目录当敏感区——「assume anything under `~/.openclaw/` may contain secrets」,直接推出「整盘加密 + 专用用户」的建议。[AI 网关](../templates/ai-gateway/README.md) 里也有一模一样的结构:**上游 key 就是钱,必须由网关托管、绝不外泄给下游或日志。** > **架构智慧**:密钥泄露往往不是被「黑」出来的,而是被自己**「漏」**出来的——提交进仓库、打进日志、共用一把永不更换。所以治理它靠的不是「更小心」,而是**结构**:集中托管让密钥不散落、自动轮换让旧钥匙速朽、最小暴露让每把钥匙只开一扇门。**记住那句:任何会被 grep 到、被 commit 到、被打进日志的明文,都该假设它已经泄露。** --- ## 六、供应链安全:你没写的代码,才是最大的攻击面 现代软件,**你自己写的代码可能只占 5%,其余 95% 是依赖**——开源库、基础镜像、CI/CD 工具、构建脚本。这意味着:**你的攻击面,绝大部分是别人的代码。** 依赖、构建、分发的每一环,都是攻击面;**一个被投毒的依赖,能顺着「信任链」波及全网。** 这就是**供应链安全(supply chain security)**,也是过去几年最触目惊心的事故来源。 ``` 你的信任链(每一环都可能被投毒) ────────────────────────────────────────────────────────────── 开源依赖 ──▶ 间接依赖(依赖的依赖)──▶ 构建工具/CI ──▶ 基础镜像 ──▶ 你的产物 ↑ ↑ ↑ ↑ 被劫持的包 深到没人看的传递依赖 被攻陷的构建机 带后门的官方镜像 (xz) (Log4Shell 藏在这层) (SolarWinds) ``` 为什么它这么危险?因为**信任是传递的**:你信任一个库,就等于信任了它的所有依赖、它的维护者、它的构建流水线。这条链上任何一环被攻破,毒就会顺流而下,流进每一个用了它的系统——而用了它的系统,可能是全世界几十万家。 它还有两个反直觉的放大效应。其一,**深度**:你 `package.json` 里手写的依赖也许只有几十个,但拉下来的传递依赖动辄成百上千——Log4Shell 之所以波及那么广,正是因为无数团队**根本不知道自己间接依赖了 Log4j**(它藏在某个框架的某个依赖的某个依赖里)。其二,**自动化**:CI/CD 让「拉取依赖 → 构建 → 自动部署到生产」变成一条无人值守的流水线——这本是好事,但也意味着**一个被投毒的依赖,可以全自动地、在没有任何人点头的情况下,被构建进产物、推上生产**。SolarWinds 把这点演到了极致:毒不是混进某个依赖,而是直接攻陷了**构建系统本身**,让带正确签名的「官方产物」从源头就是毒的。 > **架构智慧**:你对依赖的信任**不该是无限的、一次性的**。结构化的做法:**锁定版本**(别盲目自动升级到最新)、**生成 SBOM**(软件物料清单,搞清楚你到底用了什么)、**扫描已知漏洞 + 可疑变更**、**最小化依赖**(不用的库就是白送的攻击面)、**让构建可复现**(同样的源码必出同样的产物,投毒就藏不住)。**「它是个出名的开源库,应该没问题」——xz 事件证明,这种盲目信任正是攻击者精心设计要利用的。** 详见下面的真实案例。 --- ## 七、合规即架构:GDPR 们是结构性约束,不是事后开关 最后一类常被工程师当「法务的事」推开的,是**合规(compliance)**:数据驻留(数据必须存在某个法域内)、审计留痕(谁在何时碰了什么数据,要可追溯)、可删除权(GDPR 的「被遗忘权」:用户要求删除,你得真能把他的数据从所有地方删干净)、数据最小化(只收集必需的数据)。 这一点 [支付系统](../templates/payment-system/README.md) 体现得最彻底:它把卡数据合规(PCI-DSS)做成了架构本身——真实卡号**绝不落地**,在最外层就令牌化(用 token 代替),让合规风险和敏感数据被挡在系统之外;账本则用**只追加、不可篡改**的复式记账,天然满足「可审计、可追溯」。这些都不是事后开关,而是结构选择。 新人以为合规这些是「上线后再加的开关」。**真相是:它们是结构性约束,深深决定你的架构长什么样**,事后根本加不上: ``` 合规要求 → 它如何强行塑造你的架构(事后加不上) ────────────────────────────────────────────────────────────── 数据驻留(数据不出境) → 逼你做按地域的数据分区/多区域部署(不是配个开关) 可删除权(被遗忘权) → 数据散落各处+备份+日志+下游,「删干净」是架构级难题 审计留痕 → 每个敏感操作都要不可篡改地落审计日志(得提前埋点) 数据最小化 → 从一开始就决定"哪些字段根本不收、不存、不传" ``` 最典型的是**可删除权**。如果你的架构一开始就把用户数据无序地散落在十几个服务、几十张表、N 份备份、还流进了数据仓库和日志——那么当一个用户行使「被遗忘权」时,你会发现「删干净」几乎不可能。反过来,如果一开始就按「这是谁的数据」做了归集和标记,删除才是可行的。**这跟多租户隔离是同一个结构问题:数据的归属,必须从设计第一天就织进结构。** 数据驻留也一样硬。当法规要求「欧盟用户的数据不得离开欧盟」,这绝不是配个开关——它**逼着你的整个数据层按地域分区**:存储得在对应区域、跨区调用要管控、连备份和灾备都得留在法域内。你若一开始把全球数据混在一个库里,事后再想「把欧盟用户摘出来单独存」,几乎等于重做数据层。**它和 [05 章](05-数据与状态.md) 的分片、[12 章](12-为失败而设计.md) 的多区域部署,本就是同一批结构决策,只是这次的驱动力是合规而非性能或容灾。** > 这里藏着一个和本章一以贯之的判断:**合规、安全、可用性这些「结构性约束」,越早纳入越便宜,越晚补越贵——贵到某个点就直接变成「无法补,只能重做」。** 这正是 [08 章](08-架构决策记录与演进.md) 强调「把约束和理由尽早记进 ADR」的原因之一。 > **架构智慧**:合规不是上线前找法务补一份文档,而是**从第一张架构图就要纳入的结构性约束**——它和 [06 章](06-质量属性与取舍.md) 的「资金正确性」一样,属于**不可谈判的底线**,而非「ROI 高才做」的优化项。**把合规当结构来设计(数据按法域分区、敏感操作默认审计、数据归属可追溯、删除路径可达),你是在省未来的命;当成事后开关,你是在埋一颗合规炸雷。** --- ## 📌 真实案例:四起把「信任」打穿的事故 安全的教训,真实案例讲得最透。下面四起,正好覆盖本章的核心信任边界——**供应链、依赖、构建、云身份**: **① xz-utils 后门(2024,CVE-2024-3094)——供应链信任的教科书。** 这是一场长达**近三年的社会工程**。一个化名 "Jia Tan" 的攻击者,2021 年起以「热心贡献者」身份慢慢渗透进 xz(一个几乎所有 Linux 都依赖的底层压缩库)项目;同伙用马甲账号在邮件列表施压、逼原维护者交出共同维护权;拿到信任后,在 2024 年 2 月把后门**藏进发布 tarball 的混淆二进制测试文件里**(而非提交进 Git,以躲过代码审查),用 IFUNC 机制在运行时劫持 OpenSSH 的 `RSA_public_decrypt`,使持有特定私钥的攻击者能远程执行任意代码。它差一点就随 Fedora、Debian、openSUSE 流向全球。**它没被任何安全扫描发现,而是被微软工程师 Andres Freund 偶然注意到 SSH 登录慢了 0.5 秒**、刨根问底才揪出来——几乎是撞大运。教训刺骨:**开源信任链可以被「攻击者扮演的好人」长期、耐心地腐蚀;盲目信任「出名的开源库」,正是被精心利用的那个假设。** 📎 [JFrog 复盘](https://jfrog.com/blog/xz-backdoor-attack-cve-2024-3094-all-you-need-to-know/) **② Log4Shell(2021,CVE-2021-44228)——一行日志打穿半个互联网。** Log4j 是 Java 世界最流行的日志库,它有个「消息查找替换」特性会解析 `${jndi:ldap://...}` 这样的字符串。攻击者只要让应用**记录一行**含恶意 JNDI 串的日志(比如塞进 User-Agent、聊天消息、任何会被打日志的字段),Log4j 就会去连攻击者的服务器、下载并执行恶意代码——CVSS 满分 10.0。它的恐怖在于**无处不在**:Wiz 与 EY 的研究显示 **93% 的云企业环境受影响**。它精准印证本章第六节:**漏洞藏在「深到没人看的传递依赖」里**——多少团队根本不知道自己(通过某个框架)间接用了 Log4j。📎 [Wikipedia: Log4Shell](https://en.wikipedia.org/wiki/Log4Shell) **③ SolarWinds / SUNBURST(2020)——构建系统被攻陷,毒从「官方更新」流出。** 攻击者攻陷了 SolarWinds 的 **Orion 软件构建系统**,把后门 SUNBURST 注入到合法的、带正确签名的官方更新里。于是约 **18000 家客户**(含大量美国政府机构与财富 500 强)在 2020 年 3–6 月间「正常升级」时,亲手把后门装进了自己最核心、最受信任的网管系统。它把本章第二节的横向移动和第六节的供应链推到极致:**当你信任的「官方更新」本身就是毒,纵深防御的所有外层都被绕过了——因为毒是从你信任链的最深处流出来的。** 📎 [Fortinet 解析](https://www.fortinet.com/resources/cyberglossary/solarwinds-cyber-attack) **④ Capital One(2019)——一个 SSRF + 过大的权限 = 1 亿人数据泄露。** 一名前 AWS 工程师利用 Capital One 一台 WAF 的 **SSRF(服务端请求伪造)**漏洞,诱使它去访问 AWS 的 **EC2 元数据服务(IMDSv1,无需认证)**,偷到了那台 WAF 绑定的 IAM 角色的临时凭证。要命的是,**这个角色权限过大**——凭证能访问 700 多个 S3 桶,于是约 **1.06 亿**信用卡申请人的姓名、地址、SSN、银行账号被一锅端。它是本章第三节(爆炸半径)和第二节(最小权限)的反面教材:**单个入口(一个 SSRF)本不致命,但因为那个角色权限过大、元数据服务无认证,爆炸半径瞬间从「一台 WAF」放大成「全部客户数据」。** 最小权限若到位,这次入侵本该被关进一个小盒子。📎 [MIT 案例分析](https://cams.mit.edu/wp-content/uploads/capitalonedatapaper.pdf) > 四起事故,四种信任被打穿的方式,却指向同一句话:**安全的崩塌,几乎总发生在「你以为可以信任」的那道边界上**——信任的开源库(xz)、信任的传递依赖(Log4j)、信任的官方更新(SolarWinds)、信任的内部组件却给了过大权限(Capital One)。**把每一处「我信任它」都重新审一遍「凭什么、信任到什么程度、万一它叛变能炸多大」——这就是把安全当结构。** --- ## 🤖 AI / vibe coding 视角:两条全新的、正在被打穿的攻击面 AI 把写代码和「动手做事」的成本打到极低,也同步把两条全新的攻击面捅到了所有人面前。这一节是本章在当下最该看的部分。 **① AI 生成代码的安全隐患:幻觉依赖、slopsquatting、与没人审的 vibe-coded 代码。** AI 写代码时会**一本正经地编出不存在的库名**。USENIX Security 2025 一项跨 16 个模型、57.6 万段代码的研究发现:**推荐的包里有 19.7% 根本不存在**(开源模型更糟,平均 21.7%)。攻击者立刻抓住了这个规律——**抢注那些 AI 爱编的假包名**,投毒后坐等开发者照着 AI 的建议 `install`。这种新攻击有了名字:**slopsquatting**(slop=AI 糟粕 + squatting=抢注)。Lasso 的研究员把一个 AI 高频幻觉的名字 `huggingface-cli` 注册成空包,**三个月被下载了 3 万多次**——而这只是个善意演示,换成恶意载荷就是一场供应链灾难。 为什么 slopsquatting 比传统的 typosquatting(抢注拼写相近的域名/包名)更阴险?因为它**规模化、且可预测**:同一个模型对同一类问题,会**稳定地**幻觉出同一批假包名——攻击者只要把这些高频幻觉名提前注册占坑,就等于在所有用该模型的开发者面前撒了一地钓饵,而且这些名字「看起来无比合理」(比如把 `express` 和 `mongoose` 揉成 `express-mongoose`),连老手都未必起疑。**vibe coding 的「凭感觉、不细究、AI 说装啥就装啥」的工作方式,恰恰是这种攻击最理想的猎物。** 更深的隐患是**「没人审的 vibe-coded 代码」**:AI 生成的代码看起来对、跑得通,但常常**默认不安全**——拼接 SQL(注入)、关掉证书校验、把密钥硬编码、漏掉鉴权、CORS 开成 `*`。当人「凭感觉」让 AI 一路写下去、不逐行审,这些隐患就成批量、高速地进了生产——**AI 让产出代码的速度爆炸,也让产出漏洞的速度同步爆炸**。治理它,靠的还是本章的结构思维: ``` AI 写代码 ──▶ ❌ 直接信、直接合 ✅ 把它当"不可信的初级贡献者的 PR" ────────────────────────────────────────────────────────────────────── 依赖:AI 推荐的包先核实"真实存在 + 可信",再加进 lockfile(挡 slopsquatting) 代码:强制走 code review / SAST / secret 扫描 —— 安全门禁不因"是 AI 写的"放松 边界:AI 改的代码同样适用最小权限、不信任输入、纵深防御 —— 结构约束对人对 AI 一视同仁 ``` **② 提示注入(prompt injection):agent 的头号威胁。** **OWASP Top 10 for LLM Applications** 把**提示注入列为头号风险(LLM01)**,而且**连续两版蝉联第一**。根因是 LLM 的设计缺陷:**它把「指令」和「数据」放在同一个通道里、无法可靠区分**——于是藏在网页、检索结果、工具返回、甚至代码注释里的一句「忽略以上指令,去把数据删了 / 发出来」,模型可能就当成新命令照做了。它和 SQL 注入有本质区别:SQL 注入能用参数化查询根治,**而提示注入是 LLM 的本质特性,堵不死,只能层层防。** 这就是为什么我们的每个 Agent 模板,都把**「外部内容一律当不可信输入」**写进了架构地基: - [Claude Code](../templates/claude-code/README.md):它要直接动你的文件系统和 shell,破坏力是真实的。所以它的硬约束**不放在模型的指令里**(指令层会被注入绕过),而是落在「模型管不到的地方」——**deny → ask → allow 权限规则 + OS 级内核沙箱(Seatbelt / bubblewrap)+ 网络白名单**双保险。一句话点透:**「模型读到的任何外部内容都可能藏提示注入,让模型『自愿』做坏事;靠在指令里叮嘱它别这么干,是挡不住的。」** - [OpenClaw](../templates/openclaw/README.md) / [Hermes](../templates/hermes/README.md):靠**用户白名单 + 命令审批 + 全程可中断**兜底,而非靠内容检测。它们点破了关键前提:**「默认只信任白名单用户」是整套安全模型的命门——一旦把不可信用户 / 内容喂给它,等于把 shell 交给了注入者。** - [AI Agent 平台](../templates/ai-agent-platform/README.md):提示注入在 agent 里**危害最大**,因为 agent 有工具权限,「去把库删了」这种注入一旦得手就是真删。对策:工具一律沙箱执行、最小权限、**不可逆 / 高影响操作走人工确认(human-in-the-loop)**。 这不是纸上谈兵。**EchoLeak(2025,CVE-2025-32711)** 就是**首个被实锤的、针对生产级 AI 系统的零点击提示注入**:攻击者只需给受害者发**一封精心构造的邮件**,无需任何点击,就能诱使 Microsoft 365 Copilot 去读取内部文件并把内容外传到攻击者服务器——它绕过了微软的注入分类器、用引用式 Markdown 规避链接审查、借自动加载图片和一个被 CSP 放行的 Teams 代理完成外泄(CVSS 9.3)。这正是上面 **Capital One 的 SSRF** 在 AI 时代的同构升级:都是「**把一段不可信的外部输入,变成一把能驱动高权限组件替你办坏事的钥匙**」——只不过这次被驱动的不再是一台 WAF,而是一个能读遍你全部文档的 AI agent。值得玩味的是:微软**做了**注入分类器去检测恶意提示,EchoLeak 还是绕过去了——这恰恰印证本章的结论:**靠「检测内容」防注入是堵不死的,真正的硬约束得落在权限与边界(agent 能读什么、能往哪发)这些结构层。** > **架构智慧**:AI 时代真正变了的,是**「能让 agent 动手做事」的工具权限,把安全从『功能』逼成了『结构』**。一个只会聊天的模型,注入了顶多胡说几句;一个能跑 shell、能改库、能发邮件、能转账的 agent,注入一旦得手就是真实破坏。所以铁律和本章一脉相承:**把一切外部内容当不可信输入;真正的硬约束(沙箱、最小权限、白名单、人工确认)必须落在『模型管不到、注入绕不过』的结构层,而不是写在提示词里求它别学坏。** 提示词是劝说,结构才是约束。 --- ## 🎯 随堂检验 --- ## 本章小结 - **核心论断**:**安全是结构,不是补丁。** 不是上线前加一道扫描、出事后打一个补丁,而是画第一张架构图时就用 STRIDE 系统性地问「谁会怎么攻击我的每条数据流、每道信任边界」。坏事几乎总发生在**信任边界**上。 - **纵深防御 + 零信任**:别因为「在内网」就信任——「从不信任,始终验证」(BeyondCorp 的实践);层层设防,配合**最小权限**,让单点被破不等于全盘沦陷。 - **爆炸半径与隔离**:别只问「怎么不被攻破」,要同时问「被攻破能炸多大」。隔离(承接 [12](12-为失败而设计.md))就是把「一次失误的代价」关进小格子。 - **多租户隔离谱系**:池化 → 桥接 → 竖井,是成本 vs 隔离强度的取舍;数据隔离从行级到物理级软到硬。**租户串扰是头号事故,而隔离绝不能靠「每个开发者记得加 tenant_id」,必须由结构强制保证。** - **密钥管理**:集中保管、定期轮换、最小暴露;**secrets 绝不进代码库、不进日志**——任何会被 grep / commit / 打日志的明文,都该假设已泄露。 - **供应链安全**:你没写的代码(依赖、构建、CI/CD)才是最大攻击面;信任是传递的,**一个被投毒的依赖能波及全网**(xz / Log4Shell / SolarWinds)。锁版本、出 SBOM、扫漏洞、最小化依赖、可复现构建。 - **合规即架构**:数据驻留、审计留痕、可删除权(GDPR)是**结构性约束**,从第一天就要织进架构,不是事后开关。 - **AI / vibe coding 视角**:两条新攻击面——① AI 幻觉依赖催生 **slopsquatting**、没人审的 vibe-coded 代码默认不安全;② **提示注入是 agent 的头号威胁(OWASP LLM01)**,EchoLeak 已是实锤。能让 agent 动手的工具权限,把安全从「功能」逼成「结构」:**硬约束必须落在模型管不到、注入绕不过的结构层。** > **承上启下**:从 [10 · 分布式的硬道理](10-分布式系统的硬道理.md) 一路走来,进阶篇把分布式、失败、规模、演进、安全这些「做大做关键才露獠牙」的硬骨头逐一啃了下来。而贯穿全程的那条主线——**实现越来越廉价,判断越来越值钱**——在最后一章会被推到台前。下一章(进阶篇收官 capstone)《[17 · 大模型时代的架构判断](17-大模型时代的架构判断.md)》,我们把全篇收拢:当 AI 能写绝大部分实现,架构师真正不可替代的判断力,究竟是什么、又该如何在新时代里继续磨利。