# 安全 (/docs/cloud/services/agent/embed/security)



嵌入把一个智能体放进你的页面，页面的访问者就是智能体的使用者。这一页说明平台替你守住了什么、哪些要你自己守，以及宿主页怎样配置。

## 责任划分 [#责任划分]

| 平台保证                      | 你负责                                                                       |
| ------------------------- | ------------------------------------------------------------------------- |
| 嵌入令牌只能访问一个智能体下**自己名下**的会话 | 用你自己的登录态确定 `external_user_id`，不要信浏览器传来的值                                  |
| 越权访问一律返回 404，不透露资源是否存在    | 工具服务端按终端用户鉴权（[工具授权](/docs/cloud/services/agent/embed/tool-authorization)） |
| 令牌只在浏览器内存里，短期有效，可以随时撤销    | 保管 API Key，只在后端使用                                                         |
| 只接受白名单来源的页面使用令牌           | 配好 `allowed_origins`，宿主页配好 CSP                                            |

## 隔离边界 [#隔离边界]

* **令牌的主体是终端用户**，不是你的组织。持有令牌的人只能创建和读取自己名下的会话，看不到同一智能体下其他用户的会话，也不能调用签发、撤销、列出令牌这类宿主接口（返回 404）。身份来自令牌，不来自请求参数。
* **跨用户、跨智能体一律 404**，而不是 403：不告诉访问者「这个东西存在但不属于你」。
* **API Key 永远不进浏览器**。后端用它签发令牌；浏览器只拿令牌。令牌的签发与撤销见[令牌与终端用户](/docs/cloud/services/agent/embed/tokens)。

## 令牌 [#令牌]

* 令牌有效期 60 到 3600 秒。设短一些：被截获的令牌价值有限。`getToken` 会在到期前被再次调用，换发时再确认一次用户仍处于登录状态。
* 令牌只存在于内存，不要自己写进 `localStorage`、Cookie 或页面 HTML。
* 发现泄露或用户登出时，按签发记录的 `id` 撤销，令牌立即失效，进行中的事件流会收到 `EMBED_TOKEN_REVOKED` 并关闭。
* 对某个用户启用封禁（`blocked_end_users`），他就拿不到新令牌。

## 来源白名单 [#来源白名单]

嵌入策略里的 `allowed_origins` 限定哪些页面来源可以使用令牌。签发令牌时可以再收窄（传 `allowed_origins`，必须是策略白名单的子集）。来源不在名单上时，请求返回 `EMBED_ORIGIN_NOT_ALLOWED`（403）。

* 只接受 `https` 来源；`https://*.example.com` 形式的通配只匹配一级子域名，不含端口。
* 本地开发才打开 `allow_localhost`，上线前关掉。
* 来源由浏览器写入请求，它挡住的是**别的网站**拿你的令牌去用，挡不住拿到令牌的非浏览器客户端。令牌本身仍要妥善保管。

## 哪些不是秘密 [#哪些不是秘密]

下面这些内容，模型可能被用户要求复述，请当作公开信息对待：

* 智能体的指令（Console 里写的 instructions）。
* 覆盖指令（`agent_override.system`）。
* `system.message` 里的宿主上下文。
* 页面上下文和前端工具的返回值。
* 工具的名字、描述和输入结构。

所以不要在这些地方放密钥、内部规则、不想让用户看到的话术或别人的数据。需要保密的数据放在你的工具服务端，用[终端用户身份](/docs/cloud/services/agent/embed/tool-authorization)控制谁能取到。

## 提示注入与不可信上下文 [#提示注入与不可信上下文]

智能体读到的内容里，凡是来自浏览器或第三方的，都可能含有试图操纵它的指令：用户输入、页面上下文、网页内容、工具返回的文本。

* **页面上下文是不可信的**。服务端把它放进附在本轮用户消息末尾的 `<page_context untrusted="true">` 块里，不进系统提示，也不延续到下一轮。值里的 `<`、`>`、`&` 和引号会被转义，不能伪造出新的标签。详见[前端工具与页面上下文](/docs/cloud/services/agent/embed/client-tools)。
* **可信事实走 `system.message`**。只有持有 API Key 的后端能写入它，终端用户令牌不能。
* **不要靠指令里的「不要……」守住边界**。真正要禁止的事，在工具一侧不提供，或者在服务端拒绝。
* 智能体可以调用的工具越少越安全：只为这个场景开需要的工具，写入类工具保留 `always_ask`，并在服务端校验。

## iframe 与 Hosted [#iframe-与-hosted]

iframe 页的 `frame-ancestors` 来自智能体的来源白名单；白名单为空时禁止所有网站嵌入。Hosted 是独立页面，使用 `frame-ancestors 'none'`。平台托管的 chat 页从自身可信来源请求 API；宿主来源由 iframe 策略和跨窗口通信校验。

使用一行加载器时，宿主 CSP 还需在 `frame-src` 放行 `https://chat-develop.praxisusercontent.com`。加载器同时校验跨窗口消息的来源和发送窗口。Hosted 凭证只能使用一次，见[嵌入令牌](/docs/cloud/services/agent/embed/tokens)。

## Shadow DOM [#shadow-dom]

`<praxis-agent>` 渲染在 **open** Shadow DOM 里：宿主页的样式不会漏进对话，对话的样式也不会影响宿主页。这是样式隔离，**不是安全边界**：open 的 Shadow DOM 可以被页面脚本读取，同一页面上的任何脚本都能访问对话内容。不要把智能体嵌入你不信任的第三方脚本也在运行的页面里，需要强隔离时使用 iframe。

## 内容安全策略（CSP） [#内容安全策略csp]

如果你的页面设置了 CSP，需要放行 Kit 脚本所在的来源，以及 Kit 连接 API 的来源。

| 环境      | 脚本来源（`script-src`）                          | 连接来源（`connect-src`）                 |
| ------- | ------------------------------------------- | ----------------------------------- |
| develop | `https://kit-develop.praxisusercontent.com` | `https://api-develop.deepraxis.com` |
| 生产      | `https://kit.praxisusercontent.com`         | `https://api.deepraxis.com`         |

```http
Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://kit-develop.praxisusercontent.com;
  connect-src 'self' https://api-develop.deepraxis.com;
  frame-src https://chat-develop.praxisusercontent.com;
```

要点：

* `connect-src` 里的 API 地址要和 `setOptions` 的 `api.url` 一致，也就是签发令牌的那个环境。
* 如果你自己托管 Kit 的构建（如快速开始里的 `/praxis-kit.js`），`script-src 'self'` 就够了，不需要放行 CDN。
* Kit 会向 Shadow DOM 里插入 `<style>` 元素（用于主题）。设置了严格 `style-src` 的页面，需要允许内联样式，例如 `style-src 'self' 'unsafe-inline'`。
* 头像、启动图片等你通过选项传入的图片地址，要放行 `img-src`。
* 宿主页面被别人嵌入的保护由宿主自己的 `frame-ancestors` 决定。

## 子资源完整性（SRI） [#子资源完整性sri]

从 CDN 加载时，用**不可变路径**并加上 `integrity`，浏览器会校验文件内容，CDN 被篡改时拒绝执行：

```html
<script
  type="module"
  src="https://kit-develop.praxisusercontent.com/v0.1.0/praxis-kit.js"
  integrity="sha384-…"
  crossorigin="anonymous"
></script>
```

* `integrity` 的值（`sha384-…`）由发布 Kit 版本时给出，路径里的版本号（`v0.1.0`）对应一次不可变的发布，内容不会改变。
* 必须带 `crossorigin="anonymous"`，否则浏览器不会校验。
* 浮动路径 `https://kit-develop.praxisusercontent.com/v1/praxis-kit.js` 会随新版本更新内容，&#x2A;*不要配 `integrity`**，配了会在下次发布后加载失败。需要锁定内容时请用不可变路径。

## 上线前检查清单 [#上线前检查清单]

* [ ] API Key 只在后端，页面和仓库里找不到。
* [ ] `external_user_id` 来自你的登录态。
* [ ] `allowed_origins` 只有你的站点，生产环境没有开 `allow_localhost`。
* [ ] 令牌有效期合理，登出时撤销令牌。
* [ ] 指令和 `system.message` 里没有秘密。
* [ ] 工具服务端按 `X-Praxis-External-User-Id` 鉴权，并认证调用方。
* [ ] 页面 CSP 放行了 Kit 与 API，CDN 加载使用不可变路径和 SRI。

相关：[工具授权](/docs/cloud/services/agent/embed/tool-authorization) · [令牌与终端用户](/docs/cloud/services/agent/embed/tokens) · [限制与错误](/docs/cloud/services/agent/embed/limits-and-errors)
