# 安全与故障排查

保护 Bundle 平台应用 token，并按授权链路定位入口、回跳和权限问题。

## 安全要求

- 使用 PKCE `S256`，每次授权生成新的 verifier 和 state。
- `redirectUri` 必须与登记的应用入口同源。
- 不在 URL、日志、错误上报、埋点或截图中记录 token。
- token 仅保存在 SDK client 内存中，校验 `workspaceId + runtimeAppId + installId` 和过期时间；不要自行持久化。
- 内存保存 token 不消除 XSS 风险；必须执行内容安全策略和依赖安全检查。
- 应用停用、卸载、权限变化或用户退出时，清理本地会话。
- 后端服务只信任平台网关注入并验证过的上下文，不信任前端自行传入的 `userId` 或 `workspaceId`。

## 旧外部入口被拒绝

若启动提示“旧签名启动应用必须创建环境托管应用新版本”，或授权提示“外部托管应用必须使用自己的后台授权”，
先检查安装锁定的平台应用版本是否仍为 `TRUSTED_EXTERNAL_APP`。定义或版本创建成功不等于当前启动链路支持它。
按 [迁移历史外部应用](/development/bundle-development/platform-application-development/create-and-install/#迁移历史外部应用)
更新源码、入口、SDK 和完整 Bundle 版本链；不要改成普通外链后继续申请平台 token。

## 环境配置读取或版本路径校验失败

如果 `platform-app workspace list` 只返回 `/index.html`，使用
`platform-app workspace launch-context <resourceLocator> --workspace-id <workspaceId> --json`
取得该安装的完整 `accessUrl`。如需检查准确版本部署或跨工作区分享，分别使用
`platform-app version address` 和 `platform-app entry`，见
[三类入口查询](/development/bundle-development/platform-application-development/entry-and-manifest/#查询分享入口托管地址与启动上下文)。

检查实际入口同源的 `/.well-known/baijimu-platform-application.json` 是否可通过 HTTPS 直接读取，
并确认应用身份和精确 origin 与当前版本一致，SDK 7 的配置合同为 `2.0.0`。构建归档不能覆盖配置路径，不能把 API 或授权地址从 URL 参数补入。
出现 404、`NoSuchKey` 或返回 HTML 时，先区分入口类型：

- 历史 `STATIC_SPA` 或 `TRUSTED_EXTERNAL_APP`：按完整迁移流程创建 `ENVIRONMENT_HOSTED_APP` 新版本并升级 Bundle。无需为旧站点追加配置，也无需自行执行站点部署。
- 已安装的 `ENVIRONMENT_HOSTED_APP`：从当前安装返回的 `accessUrl` 访问，核对版本是否已升级、资源台账是否已验证、托管物化是否完成，再排查平台生成配置的步骤。

不要把旧站点的 404 直接解释为 CLI 缺少“配置托管入口”的命令；也不要增加硬编码网关、授权地址、公共 SaaS 默认地址或旧配置缓存。

## 授权页提示缺少参数

检查参数是否写在 Hash Router 路由后：

```text
正确：https://console.baijimu.com/#/platform-app-authorize?installId=...
错误：https://console.baijimu.com/?installId=...#/platform-app-authorize
```

## 分享入口提示需要安装 Bundle

这是预期的授权边界，不是平台应用缺少单独授权。确认分享地址使用 `/bundle-app/{bundleId}/{platformAppId}`，选择正确工作区，并让工作区所有者或管理员安装提示的 Bundle。平台应用不能脱离 Bundle 单独安装。

## `redirectUri` 不合法

检查协议、域名和端口是否与环境托管后的实际入口同源，并核对同源配置的 `applicationOrigin`；SDK 7 新部署不含 `releasePath`。清单中的 `/index.html` 是构建产物内路径，不能直接充当授权回跳 URL，也不能用外部域名替换托管入口。

## 缺少 PKCE verifier

通常是授权前没有保存 verifier、回跳进入了不同浏览器会话，或者应用刷新逻辑提前清理了 sessionStorage。重新开始授权并保证同一浏览器会话完成回跳。

## state 校验失败

停止兑换 token。检查是否同时发起了多个授权请求、多个安装共用了同一个 storage key，或者回跳参数被路由层覆盖。

## 授权回跳后提示缺少 `installId`

先查看平台安装记录返回的 `accessUrl`，确认平台已在托管入口中追加 `installId`。如果首次跳转有该参数、授权回跳后却
消失，通常是应用自行执行了类似 `history.replaceState({}, "", location.pathname + location.hash)` 的清理逻辑，
把整个查询串连同 `installId` 一起删除了。

不要为此增加缓存态 `installId` 或从其他字段猜测安装身份。环境托管应用必须让
`@baijimu/platform-application-sdk` 完成回跳清理；SDK 只删除一次性的 `code` 和 `state`，保留入口身份参数。
迁移旧项目时，应同时删除自实现的 PKCE、token 缓存和 Gateway client，避免两套状态机竞争。

## token 兑换失败

依次确认：

1. `code` 尚未使用且没有过期。
2. `installId` 与发起授权时一致。
3. `codeVerifier` 与原始 challenge 匹配。
4. 请求体字段使用 `code` 和 `codeVerifier`。

## 平台切换账号后应用仍是旧用户

先核对部署产物使用的 SDK 版本。SDK 6.0.0 以前会复用应用 origin 的持久化会话，平台与应用域名的
存储不会因另一边退出自动同步。升级 SDK、删除应用自实现缓存并重新构建、创建应用版本、升级 Bundle，再重新打开应用验证。
不要把 token 的有效期当成当前平台身份的证明，也不要通过修改 userId 参数补偿错误身份。

SDK 6.0.0 新开或刷新重新授权；已打开页面的即时账号同步需要平台会话联动。排查时分别记录平台身份、
应用授权返回身份、应用版本和打开时间，不记录完整 token。

## 接口返回无权限

不要立即重新登录。先区分：

- App Gateway 明确拒绝会话，SDK 已清空 `getSession()`：重新授权。
- 后端返回合法 CModel `UNAUTHORIZED`：保留登录会话并展示业务错误；HTTP 401 本身不能替代 SDK 的会话判断。
- 模块方法未声明：创建包含正确最小权限的平台应用新版本。
- 工作区未安装依赖能力：修复安装或接口绑定。
- 用户没有目标业务权限：由业务权限管理员处理。
- 后端服务异常：查看目标服务日志和追踪信息。
- 安装或升级未确认跨 Bundle 方法：重新执行预览，核对精确清单后再确认；不要复用旧预览。

排障时保留 `installId`、`runtimeAppId`、平台应用版本号、模块 service、方法名和平台
返回的公开错误信息；不要保存完整 token。
