护栏系统的自研与外购:内容审查 API 已成为安全关键路径上的核心依赖
你为了加快上线速度而购买的托管审核 API,现在已经成了你安全关键路径上的一个同步外部依赖。这句话并非观点——而是被如实重绘后的架构图。在供应商服务降级的日子里,你面临两个选择,且两者都很糟糕:故障开启(fail open),此时护栏在最需要的时候恰恰失效了;或者故障关闭(fail closed),护栏的故障直接导致了功能的停摆。大多数团队是在事故发生时才发现自己选了哪一个,而不是在此之前。
团队选择供应商的原因并非因为懒惰。在内部构建内容分类器、提示词注入检测器和 PII 脱敏工具,看起来像是背离实际产品开发的六个月漫长弯路,而供应商通常提供免费额度和五分钟即可完成的集成。这种集成确实很快。但随之而来的架构后果是,第三方现在介入了每一次面向用户的生成请求路径,其可用性、延迟和行为特征是你无法控制且未曾建模的。
这篇文章的主旨是将这一决定视为架构决策,而非采购决策。
故障开启 / 故障关闭的抉择是关键决策,而非默认设置
当托管护栏超时、返回 5xx 错误或开始表现异常时,你的代码必须做出响应。两个默认选项是故障开启(未经检查直接放行请求)和故障关闭(拦截请求)。团队几乎总是通过一行配置更改来选择其中之一,且几乎总是在没有记录权衡代价的情况下做出的选择。
故障开启意味着在供应商服务降级的瞬间,你的安全控制就成了摆设。护栏专门设计的防范请求会在你最需要检查的窗口期长驱直入,因为护栏的失效模式往往与流量异常相关,而流量异常通常又与滥用行为相关。故障关闭则意味着你将产品的可用性与供应商绑定。护栏 99.9% 的 SLA 设定了你功能 SLA 的上限,“护栏供应商宕机”会演变成你的值班人员必须响应的事故,尽管你的模型或数据本身毫无问题。
正确的答案很少是“二选一”。这是一个与特定检查的尾部风险相关的、针对每个策略的决策。一个剥离明显 PII 的输出脱敏层或许可以故障开启,辅以日志记录和异步重试,因为在 10 分钟窗口内漏掉脱敏的代价虽然烦人,但是可控的。而一个针对能退款或发邮件的工作流的越狱(jailbreak)分类器,则应该故障关闭,因为漏掉一次越狱攻击可能会导致六位数的事故损失。这种决定应该体现在代码中,并附带解释权衡的注释,而不是隐藏在 SDK 的隐式默认行为里。
