Files
cpa-plugin/docs/modules/security.md
T

17 KiB
Raw Blame History

安全模块

1. 安全目标

cpa-ext 处理三类高价值资产:下游用户 Key、金额账本、上游 OAuth/Auth。它又以 native dynamic library 运行在 CPA 进程内,因此安全目标不只是“接口有密码”,而是:

  1. 未通过 cpa-ext 认证和金额准入的用户不能调用上游;
  2. 插件缺失、panic、fuse、配置失败或数据库故障时不能退化成免费放行;
  3. 一个下游 Credential 只能读取和消费其授权的 BillingAccount
  4. 金额、价格版本和账本不能被静默覆盖或重复结算;
  5. downstream Key、Management Key、HMAC keyring、OAuth Token 和请求内容不泄露;
  6. 管理写操作可追责、可回滚且不能通过 CSRF/SSRF/路径注入扩大权限;
  7. native 插件故障不应无限阻塞、退出或破坏 CPA 进程。

2. 信任边界

Untrusted user/client
        │ downstream key + model request
        ▼
Reverse proxy / readiness gate
        │
        ▼
CLIProxyAPI HTTP + auth pipeline
        │ in-process C ABI / JSON RPC
        ▼
cpa-ext native plugin ───── SQLite + HMAC keyring
        │ host callbacks
        ▼
CPA Auth manager / provider network

Administrator ── CPA Management auth ── cpa-ext Management API
Browser resource ── unauthenticated static GET / user-key-protected GET

信任结论:

  • plugin binary 是与 CPA 同权限的受信代码,不是隔离沙箱;
  • Management Key 信任的是 CPA 管理员,不自动代表具体个人;
  • ResourceRoute 默认完全不可信;
  • scheduler candidate 是宿主过滤后的瞬时候选,不是完整账户清单;
  • provider response、models.dev、Auth 文件 display metadata 和所有 HTTP 输入都必须验证。

3. 必须始终成立的安全不变量

  • 生产 frontend_auth_provider_exclusive=true
  • CPA 原生 api-keys 中不保存任何分发给用户的 Key
  • 未绑定账户/套餐/价格默认拒绝;
  • 核心依赖不健康时新请求 fail closed
  • 用户核心额度单位只有 Money
  • 账本 append-only,余额只是可重建投影;
  • secret 明文只在签发/轮换首次响应存在;
  • 下游 Key 不出现在 URL、日志、事件、数据库明文字段或前端持久存储;
  • 上游 Auth 原文只允许存在于最小 provider adapter 的短生命周期缓冲;
  • 所有业务拒绝都走 CPA 实际会执行的终止协议;
  • 权限由服务端 scope 构造,不能信任调用方传入 account/credential ID
  • 插件无法独自证明的宿主状态由部署网关强制检查。

4. 当前 CPA 的 Critical 限制

4.1 Exclusive 只在插件 active 时存在

当前 CPA 在插件未加载、被 disable、register/reconfigure 失败或 fuse 后会重建 frontend providers。exclusive 可能被清除,CPA 原生认证重新生效;零 provider 还可能走 legacy 放行路径。

因此“插件声明 exclusive”不是完整安全边界。生产必须同时配置:

  1. 一个高熵、随机、只由运维离线保管的 CPA native sentinel key
  2. 不把 sentinel 发给用户、前端、自动化调用方或日常网关;
  3. 所有用户只获得 cpa-ext 自管 Key
  4. 外部 gateway 在 cpa-ext readiness 和负向认证探针通过前不开放用户端口;
  5. 插件 route 消失、状态不健康或负向探针异常时立即撤流量。

sentinel 的作用是防止“零 native provider”变成隐式放行,并确保插件失效时普通用户 Key 仍不被 CPA 原生认证接受。它不是备用共享 Key。

4.2 Reconfigure 失败会撤掉 active record

当前 Host.ApplyConfig 中,已经加载的插件如果 plugin.reconfigure 返回错误,会被排除在新 active records 外,随后可能清除 exclusive。

所以已注册后的无效热配置必须:

  • 插件内部保留 last-known-good Runtime
  • 记录 reconfigure_rejected
  • 仍返回 success registration 和上一次相同 capability shape
  • 由 API/诊断提示配置未生效。

只有 CPA 宿主先实现“失败保留旧 active record”后,插件才可以安全返回 reconfigure error。

4.3 Interceptor error/panic 是 fail-open

CPA 对 request interceptor 的 Go error 或越过边界的 panic 会记录并继续原请求。所有预期拒绝、DB/账本/价格依赖故障必须返回:

RPC envelope: ok=true
RequestInterceptResponse:
  Terminate=true
  StatusCode=合适的 4xx/503
  ResponseBody=脱敏稳定错误

dispatcher 在插件内部 recover,并把 panic 转为上述 503 termination。若 panic 已越过 native 边界导致宿主 fuse,纯插件无法保证当前请求,必须由 gateway/sentinel 防线兜底。

4.4 Scheduler error/panic/无效响应会 fallback

strict binding 不能只依赖 scheduler.pick。每个 Execution 的 after-auth interceptor 必须复核实际 selected_auth_id,不属于允许集合就正常返回 Terminate=true。宿主 fuse 场景仍依赖外部门禁。

4.5 Handler 前宿主会 ReadAll

frontend auth 和 Management handler 在插件校验前已经收到完整 Body copy。插件内部 413 不能保护 CPA 内存,反向代理/CPA HTTP 层必须限制 body、header、连接数和读取时间。

4.6 Resource routes 未鉴权

ResourceRoute 只能放静态资源、最小 readiness 和自行校验 downstream Key 的用户只读 JSON。绝不允许 host.auth.get/save、管理员数据、备份、价格发布或任意写操作。CPA 官方 auth-files callback example 是能力演示,不是生产安全模板。

5. 下游 Credential 安全

5.1 Key 格式与生成

建议格式:

cpae_<public_id>_<random_secret>
  • public_id 只用于快速定位 Credential,可公开但不可授权;
  • random_secret 至少 256 bit CSPRNG
  • 完整 Key 只在首次签发/轮换响应显示一次;
  • 前缀和 preview 用于人工识别,不足以认证;
  • Key 必须可按 SecretVersion 独立撤销、过期和轮换。

5.2 存储与校验

数据库保存:

  • public ID
  • HMAC-SHA-256(keyring_secret, full_key) digest
  • hmac_key_id
  • preview、状态、创建/过期时间。

不保存可恢复明文。HMAC secret 来自环境指向的受限 secret file/OS secret store,不能写入 CPA YAML、SQLite、日志、metadata 或备份包。比较使用 constant-time compare。

高熵随机 Key 使用 keyed digest 足够;不能改为普通无密钥 SHA-256。若未来允许用户自选短密码,必须改用专门 password KDF,不能沿用此模型。

5.3 Keyring 轮换

  • keyring 每个 secret 有稳定 key ID
  • 新 Credential 使用 active key
  • 旧 digest 继续按原 key ID 校验;
  • 缺失未知 key ID 时认证 fail closed 并使 readiness 失败;
  • 不能启动时自动生成替代 secret
  • SQLite 与 keyring 分开加密备份、成对恢复并校验 fingerprint。

6. 身份认证与授权

6.1 模型请求

frontend auth 只完成 Credential lookup 和 principal 建立。金额、状态、模型、tier、endpoint、并发和 route policy 在 before-auth/Core 准入再次检查。

principal metadata 只携带 opaque Credential/BillingAccount IDs 和最小 scope,不携带 raw Key、digest、余额或可篡改 JSON。

6.2 用户查询

用户 Resource API 每次自行校验 Bearer downstream Key,并由 Credential 反查服务端 scope

  • 共享 BillingAccount 可以看共享金额余额;
  • 默认逐请求只看当前 Credential;
  • 调用方传入 ID 只能缩小,不能扩大 scope;
  • unknown/revoked/expired 使用统一 401,防止枚举;
  • 登录尝试按 IP + public ID preview + 全局维度限流;
  • Key 不进入 URL/cookie/localStorage,请求和响应 no-store。

6.3 管理员

Management API 继承 CPA Management middleware

  • 生产 remote-management.allow-remote=false,或只经受信 TLS/mTLS 管理网关暴露;
  • 使用随机高熵 Management Key,不复用 sentinel/downstream/HMAC secret
  • 浏览器只在当前页面内存保存 Key,刷新即失效;
  • cpa-ext 不把入站 Authorization 传给 Core、日志、host HTTP 或 provider
  • 每个写操作需要 reason、revision 和审计;
  • CPA 共享 Key 不能提供真实个人 RBAC。需要多管理员角色时增加可信 identity gateway/sidecar。

测试环境可以按用户约定使用 000 便于联调,但仅限 loopback、无真实 OAuth/生产数据、端口不对外开放的临时环境。任何可被其他机器访问或接入真实账户的部署都禁止使用 000

7. 租户与数据隔离

  • Repository 查询必须接收已验证 ViewerScope,不接受裸 account ID 作为授权;
  • 管理 DTO 与用户 DTO 分开定义,不能返回管理员对象后由前端隐藏;
  • 缓存键包含 viewer scope,不能跨 Credential 复用敏感响应;
  • cursor 绑定 scope 和过滤条件,不能拿另一账户 cursor 继续翻页;
  • 导出任务固定 scope、时间范围和创建者;
  • 账本 adjustment、route binding、价格发布均检查对象 revision;
  • 统计聚合中低基数结果也不能绕过用户 scope。

8. 上游 Auth 与网络安全

8.1 最小读取

账户目录只调用 host.auth.list/get_runtimehost.auth.get 返回原始 Auth JSON,只能由 provider-specific quota adapter 在管理员触发的有界命令中读取。

raw Auth 数据:

  • 不进入 domain DTO、generic map、error、trace 或数据库;
  • 不跨 goroutine/channel
  • 从最小缓冲提取必要字段后尽快覆盖并释放;
  • 任何失败只输出分类,不包含 body/header/token。

Go/OS 无法保证内存立即物理清零,因此更安全的长期方案是 CPA 提供 provider-scoped quota callback,不把 secret 交给插件。

8.2 SSRF 防护

Quota/provider adapter 的 URL、method、headers 和 body 均由代码定义:

  • 只允许 HTTPS
  • 精确 allowlist host + path,不允许重定向到新 host;
  • 禁止 localhost、私网、link-local、file/unix scheme
  • 调用方只能传内部 UpstreamAccountID
  • 限制响应 body 和 header 大小;
  • 独立超时、并发和速率限制;
  • 不把 CPA Management Authorization 转发给 /api-call 或第三方;
  • 代理配置由宿主/运维控制,不允许用户覆盖。

9. 金额和数据完整性

  • 金额/费率使用整数定点和有理数倍率;
  • PriceVersion 发布后不可变;
  • canonical Usage revision 使用 CAS/idempotency
  • 账本 append-only,删除 API 不存在;
  • adjustment/refund 是新账本项,必须含 reason/actor
  • DB 事务同时写事实、账本、projection event/outbox
  • checkpoint 可从事实重建并做总额 reconciliation
  • 时间来自服务端 UTC,调用方时间只作为未经信任 metadata;
  • 重复、乱序、迟到 callback 不能重复扣费;
  • SQLite 失败后不得改用内存余额继续免费运行。

数据库文件、WAL、备份目录只允许 CPA 运行用户访问。备份使用 SQLite online backup API;恢复前校验 schema、integrity、账本和 keyring fingerprint。

10. 管理 API 与浏览器安全

  • exact route + method 二次校验;
  • strict JSON、字段/集合/响应大小限制;
  • 所有动态 HTML/文本安全转义;
  • React 只用 text node,不使用 dangerouslySetInnerHTML
  • CSP、nosniff、no-referrer、no-store
  • 默认无 CORS
  • 不注册 service worker
  • CSV 防公式注入;
  • 用户和管理员 secret 不进入 query/fragment
  • Management JSON 的宿主 entity 转义按 api.md 兼容一次,不能把 entity decode 后内容当 HTML。

如果未来使用 cookie session:必须在独立 authenticated route/sidecar 设计 HttpOnly、Secure、SameSite、CSRF token、Origin 校验、登录限流和 session revoke。当前插件 resource GET 不具备这些条件。

11. Native 插件与供应链

11.1 运行时

  • ABI 入口只做 byte copy、JSON envelope、C buffer ownership
  • C 返回内存由匹配的 plugin free 释放,不返回 Go heap pointer
  • 不使用 os.Exitlog.Fatal 或故意 panic
  • dispatcher/Core 边界 recover,错误脱敏;
  • shutdown 幂等且有界;
  • 不持锁执行 callback、网络或慢 SQL
  • 双 Go runtime/SQLite/worker 必须通过 24h soak
  • gate 未通过前动态库零长期 worker,复杂任务移到 sidecar/运维命令;
  • 二进制升级排空并重启 CPA,不依赖 hot reload 回收旧 runtime。

11.2 发布物

  • 每 OS/arch 独立构建;
  • 记录 Go/C toolchain、CPA commit、ABI/schema 和源码 commit
  • 归档只含动态库、LICENSE/NOTICE、版本元数据;
  • 发布 SHA-256 checksums,最好增加签名/透明 provenance
  • 安装前验证 checksum、文件名和目标目录;
  • 不从未知 plugin store/source 安装;
  • 发布包不含 config、SQLite、keyring、日志、Auth 或测试 secret
  • 复制两个 MIT 项目代码时维护 THIRD_PARTY/NOTICE 和来源 revision。

12. 日志、诊断与审计

禁止记录:

  • Authorization、X-Management-Key、Cookie
  • downstream full key/HMAC digest
  • OAuth/API token、Auth JSON、代理凭证;
  • Prompt、Response、原始失败 body
  • database absolute path(普通用户可见日志);
  • raw config YAML。

允许记录:

  • stable opaque IDs
  • Key preview
  • provider/model 分类;
  • Money、状态、事件 ID
  • 脱敏 error code
  • callback/RPC 大小和耗时;
  • readiness component 状态。

日志参数采用结构化白名单 DTO,不接受任意 wire object。安全审计和调试日志分开保留,审计不可由普通“清空日志”操作删除。

13. 可用性与资源滥用

  • downstream/API Key 级并发和速率限制;
  • BillingAccount 默认并发 1
  • Management 写操作串行化到明确对象/revision,不用全局长锁;
  • query 时间范围、分页、维度和导出上限;
  • SQLite busy timeout 有界,锁等待产生 metric
  • quota refresh 去重、批量/并发/冷却;
  • stream callback 性能门禁,正常 chunk 成本不能随 prompt/history 线性放大;
  • 请求体上限在反向代理先执行;
  • readiness unhealthy 时撤流量,不能让重试风暴打满 DB/provider。

14. 威胁与控制矩阵

威胁 主要控制
插件缺失/fuse 后免费调用 exclusive + sentinel + required readiness gateway + 负向探针
DB 故障被 interceptor error 忽略 success envelope + Terminate 503frontend auth 第二道 fail closed
strict binding 回退其他账户 scheduler 决策 + after-auth 实际 Auth 验证
用户 Key 枚举/泄露 高熵 Key、HMAC、统一 401、限流、no-store、日志白名单
跨租户查询 服务端 ViewerScope、scope-bound cursor/cache、独立 DTO
余额/价格篡改 revision、不可变 PriceVersion、append-only ledger、审计
quota SSRF/凭证代理 固定 provider adapter、HTTPS host/path allowlist、无任意 URL/header
Management Key 泄露 loopback/管理网、TLS、高熵、仅内存、响应/日志删除
大 body/慢请求 DoS 入口代理 limit/read timeout、分页/响应预算
恶意动态库/供应链 checksums/signature/provenance、受信 registry、最小发布包
SQLite/backup 被复制 OS ACL、加密备份、keyring 分离、secret 不落库
重复/迟到 Usage 重复扣费 event/idempotency key、canonical revision、CAS、差额账本

15. 事件响应

15.1 downstream Key 泄露

撤销对应 SecretVersion → 刷新内存 snapshot → 确认新请求 401 → 签发新版本 → 审计受影响窗口 → 检查异常金额/路由。

15.2 Management Key 泄露

立即从 gateway 撤下管理入口 → 轮换 CPA Management Key → 审查价格、adjustment、Key、route、backup/export 审计 → 必要时恢复已验证 DB backup。

15.3 HMAC keyring 泄露

视为所有 downstream Key 校验材料泄露。先撤流量,再增加新 active HMAC key、轮换所有 Credential SecretVersion、保留旧 key 仅完成迁移,最后撤销旧 key。仅轮换 keyring 而不轮换用户 Key 不足以消除风险。

15.4 OAuth/Auth 泄露

在 CPA/provider 侧撤销 OAuth → 将 UpstreamAccount 标不可用 → 检查 quota adapter、日志、dump 和 backup → 重新登录并人工 reconciliation,不自动继承旧绑定。

15.5 账本/数据库疑似损坏

gateway fail closed → 保存现场副本 → integrity/reconciliation → 用匹配 keyring 的已验证备份离线恢复 → 从权威事实重建投影 → 对差异使用显式 adjustment,不手改账本行。

16. 验收标准

  • 插件缺失、disable、register/reconfigure 失败、panic、fuse 和 Home 模式均无法让普通用户 Key 访问上游;
  • interceptor/scheduler 的 error、panic、timeout 和无效响应完成故障注入;
  • DB、keyring、price、ledger 不健康时新请求 fail closed
  • downstream/Management/HMAC/OAuth secrets 不出现在数据库、日志、API、core dump 测试样本或发布包;
  • 用户跨账户 ID/cursor/cache 尝试全部失败;
  • resource route 枚举证明没有未鉴权写操作或管理员数据;
  • SSRF 测试覆盖重定向、私网、DNS 变化、超大响应和恶意 URL;
  • 金额/价格/账本篡改和重复事件被 revision/hash/idempotency 检测;
  • 入口大小限制在 CPA ReadAll 前生效;
  • 动态库检查、checksum、NOTICE 和构建 provenance 完整;
  • 安全事件均有可执行 runbook 和审计证据;
  • 所有门禁纳入 test-plan.md,而不是只写在文档里。