# 安全白皮书

> 初稿（2026-11-05）。描述当前版本实际具备的安全控制，供客户的安全评审人员使用。组件与网络拓扑见同一资料包中的[《产品架构与部署白皮书》](/trust/architecture)；备份、加密备份的密钥保管与恢复见[《备份与灾备说明》](/trust/backup-dr)。本文不含任何服务水平承诺。

## 1. 租户隔离

### 1.1 数据库：行级安全

| 控制 | 做法 |
|---|---|
| 运行时角色 | 平台以独立的数据库角色处理请求：不是表的所有者、不是超级用户、`NOBYPASSRLS`。表结构变更只在升级时用所有者凭据执行。 |
| 每张租户表 | `ENABLE` 并 `FORCE ROW LEVEL SECURITY`（`FORCE` 让策略对表所有者也生效），策略只放行与当前租户一致的行。 |
| 租户上下文 | 每个租户范围的数据库操作都在一个事务里进行，事务开始时用事务级会话变量写入当前租户；查询即使漏写租户条件，数据库也只返回本租户的行。没有设置租户的调用直接报错。 |
| 平台范围 | 运营接口等平台自身的代码路径使用单独的“系统”范围读写跨租户数据（例如租户列表）；租户的请求不会进入这个范围。 |
| 托管应用自己的库 | 声明了自有数据表的应用使用自己的数据库角色（同样 `NOBYPASSRLS`）；平台在上架时检查它的迁移脚本，建表而不 `FORCE ROW LEVEL SECURITY` 的版本被拒绝。 |
| 分析工具 | 分析与报表工具各用一个非超级用户角色，只能读按租户过滤的视图，不能连接审计库。 |

审计库的运行时权限见第 4 节。

### 1.2 跨租户测试

- 真实 Postgres 上的集成测试：一个租户写入的数据，另一个租户无论通过存储层还是直接 SQL 都读不到；不设租户上下文时也读不到。
- 真实部署栈上的端到端检查：另一租户的管理员看不到本租户的成员、审计事件、Webhook、服务账号、文件和模型用量，也不能操作本租户的用户。
- 接口与角色的权限矩阵（管理员、审计员、分析员、成员、无令牌）在真实栈上自动逐项校验。
- 租户清除的守卫测试：任何带租户列、却不会随租户删除的表都会让测试失败，除非登记了保留理由（见第 6.4 节）。

### 1.3 对象存储

使用平台存储时，租户的文件统一放在以该租户标识开头的前缀下，平台读写时由服务端加上前缀。租户可以改用自己的 S3 兼容存储桶（自带存储），此时文件进入客户自己的桶，访问密钥保存在密钥库，数据库只存引用；连接地址在连接时按实际解析的地址校验，回环与链路本地（含云元数据）地址始终被拒绝。

### 1.4 托管应用的隔离

| 层 | 做法 |
|---|---|
| 进程 | 单机部署以容器运行：非 root 用户、只读根文件系统、丢弃全部 Linux capabilities、禁止提权、内存 / CPU / 进程数限额、不发布端口。Kubernetes 部署以 Pod 运行：同样的限制，加 `RuntimeDefault` seccomp、不挂载服务账号令牌，命名空间启用 Pod 安全标准 `restricted`。可选为全部应用进程指定 gVisor 等隔离运行时，默认不启用。 |
| 镜像 | 清单里的镜像必须按摘要固定（`name@sha256:…`）；平台只拉取、不构建。运营者可以在镜像策略里要求签名校验（cosign 公钥）和漏洞扫描（超过设定严重度即阻止上架），默认关闭；开启而工具未安装时校验失败，不会放行。 |
| 网络 | 单机部署：应用所在的内部网络没有到外网的路由。Kubernetes：NetworkPolicy 只允许平台访问应用，应用只能访问平台的 API、出网代理和集群 DNS；有自有数据表的应用另有一条策略只放行到数据库端口。 |
| 出网 | 唯一出口是平台内置的出网代理。只有在清单里声明了出网主机的应用版本才拿到代理地址和它自己的凭据，代理只放行声明过的主机和端口，并拒绝解析到私网、回环或元数据地址的连接；被拒的连接写入平台审计。可整体关闭出网，也可经企业代理出去。 |
| 身份 | 应用收到的租户、用户、角色等请求头由平台网关在验证令牌后重新设置，客户端带来的同名头会被丢弃。 |

当前托管运行时是所有租户共用一个应用进程（按版本），租户之间的数据隔离有一部分依赖应用代码本身；因此目前只适合上架受信任开发者的应用（见第 9 节）。

## 2. 身份与访问

### 2.1 身份源与租户

- 身份源是 Zitadel（OIDC）。每个租户对应 Zitadel 中的一个组织，平台记录二者的一一映射；令牌里的租户声明缺失时按组织映射确定租户。
- 租户角色只在用户自己的组织内授予时才生效。
- 租户被暂停后，该租户的令牌在 15 秒内失效，多副本之间通过数据库通知即时同步。

### 2.2 角色

| 角色 | 能做什么 |
|---|---|
| `tenant.admin` | 组织管理的全部功能 |
| `tenant.auditor` | 组织管理只读，加审计日志的查询、校验、导出 |
| `tenant.analyst` / `tenant.member` | 使用工作台与应用，不能进入组织管理 |
| `platform.ops` | 运营台全部功能（开通、暂停、注销租户等） |
| `platform.support` | 运营台只读，所有改动接口返回 403 |

在平台角色之下，租户还可以自定义业务角色：按部门树授予权限点，并为每个权限指定数据范围（本人、本部门、本部门及下级、指定部门、全部），对字段可设掩码或隐藏。对象引擎在每次记录操作时检查。

### 2.3 登录与多因素

- 登录页支持密码、TOTP 验证器、通行密钥（WebAuthn，必须经过用户验证才能作为第一因子）。组织可以在登录策略中强制多因素认证、允许或禁止通行密钥。
- 登录尝试按来源地址和登录名限流，多副本共享计数；连续失败会锁定账号，由管理员解锁。验证码流程对已知和未知账号的回答相同。
- 高风险操作（如创建或轮换服务账号密钥、Webhook）在执行前要求本人 5 分钟内有过一次新的登录；平台核对身份令牌的签名、签发方、受众、主体、nonce 和登录时间。

### 2.4 服务账号与 API 令牌

- 服务账号是机器用户，用 client-credentials 换取令牌；只能持有 `tenant.member` / `tenant.analyst` / `tenant.auditor`，不能是管理员。密钥只在创建和轮换时返回一次。
- 个人 API 令牌只用于模型接口：平台只保存其 SHA-256 摘要，必须有有效期（默认 90 天，最长 365 天），可随时吊销，组织未开启外部接口时一律拒绝。令牌本身不带角色，以持有人当前的角色为准。
- Webhook 投递带 HMAC-SHA256 签名和时间戳；默认只允许公网 `https://` 目标，并在连接时再次校验地址。

### 2.5 助理的委托令牌

助理调用平台接口时使用平台签发的委托令牌，形态参照 RFC 8693：`sub` 是用户本人，`act` 是助理，`scope` 为“读取、起草”，有效期 10 分钟，每次工具调用重新签发。委托令牌只能携带租户角色，并且：

- 被所有管理、审计、运营接口拒绝（403）；
- 不能应用或丢弃草稿：应用只接受用户本人的会话；
- 读取记录时加密字段显示为已加密，不能导出数据。

### 2.6 签名密钥与 JWKS

平台用 Ed25519 密钥签发给应用的断言、页面会话、租户 / 员工令牌、委托令牌和支持令牌；公钥以 JWKS 发布，每把带 `kid`。轮换不停机：重叠期内新钥匙签名、旧钥匙仍可验证，重叠期结束后旧钥匙签的令牌被拒（401）。两把钥匙的 `kid` 必须不同，否则启动失败。OIDC 登录令牌的签名密钥由 Zitadel 自己管理。

### 2.7 运营入口

运营台和运营工具（监控、工作流、模型网关、密钥库界面等）只在单独的运营端口上提供，默认只绑定本机地址，部署时绑到管理网络。所有运营站点先经过单点登录：登录时由身份源检查运营项目的角色，没有运营员或支持角色的人无法完成登录；运营 API 再按角色检查。运营 API 在平台的单独监听上，公网站点不转发到它。

## 3. 数据加密

### 3.1 传输中

- 所有对外站点由网关终止 TLS：公网证书经 ACME 自动签发，内网或离线部署使用网关自带的 CA。响应带 HSTS（一年，含子域名）、`X-Content-Type-Options: nosniff` 等安全头。
- 网关之后，各组件之间（平台、数据库、密钥库等）在部署的内部网络里通信，单机 Compose 部署中这段链路不加密。

### 3.2 敏感字段：信封加密

- 对象引擎里标为敏感的字段逐条记录加密：每条记录一把数据密钥，数据用 AES-256-GCM 在平台内加密，数据密钥只以被主密钥包裹的形式保存。
- 生产后端是 OpenBao：主密钥在其 transit 引擎中（`aes256-gcm96`），不离开 OpenBao，可轮换，旧版本仍可解密。平台以单独的 AppRole 访问 OpenBao，策略只授予它用到的路径：生成和解开数据密钥、读写平台自己的密钥路径、删除数据密钥与租户凭据、为托管应用签发数据库账号。
- 本地开发和单机精简部署可用内置后端：主密钥来自部署配置，包裹后的数据密钥存在 Postgres。本地机房和云环境包默认使用 OpenBao；Helm chart 的默认值是内置后端，生产部署应改为 OpenBao。
- 密钥库在平台中是可替换的接口；当前提供 OpenBao 和内置两种实现。

### 3.3 销毁即不可读（crypto-shred）

擦除记录或注销租户时，先永久删除对应记录的数据密钥（OpenBao 中删除全部版本），再删除数据行。备份里残留的密文因此无法再解密。

### 3.4 凭据与密钥库的保管

- 租户的凭据（自带存储密钥、自带模型密钥、Webhook 密钥等）存入密钥库，数据库只存引用；审计里只记录端点等非敏感信息。
- OpenBao 初始化生成 5 份解封份额，门限 3。安装后应立即把份额分发给不同的保管人离线保存，并删除主机上的初始化文件；root token 同样离线保存或吊销。OpenBao 每次重启都处于封存状态，此时平台的就绪检查返回不可用，直到保管人解封。
- 托管应用访问自有数据表时，默认从 OpenBao 拿短期数据库账号（默认 1 小时续期，最长 24 小时），版本停止后吊销。

## 4. 审计

| 控制 | 做法 |
|---|---|
| 范围 | 每个租户一条审计链，平台自己另有一条。管理动作、草稿应用、支持访问、出网拒绝、租户清除等都写入。对象数据的改动只记录改了哪些字段，不记录字段值。 |
| 只追加 | 数据库触发器拒绝对审计表的 `UPDATE`、`DELETE`、`TRUNCATE`，对所有角色（包括表所有者）都生效；平台的运行时角色只有 `SELECT` 和 `INSERT`。审计库可以单独部署。 |
| 哈希链 | 每条事件带前一条的哈希和自己的 SHA-256 哈希；校验接口逐条重算，库下篡改（例如从改过的备份恢复）会被发现。 |
| 写不进审计 | 改动请求的审计写入失败时，接口不会返回成功，而是返回 500 并说明“已执行、未记录”；支持访问的请求在审计写不进时直接被拒绝。 |
| 归档 | 已满 24 小时的事件按段（每段最多 1 万条）封存到数据库之外的对象存储，段与段首尾相连，数据库与归档互相核对；链本身有问题时拒绝封存。可选启用对象锁定（专用桶 + 保留天数，`COMPLIANCE` 模式下保留期内连存储管理员也不能删除或修改原版本）；存储不支持时降级为“独立副本 + 哈希核对”，并在界面上提示未锁定。 |
| 在线瘦身（可选） | 可让已封存且超过设定天数的事件从数据库移除；只能通过一个数据库函数进行，且只删已有归档段覆盖的范围，移除前重新校验归档。之后校验与导出自动从归档读取。 |
| 谁能读 | 租户的 `tenant.admin`、`tenant.auditor`：查询、校验、导出、下载归档段并做完整校验。委托令牌被拒绝；供应商支持会话只能查询和校验，不能导出或下载（§5）。审计事件也可通过 Webhook 推送到客户自己的系统。 |

## 5. 供应商支持访问（break-glass）

平台支持人员默认没有任何租户数据的访问权限。流程：

1. 支持或运营人员在运营台为某个租户提出申请，写明原因（至少 10 个字，客户可见）和时长（15–240 分钟）。
2. 客户的 `tenant.admin` 在组织管理的“支持访问”中查看并批准（可缩短时长）或拒绝；审计员可以查看申请，不能批准。申请、批准、拒绝都写入客户自己的审计链，也可经 Webhook 推送。
3. 批准后，申请人可以换取支持令牌：每次最长 15 分钟，且不超过授权截止时间。
4. 支持令牌无论声称什么，都只带审计员角色；只接受 `GET` / `HEAD` 请求；加密字段显示为已加密；可以查看审计事件，但不能导出任何数据（业务记录、审计记录、审计归档）；对运营接口无效。
5. 每一个请求（方法与路径）都写入租户的审计链；无法写入时请求被拒绝。
6. 客户可以随时撤销，撤销通过数据库通知同步到所有副本，令牌在下一个请求即失效；授权到期自动失效。

没有绕过客户批准的、经过产品的访问方式。部署方运维人员直接登录主机或数据库，不在本流程内：谁能这样访问、每次访问怎么记录、记录放在哪里、客户能要求看什么、哪些由合同约定，见《运维人员访问说明》（operator-access.md）。私有化部署中这部分由客户自己掌握。

## 6. 数据合规

以下功能覆盖对象引擎（“业务数据”）中的记录。

### 6.1 数据清单

租户管理员可以看到每个数据类型的记录数、哪些字段是敏感字段、最早的记录时间，以及该类型的保存期限。

### 6.2 保存期限

每个数据类型可设 1–3650 天。平台每天自动清理一次过期记录（也可预览、立即执行）；仍被其他记录引用的记录跳过；每次执行写审计。

### 6.3 个人信息主体请求

管理员输入邮箱、手机号或姓名，平台在所有数据类型中查找匹配的记录，包括加密字段（手机号忽略写法差异）。结果可导出为 JSON，可擦除：擦除前可预览，执行需重复输入确认；擦除时销毁这些记录的数据密钥。审计链只记录改动了哪些字段，因此擦除后审计中没有残留的值。

### 6.4 租户注销与清除证书

1. 运营方先暂停租户，再注销（需重复输入租户标识）：租户被锁定，进入宽限期（默认 7 天）。
2. 宽限期满后执行清除，依次：销毁加密字段的密钥并删除业务数据；删除平台存储中该租户前缀下的文件和头像；删除密钥库中该租户的凭据；删除模型网关中的租户密钥；删除身份源中的组织及其用户；最后删除租户记录，数据库中引用它的数据随之级联删除。
3. 清除返回一份证书：租户、执行人、时间、每一步的结果；证书写入平台的审计链。某一步失败时租户保持“注销中”，证书标明失败的步骤，每一步都可重复执行。
4. 有意保留的：账务记录（账本、计量、发票），以及该租户的审计链和归档（不含个人数据的字段值）。客户自带存储中的文件属于客户，平台不删除。

### 6.5 模型调用的数据驻留

租户可以为模型调用设置数据区域（如 `cn`、`eu`、`us`）。运营者为每个模型供应商标注区域；只有区域相符的供应商的模型会出现在租户的可选列表中，指定了其他区域模型的调用被拒绝。标注为“任意区域”的供应商对所有租户可用，因此区域标注的准确性由运营者负责。

## 7. AI 与模型调用

### 7.1 模型网关

平台的所有模型调用，包括助理和后台功能，都经过模型网关（Bifrost），每个租户一个虚拟密钥。租户自带模型时，其供应商也注册在网关中，平台本身不直连模型供应商。

### 7.2 租户策略

| 项 | 做法 |
|---|---|
| 模型允许名单 | 不在名单内的模型调用被拒绝（403）。 |
| 数据驻留 | 见第 6.5 节。 |
| 额度 | 租户月度 token 额度、每人月度额度、按部门或角色的额度与可用模型；用完后调用被拒绝。 |
| 速率 | 租户每分钟请求数、每人每分钟请求数、每人并发数。 |
| 按人管控 | 管理员可暂停某个人的模型使用。 |

### 7.3 自带模型

租户可以填写自己的供应商地址、密钥和模型列表。密钥只写、存入密钥库；地址必须是 `https`，并做防 SSRF 检查（私网地址只在部署允许时可用）。

### 7.4 记录什么、不记录什么

| 位置 | 内容 |
|---|---|
| 模型网关日志 | 默认不记录提示词和回答，只留模型、虚拟密钥（即租户）、状态、延迟、token；日志保留 30 天。 |
| 链路追踪 | 网关链路中的消息内容在写入追踪存储前被删除。平台自己的模型调用链路只带模型、token、成本、租户、用户和会话标识；租户在模型策略中主动打开“记录内容”时才附带提示词和回答。 |
| 指标 | 指标中不含任何消息内容。请求数、token、计费、拒绝次数按租户计数；调用延迟只按模型和来源（平台 / 自带），首 token 延迟只按模型；对外模型接口的请求数只按接口类型、客户端类别和状态计数，不含令牌、个人和租户。 |
| 计量 | 按租户、天、模型、来源、个人记录 token 与金额，用于额度与账单。 |
| 对话 | 助理会话的消息作为租户数据保存在平台数据库中，受行级安全保护，随租户清除而删除。 |

### 7.5 助理只能读取和起草

助理通过委托令牌工作（第 2.5 节）：它可以读取数据和生成草稿，管理、审计、运营接口拒绝它，应用草稿只接受用户本人的会话。草稿由用户在拥有数据的应用里审阅后执行。

## 8. 软件供应链

| 项 | 做法 |
|---|---|
| 镜像签名 | 发布流水线构建平台镜像（`linux/amd64`、`linux/arm64`），用 cosign 无密钥签名，签名身份是厂商的发布工作流；Helm chart 同样签名。客户可在拉取后用 cosign 核对签名身份。 |
| SBOM 与来源证明 | 镜像构建时生成 SBOM 和最高级别的 provenance 证明，随镜像推送。 |
| 离线安装包 | 包内 `SHA256SUMS` 用厂商的 ECDSA P-256 私钥签名。安装脚本先用公钥验证签名，再逐个文件核对摘要，拒绝清单之外的文件、链接和特殊文件，加载镜像后与签过名的镜像清单逐个比对；任何一步不符即停止并说明原因。可以只校验、不改动任何东西。签名私钥不进安装包；**公钥由厂商另行交付**（合同附件、交付邮件等），不能只依赖包内的副本。 |
| 第三方版本 | 所有第三方组件的版本集中在一份版本清单（BOM）中，Compose、Helm 和离线包都从它生成；模型网关的镜像按摘要固定。 |
| 许可证 | 私有化许可证是 Ed25519 签名的文件，离线用厂商公钥验签，不做用量回传。安装和升级在启动任何服务之前先检查许可证；验签失败的文件不被信任，未授权的模块不启动。 |

## 9. 尚未提供

- 第三方渗透测试：尚未进行。
- 安全认证：没有等保 2.0 测评、ISO 27001、SOC 2 等认证或审计报告。
- 压测与容量验证：目标硬件上尚未进行。只有一份开发机上的性能基线（2026-11-10），不是容量承诺。
- 托管运行时对不可信第三方代码的隔离：gVisor 等隔离运行时未默认启用，没有按租户隔离的运行级别；当前只适合上架受信任开发者的应用。
- SCIM 用户与组织同步：没有。
- 企业身份源联邦（SAML 及真实企业 IdP）：只用模拟的身份源验证过。
- 云 KMS：密钥库没有云 KMS 的实现；OpenBao 没有自动解封，每次重启都要由保管人解封。
- 数据主体请求与保存期限：只覆盖对象引擎中的记录，不覆盖各业务应用自己的数据表。
