# 安全审查与修复报告（0.10.1 修订）

审查范围：`internal/*`（29k 行 Go）、`migrations/`、`deploy/`、`api/openapi/`。
审查方式：4 路并行深度代码审查 + 关键发现人工复核 + 全量单元测试/`go vet`。
日期：2026-08-12。以下条目均已在代码中修复并验证；未修复项作为已知限制列出。

## 高危（已修复）

### 1. 数据平面 SSRF：DNS rebinding 可把代理流量引向内网
`internal/gateway/proxy.go` 的 `http.Transport` 原先使用普通 `net.Dialer`，
Provider 主机名被 DNS 重绑到 169.254.169.254 / 10.x 后，网关会把带完整
Prompt 的请求转发到内网并回传响应。控制面有 `safeDialContext`，数据面没有。
**修复**：`provider.SafeDialContext`（拨号前对所有解析地址做公网校验、直连
已校验 IP 防止二次解析）统一供控制面与数据面使用；`Proxy` 新增
`SetAllowPrivateProviderURLs`，由 `ALLOW_PRIVATE_PROVIDER_URLS` 控制。

### 2. 网关 API Key 泄露给上游
`internal/provider/openai/adapter.go` 的 `Prepare` 只在配置了上游凭据时
`Set Authorization`，从不剥离客户端头——Provider 无 API Key 时，客户端
用于认证网关的完整 Bearer Key 被原样转发给上游。
**修复**：无条件 `Header.Del("Authorization")` 后再按需注入。

### 3. SSRF 判定网段不完整（CGNAT/6to4/NAT64 可绕过）
`isPublicAddress` 只用了 Go 内建分类，漏掉 100.64.0.0/10、198.18.0.0/15、
192.0.2.0/24、192.88.99.0/24、240.0.0.0/4、2002::/16（6to4 内嵌 IPv4）、
64:ff9b::/96（NAT64）等；且存在两份重复实现（provider 与 controlplane）。
**修复**：导出 `provider.IsPublicAddress` 单点实现，补齐全部特殊用途网段，
v4-mapped v6 先 `Unmap()` 再判定；两处调用统一。

### 4. 应用/数字员工运行把所有网关错误伪装成 200
`internal/workbench/runtime_http.go` 的 `boundedRecorder` 初始 `code=200`，
`WriteHeader(4xx/5xx)` 变成 no-op：鉴权 401、限流 429、上游 502 全部被
记录为成功，`callGateway` 的错误分支是死代码，审计/trace/事实核验数据被
污染。
**修复**：`code` 初始化为 0，`Write` 兜底 200；错误分支恢复生效。

### 5. 登录枚举 + 锁定状态泄露（响应差异 + 计时侧信道）
`internal/identity`：未知账号 401 / 停用 403 / 锁定 429（精确倒计时）三态
可枚举全部管理员/门户账号及其安全状态；停用/锁定账号不执行 dummy 哈希，
响应时间还泄露"账号是否存在"。
**修复**：登录路径四种失败（未知/停用/锁定/口令错）统一返回 401
"账号或口令错误"；停用/锁定账号同样执行 600k 次 dummy PBKDF2，计时一致。

### 6. 登录限流可被伪造 X-Forwarded-For 绕过
`ClientIP` 无条件采信 XFF 首值；dev compose 把网关端口暴露在 0.0.0.0，
直连时每请求换一个 XFF 值即可旋转 `gateway:login-limit:<ip>` 键。
**修复**：新增 `TRUSTED_PROXIES`（默认环回+RFC1918+ULA，覆盖 compose 的
nginx 同网段场景），仅可信对端携带的 XFF 被采信；dev compose 网关端口
改绑 `127.0.0.1:8080`。

### 7. 凭据变更后旧会话继续有效
会话是独立 Redis 键，无账号级版本：改密/停用 2FA 后，被盗会话在 TTL
（12h）内一直有效，强制改密形同虚设。
**修复**：`Principal.AuthVersion` + Redis 计数键（TTL 7 天 > 会话 12h，
保证版本键过期时旧会话已自然消亡）。改密、TOTP 启用/停用、备用码重生成
均 `BumpAuthVersion`；会话与 TOTP 挑战校验版本。

### 8. Webhook 投递失败被吞掉（无重试、无死信）
`notifications.go` `deliver` 失败只记日志，事件照常 ACK——与注释声称的
"reclaim 重试"相悖，通知实际是 at-most-once。
**修复**：投递失败让事件留在 pending 由 reclaim 重试；单条投递记录
`attempts >= 10` 后放弃自动重试（保留 failed 状态供管理端人工重试），
防止毒 Webhook 永久卡死事件队列。

## 中危（已修复）

### 9. API Key 认证热路径全表扫描
`api_keys` 只有 `(key_prefix, key_hash)` 复合索引，`WHERE key_hash=$1`
无法使用 → 每个代理请求全表扫描。
**修复**：迁移 `000029` 新增 `UNIQUE (key_hash)` 索引。

### 10. 撤销/改限流的缓存传播窗口与误报
缓存 TTL 30s + `Invalidate` 失败时 DB 已生效却向管理员报错（重试会造成
混乱）。
**修复**：`Invalidate` 失败降级为 Warn 日志（DB 是权威源）；缓存 TTL 降至
10s 作纵深防御。

### 11. Token 配额提交脚本可重建无 TTL 残留键
`SET ... KEEPTTL` 在键已过期（跨月）时创建永不过期的新键。
**修复**：`EXISTS` 为 0 时放弃回写。

### 12. 熔断器忽略 HTTP 500
只有 502/503/504 计入熔断失败，持续 500 的上游永远不熔断且 `success()` 
不断清零失败计数。
**修复**：`failureResult` 将所有 5xx 计入熔断；重试仍只针对 502/503/504
与可重放请求。

### 13. Outbox 退避在 attempt≥35 溢出为负延迟
`2^(attempt-1)` 秒转 `time.Duration` 溢出 → `MarkFailed` 把 available_at
设到过去 → 事件被立即重新认领，形成热循环烧掉重试预算（`MAX_ATTEMPTS`
允许到 100）。
**修复**：`attempt > 30` 直接返回上限。

### 14. 调度器停机期间漏掉所有中间执行
`scheduleDue` 只补最新一次，`* * * * *` 任务宕 30 分钟丢 29 次执行。
**修复**：从旧 `next_run_at` 起逐个 occurrence 补跑（单任务单轮上限
`catchUpLimit=100`，防长期停机瞬间灌入海量记录）。

### 15. 多字节截断产生无效 UTF-8，事件永久卡死
scheduler/trace/notifications 按字节截断 `[:1000]`，切半多字节 rune 后
PostgreSQL `text` 列拒绝写入 → 重试循环永远失败；inbox 的 Go `len()` 
（字节）与 DB `length()`（字符）口径不一致，4000 字节中文正文超 4000
字符 CHECK。
**修复**：全部改为 rune 边界安全截断；inbox 落库前按 rune 数截断
（title 256 / body 4000 / link 512）。

### 16. 管理端部分更新静默重置角色/状态
只改显示名的 PUT 把 superadmin 降为 operator、把停用部门重新激活（绕过
停用保护）。
**修复**：`role` 留空 = 不修改（仅创建时默认 operator/member）；`active`
省略时保留当前值（部门与账号一致）。

### 17. Agent 节点心跳可枚举 + 令牌非恒定时间比较
`WHERE code AND token_hash` 单条 UPDATE：404 vs 401 区分有效 code。
**修复**：先按 code 取哈希、`subtle.ConstantTimeCompare`，未知 code 与
错误令牌统一 `ErrInvalidToken`；`Endpoint` 增加 http(s) 绝对 URL 格式校验。

### 18. MCP 服务器内部端点泄露给门户用户
`marketplace.Detail` 原样返回 `endpoint_url`/`has_secret_headers`，任何
登录门户用户可读取内网服务拓扑。
**修复**：门户详情脱敏这两个字段（管理端与运行时列表不受影响）。

### 19. usage_daily 多币种成本混算
价格支持任意货币，但日聚合无币种维度，USD 与其它币种相加成单一数字。
**修复**：迁移 `000030` 将 `currency char(3)` 并入 PK；recorder 按币种
聚合，查询视图返回 `currency`。

### 20. Content policy 脱敏整包重编码
`json.Marshal` 重编码改变键序/数字格式/HTML 转义，破坏上游签名与字节
级契约。
**修复**：`redactBytes` 状态机在原始字节上仅对 textual 字段值字符串做
替换，其余字节零改动（语义与 walkText 一致，含嵌套/数组上下文传播）。

### 21. 事实核验不作用于数字员工
`runApplication` 有 `applyFactCheck`，`runDigitalEmployee` 没有——`block`
策略可被数字员工入口绕过。
**修复**：数字员工响应同样执行事实核验管线。

### 22. 其它（批量）
- `shadow` 中间件：handler panic（如 `http.ErrAbortHandler`）泄漏并发槽位
  → 新增 completed 标志在 panic 路径归还。
- 审计关闭 drain 只试一次 → 3 次有限重试 + 失败明确日志。
- Trace 表无保留 → `TRACE_RETENTION`（默认 90 天）由 maintenance worker
  清理（spans 靠 ON DELETE CASCADE）。
- 广播（inbox）非事务逐行插入 → 单事务包裹，中途失败不留半套消息。
- `visible()` 空 APIKeyID fail-open → fail-closed。
- 文件下载缺 `X-Content-Type-Options: nosniff`（用户可控 Content-Type）。
- Provider 管理错误原样返回内网解析地址 → `ErrBlockedAddress` 哨兵，
  客户端只收通用提示，细节进服务端日志。
- 备用码生成 `%31` 取模偏差 → 拒绝采样（丢弃 ≥248）。
- OIDC 每次登录拉 discovery+JWKS → JWKS 按 provider 缓存 5 分钟。
- MCP 客户端缓存不随服务器编辑失效 → 缓存键含 `revision`。
- 凭据轮换单条损坏阻塞全部 → 跳过并计数。
- `auditSpan.capture` 无锁写 → 纳入锁。
- `proxyFor` 并发 cache-miss 重复构建 → `LoadOrStore` 单飞。
- `max_tool_rounds=0`（未配置）导致应用首个工具调用即失败 → 默认 5。

## 部署配置（已修复）

- **`CREDENTIAL_MASTER_KEY` 全零默认值**（compose）：任何拿到仓库的人可
  解密全部 Provider 凭据/TOTP 密钥/Webhook 签名。compose 改为
  `${CREDENTIAL_MASTER_KEY:?...}` 强制设置；`ValidateRuntime` 拒绝已知弱
  密钥（所有环境）。
- 网关端口 8080 暴露 0.0.0.0 → 绑定 `127.0.0.1`（nginx 是唯一外部入口）。
- nginx：`server_tokens off` + `X-Content-Type-Options`/`X-Frame-Options`/
  `Referrer-Policy` 安全头。
- worker 服务加内存限制（`deploy.resources.limits`）。
- 本地 compose 的 PostgreSQL/MinIO 弱口令为本地开发默认，README 已注明
  生产必须覆盖（生产 compose 已强制要求）。

## 已知限制（未修复，建议后续处理）

1. **审计维护大锁**（`audit/maintenance.go`）：分区 drop/move 全程持
   `ACCESS EXCLUSIVE`，flush 会阻塞。建议把空分区创建移出锁区间、仅
   detach/move/attach 留在锁内，并分批删除。
2. **内容策略单条规则失败冻结全部策略**：`Reload` 时任一规则编译失败会
   中止整个快照刷新（保存时 `Validate` 已缓解）。建议 per-policy skip。
3. **无定价模型静默按 0 计费**：`pricing.Calculate` 未命中返回零成本且无
   信号。建议未定价模型加日志/指标或可配置默认价。
4. **outbox 事件丢失预算**：Redis Stream `MAXLEN ~100k` 裁剪 + worker 宕机
   超预算时事件永久丢失（无回放）。建议 worker 启动时按
   `published_stream_id` 从 outbox 表补发。
5. **audit 幂等**：`audit_events` 无唯一键，响应丢失重试会重复落审计行。
   可用 `request_id + recorded_at` 建唯一约束。
6. **Outbox marker 30 天 Redis 内存税**：高吞吐下 `published:*` 键累积。
7. **多实例 JWKS/会话版本键无 TTL 清理**：auth-version 键 7 天 TTL 已缓解。
8. **`UPSTREAM_FALLBACK_ENABLED` 是空转**：fallback adapter 与主 adapter
   相同，没有真正的第二个上游。建议支持第二上游 URL/凭据或移除该开关。
9. **OpenAPI 漂移**：`scripts/check_openapi_routes.py` 报 52 条已实现路由
   未收录 OpenAPI（admin files/inbox/marketplace/skills、portal marketplace、
   `/v1/mcp-servers` 等）。建议把检查脚本接入 CI 并补齐文档。
10. **`/metrics` 未认证**：仅靠网络位置保护（dev compose 已回环绑定），
    生产建议放内网或加访问控制。
11. **scheduled_task_runs.response 无保留**：每次执行持久化最多 2 MiB
    响应。建议只存截断预览或加清理任务。
12. **agent_traces 会话聚合无预聚合表**：大表上 GROUP BY 查询慢。

## 验证

- `go build ./...`、`go vet ./...` 通过。
- `go test ./...` 全量单元测试通过（含 gateway/identity/scheduler/
  workbench/contentpolicy/audit/outbox/provider/shadow）。
- 新增测试：XFF 可信代理/不可信对端、无可信代理时的回退行为。
- 迁移 `000029`（key_hash 索引）、`000030`（usage_daily currency）在
  真实 PostgreSQL 上执行通过。
- Docker Compose 全栈（API + 5 workers + admin/portal 前端 + 依赖）启动
  健康，`/healthz` `/readyz` 正常。

---

# 追加：业务逻辑正确性专项审查（第二轮，2026-08-13）

以功能正确性视角（算法边界/算术/并发语义/协议语义/事务一致性）对全模块复核，
在安全审查基础上新增修复：

## 已修复

1. **[高] 裸 body 上传 nil reader panic**（`files_admin_http.go`/`portal_files_http.go`）：
   非 multipart Content-Type 上传时 body 保持 nil，`io.LimitReader(nil,...)` 在
   `Upload` 中 panic（net/http 恢复后每个此类请求 500 + panic 日志）。
   修复：非 multipart 时回退 `r.Body`。
2. **[高] Bootstrap key 流量永久卡死审计管线**（`audit/recorder.go`）：
   bootstrap 身份 `APIKeyID="bootstrap"` 非 UUID，写入
   `usage_daily.api_key_id uuid NOT NULL REFERENCES api_keys` 使整批事务失败、
   pending 永不清理、审计队列填满后所有事件被丢弃。
   修复：`uuidPointer` 对非 UUID 返回 NULL；日聚合跳过非 UUID 身份（审计
   事件本身仍落 audit_events）。
3. **[高] 定价通配符优先级错误**（`pricing/service.go`）：通配符只按生效日期
   排序，`gpt-4o-mini` 可能命中更宽泛的 `gpt-4*` 价格。
   修复：通配符按前缀长度降序（更具体优先）再按生效日期。
4. **[高] 全局工具对所有人生效不可见**（`runtime_http.go visible()`）：
   `len(departments)==0` 对 secure 资源返回 false——无部门限定的工具/MCP
   全部消失，绑定它们的应用运行时失败且无配置期报错。
   修复：无部门限定=全局资源，所有已认证主体可见；部门匹配只约束有部门
   限定的资源。
5. **[高] 调度任务可配置永远无法执行的目标**（`scheduler/service.go`）：
   管理员创建的 API Key 无 tenant，对部门限定应用/员工运行时一律 404/403，
   但保存时无任何校验。
   修复：`validateTarget` 增加 `requireKeyTenant` 预检——目标部门限定时，
   任务 API Key 必须存在、启用且 tenant 匹配，否则保存即报错。
6. **[中] 路由权重合计 0 → 整数除零 panic**（`runtime/resolver.go`）：weight≤0
   的脏数据行（绕过管理端校验）使 `Sum64()%0` panic。
   修复：权重池过滤非正权重；全非正时视为无匹配。
7. **[中] 重试路径 nil `GetBody()` panic**（`gateway/resilience.go`）：GET/HEAD
   携带未设置 GetBody 的 body 时重试调用 nil 方法。
   修复：GetBody 缺失时放弃重试（避免空体重发）。
8. **[中] TOTP 挑战令牌双重使用竞态**（`identity/session.go`）：两个并发请求
   用同一 pending 令牌 + 各自有效的不同因子可铸出两个会话。
   修复：`AuthenticatePending` 改 `GetDel` 原子消费。
9. **[中] 事实核验策略作用域失效**（`factcheck/engine.go` + workbench）：
   `ORDER BY scope LIMIT 1` 只执行字典序第一条策略，其余部门策略被静默忽略
   或张冠李戴。
   修复：`Check` 增加 scope 参数，`enabledPolicy` 优先精确匹配
   `department:<uuid>`、回退 global；workbench 从应用/员工部门推导 scope。
10. **[低] 熔断器陈旧成功关闭刚打开的电路**（`gateway/resilience.go`）：
    打开前通过 allow() 的在途请求成功会清零失败计数并清除 openUntil。
    修复：仅半开探针成功可关闭电路；关闭态成功只清零计数。
11. **[低] MCP 工具缓存读竞态**（`mcp_client.go`）：tools/toolsAt 无锁读。
    修复：读写统一持锁。
12. **[低] 心跳 UPDATE 不复查 token_hash**（`agentnode/store.go`）：轮换瞬间
    旧令牌仍可更新一次节点元数据。
    修复：UPDATE 条件恢复 `token_hash=$2`。
13. **[低] 卸载已停用/归档资源失败**（`marketplace.go`）：publishedResourceID
    要求 enabled+published，管理员停用后用户无法卸载，安装行永久卡死。
    修复：Uninstall 改用不限制状态的 `resourceIDByCode`。

## 复核为正确的关键逻辑（节选）

- 限流 Lua：无 off-by-one（限额 N 精确放行 N 次）；月配额键含月份自动翻转。
- Token 配额 reserve/commit/refund 对账数学正确、并发安全；跨月提交安全跳过。
- `usageSession.finish` sync.Once 保证恰好执行一次；SSE/JSON usage 提取路径
  无双重计数。
- 审计 recorder 队列/pending 无丢失无重复（事务内 CopyFrom+聚合原子提交）。
- 分区维护 SQL 边界（[start,end)、drop 判定）正确。
- PBKDF2/TOTP/备用码密码学实现正确（±1 窗口、原子防重放、拒绝采样）。
- 调度器补跑循环无 off-by-one；outbox claim/发布/标记与去重正确。
- 会话 AuthVersion 语义正确：bump 后新登录签发新版本、旧会话 fail-closed。

---

# 追加：网关接入报错修复与部署密钥持久化（2026-08-13）

## 用户报告的问题
管理端「网关接入」页面提示"服务器内部错误，请稍后重试"。

## 根因
`GET /api/v1/admin/providers` 对每条记录解密凭据，**任何一条解密失败
（AES-GCM tag 校验失败）都会让整个列表返回 400**。本环境每次重建部署都
重新生成随机 `CREDENTIAL_MASTER_KEY`，旧 key 加密的 Provider 记录全部
无法解密 → 列表接口 400 → 前端显示"服务器内部错误"。

## 修复
1. **[bug] Provider 列表降级展示**（`provider/admin_http.go view()`）：
   单条凭据解密失败不再使整个列表报错，改为 `credential_error` 警示字段
   返回 200，管理端仍可编辑/删除该记录恢复。
2. **[功能缺口] Provider 缺少删除接口**：新增
   `DELETE /api/v1/admin/providers/{id}`（`repository.Delete` 事务 + outbox
   `provider.deleted` 事件；provider_models/model_routes 由 FK 级联清理）。
3. **[部署] master key 持久化**：本地 compose 首次启动生成
   `deploy/.env`（compose 自动读取）并复用，重启/重建不再换 key；
   `.gitignore` 排除 `deploy/.env`；`deploy/PRODUCTION.md` 增加持久化与
   轮换说明。

## 验证
- 列表接口对损坏记录返回 200 + `credential_error` 警示（旧代码返回 400）。
- DELETE 接口删除成功，级联清理正常。
- 用持久 key 重建 Provider 后**容器重启凭据仍可解密**
  （`key_configured: true, credential_error: ''`），resolver 快照刷新无错误。
- 全量单测 24 包通过。

---

# 追加：第三轮审查（前端 + 回归，2026-08-13）

## 前端审查发现并修复（web/apps/admin + portal + api-client）

1. **[高] 凭据异常不可见**：后端新增的 `credential_error` 字段前端未展示，
   运维无法发现 KEK 失配/损坏的 Provider。修复：`ProviderRecord` 增加字段，
   凭据列对异常记录显示红色警示 Tag（tooltip 展示原因）。
2. **[高] localStorage 持久化敏感数据**：Pinia persist 全量落盘
   （accessToken/refreshToken/账户信息/锁屏密码密文），任何 XSS 直接窃取。
   修复：改为 sessionStorage + `pick` 白名单（仅 isLogin/token/language），
   账户信息与锁屏密码不再持久化。
3. **[中] 后端错误消息被吞**：`handleError` 优先用状态码通用文案，400/429
   显示"服务器内部错误"，校验失败原因不可见。修复：后端 `msg` 优先；
   补齐 400/429 文案与枚举。
4. **[中] 门户用量页显示原始微单位**：`cost_microunits` 原样输出。修复：
   按 /1e6 转换显示 USD 金额（与管理端一致）。
5. **[功能] Provider 删除按钮**：前端补齐删除入口（确认框 + 级联提示）。
6. 记录未修项：refreshToken 双端死代码、锁屏密码加密占位 key（已不落盘，
   建议后续改服务端校验）、注册/找回密码页为模板 mock、B7 约 21 处
   ElMessageBox 取消未捕获等。

## Go 回归审查发现并修复

1. **[高] >4MB 非流式响应 token 计量丢失**：`consumeUsageObject` 提取的
   usage 对象以 `inUsage=false` 解析，顶层 *_tokens 永不计数；压缩触发后
   计量静默回退为预留值。修复：`consumeJSONAsUsage`（inUsage=true），
   并加固 "usage" 键位置判定（要求前一非空白字符为 { 或 ,）。
2. **[中] 脱敏检测与改写不一致**（转义内容）：`\u002d` 等 JSON 转义使
   解码后匹配成功但字节改写失败，请求原样上送却标记已脱敏。修复：
   `applyRules` 增加解码文本回退路径（替换后重新 JSON 转义）；替换文本
   本身做 JSON 转义防破坏结构；`Redacted` 以字节改写结果为准，不谎报。
3. **[中] 脱敏规则跨策略泄漏**：字节改写使用所有 redact 策略的规则（含
   不适用于本请求的端点/模型作用域）。修复：仅收集本请求命中的 redact
   策略规则。
4. **[中] 熔断探针语义**：陈旧成功/取消探针会错误关闭电路。修复：
   `allow` 返回探针标记，`success(probe)` 仅探针成功可关闭；取消探针
   调用 `abortProbe` 保持打开。
5. **[中] TOTP 挑战令牌先消费后验证**：验证码输错即烧令牌、强制重新登录
   且叠加锁定计数。修复：`AuthenticatePending` 改只读，验证通过后
   `ConsumePending`（GetDel）原子消费——重试友好且防双会话。
6. **[中] 管理员重置密码不作废会话**：management update 补
   `BumpAuthVersion`，与自助改密语义一致。
7. **[低] 会话 TTL 校验**：`AUTH_SESSION_TTL` 上限 7 天，`authVersionTTL`
   提高至 14 天，杜绝版本键先过期导致旧会话复活。
8. **[低] provider delete 补充 `tenant_id IS NULL` 过滤**（与其它查询一致）。

## 新 logo（LLMGuardX语枢）

- 设计：AI 网关路由主题——深蓝圆角方块 + 青色枢纽节点 + 三向分支连接，
  中心"语枢"字母 A 标记；沿用品牌色（#071F4D/#00E4E5/#006EFF）。
- 替换：侧边栏/顶栏 logo（SVG，Vite 内联）、登录页图标、favicon（16-256
  多尺寸 ICO）。程序化像素验证渲染正确。

---

# 追加：旗舰版 Ultra 功能实现（2026-08-13）

按旗舰版功能矩阵补齐的模块(均含后端+前端+迁移+端到端验证):

## 新功能模块
1. **License 授权**（`internal/platform/license` + 迁移 000031 前置账号管控）：
   HMAC-SHA256 签名 License 文件(edition/max_accounts/有效期/特性)，启动校验 +
   热更新上传，账号创建按上限管控(Free=3)。管理端「系统管理→License 授权」。
2. **登录记录**（迁移 000031）：admin/portal 每次登录尝试(成功/失败/原因/IP/
   UA)落库；门户「登录记录」+ 管理端「系统管理→登录记录」(audit 权限)。
3. **会话管理**：门户会话列表/重命名/删除(PATCH/DELETE)。
4. **角色管理 CRUD**（迁移 000032 + `internal/identity/roles.go`）：自定义
   角色 + 权限字符串,内置角色合并展示;分配账号时权限展开。
5. **门户定时任务**（`internal/scheduler/portal_http.go`）：门户工作台
   定时任务创建/启停/立即执行/历史(仅本人任务)。
6. **按模型 Token 配额**（迁移 000033 + `internal/modelquota`）：企业级
   模型总配额(Provider+模型模式),所有 Key 共享自然月计数,与 Key 级配额
   叠加;管理端「模型配额」。
7. **输出侧脱敏**（`internal/contentpolicy/output.go` + gateway wrapper）：
   模型回答(content/delta)应用 redact 规则拦截替换,SSE 逐行/JSON 缓冲;
   响应头 X-Gateway-Output-Redacted。
8. **供应链安全扫描**（`internal/workbench/scan.go`）：skill/mcp 定义静态
   检测(硬编码密钥/内网端点/危险命令/提示词注入/base64 混淆),管理端扫描
   按钮+结果展示。
9. **记忆管理**（迁移 000034 + `internal/memory`）：用户/部门/全局三层记忆,
   Ollama 向量化语义召回(pgvector HNSW),关键帧衰减清理,向用户授权;
   门户「记忆管理」。
10. **AI 助手**（`internal/assistant`）：管理平台自然语言问答,注入实时平台
    统计(供应商/模型/账号/用量/待办),走默认模型供应商。
11. **概览真实数据**：dashboard 由模板 demo 改为 system-info +
    monitoring/overview + license 实时数据。

## 遗留(记录,建议后续)
- 渠道接入(企微/钉钉/飞书)与扫码:依赖外部 IM 开放平台,需企业凭据,
  建议独立迭代。
- 多租户管理:现有 tenant 为单租户模型,平台管理员多租户管理需数据隔离
  重构。
- ARM64 安装包:构建已可交叉编译(CGO_ENABLED=0),发布流程待配置 buildx。
- 个人环境变量注入、资源权限等级(查看/仅使用/管理)、收藏:前端增强。

---

# 追加：旗舰版功能完善第二轮（2026-08-13）

1. **资源权限等级**（迁移 000035）：marketplace 安装支持
   view/use/manage 三级权限,门户"我的资源"展示。
2. **个人环境变量**（迁移 000036 + `internal/workbench/envvars.go`）：
   门户配置 key-value(加密存储),对话/应用运行时自动合并进请求变量。
3. **管理平台收藏**（`art-favorites` 组件）：顶栏星标收藏当前页,
   弹窗快捷跳转(localStorage)。
4. **企业报表**（`gateway/reports`）：按供应商/模型/日三个维度汇总
   用量与成本。
5. **租户概览**（`/api/v1/admin/tenants/overview`）：以部门为租户维度,
   统计账号/Key/今日用量,支持多租户管理视角。
6. **ARM64 发布流程**：`scripts/publish-images.sh`(buildx 多架构推送)
   + PRODUCTION.md 说明。
7. **渠道接入**（迁移 000037 + `internal/channel`）：统一渠道抽象,
   支持通用 Webhook/企业微信/钉钉/飞书;入站
   `POST /v1/channels/{code}/inbound`(企微签名校验/钉钉加签工具),
   绑定模型应答后按平台协议回复(企微应用消息/钉钉机器人/飞书应用),
   管理端渠道 CRUD + 连通性测试。

# 追加：旗舰版功能完善第三轮（0.11.2，2026-08-13）

1. **门户通用聊天**（迁移 000038 + `internal/portal/chat.go`）：
   门户「通用聊天」页选择已批准模型直接对话。模型权限审批通过后按需自动
   开通用户级运行时 API Key（`portal_user_runtime_credentials` 加密落库，
   限额取批准申请值），聊天请求经 `SetGateway` 注入的受管网关执行，与外部
   Key 同权受审计/配额约束。会话复用哈希链消息完整性与 busy 租约防并发，
   失败不落库（无孤儿/重复消息）。
2. **企微/钉钉/飞书扫码登录**（迁移 000039 + `internal/identity/social*.go`）：
   `identity_providers` 扩展三种 kind；管理端「账号与权限 → 扫码登录」配置；
   登录页自动展示；`start/callback` 复用 SSO 路由按 kind 分发。安全设计：
   - state 为 128-bit 一次性令牌（Redis one-time，5 分钟），回调原子消费；
   - 平台 AppSecret 用 IdP 专用 KEK 加密存储，列表/视图只回 `secret_configured`；
   - `provider_uid` 全局唯一 → 平台账号不可同时绑定多个本系统账号；
   - 未绑定且未开自动开户时重定向回登录页提示，不静默开户；
   - 平台端点全部为固定公网 URL，复用 public-only 拨号客户端（无新增出站面）。
3. **个人安全策略**（迁移 000040/000041 + 会话索引）：
   - 「账号安全」页：登录设备列表（IP/UA/时间，索引只存令牌 SHA-256 摘要，
     惰性清理过期项）、吊销任意非当前会话、扫码绑定/解绑；
   - 新设备登录提醒：登录成功发布 `security.login_detected` outbox 事件，
     notification worker 依据 `portal_security_prefs.login_notify`（默认开）
     落站内信（新增 security 类别），可单独关闭；
   - 当前会话吊销返回 400（`ErrRevokeCurrentSession`）。
4. 修复：`request.patch` → `put`（前端无 patch 方法）；社交 provider 更新
   SQL `$14`→`$13` 参数越界；update 返回视图未回读凭据导致 secret_configured
   显示失真；`/api/v1/portal/chat/` 与 `/api/v1/admin/social-providers` 挂载缺失；
   start/callback 用 provider code 而非 kind 查询。
