Skip to content

安全最佳实践 ​

你的应用运行在别人的客户端里、调用的是组织的资源——安全不是可选项。这一页 是接入前后的对照清单,按重要性排序。

1. app_secret 只放服务器 ​

app_key/app_secret 等价于应用后端的密码:

  • ✅ 存在服务器环境变量 / 密钥管理服务里
  • ❌ 不进前端代码、不进页面 JS、不进 git 仓库、不打进日志
  • 页面只需要 jlt.js 与免登码,永远不需要接触 secret

怀疑泄露?立即在应用设置里轮换密钥——旧 secret 与它签发的所有 token 即时失效(token 有效期最长 2 小时,轮换不等待)。

2. 配置服务器 IP 白名单 ​

白名单是 secret 泄露的兜底防线:非空时 所有 /api/open/*(含换 token)只接受你后端出口 IP(精确 IP 或 CIDR)。 来源判定以反向代理记录的真实地址为准,伪造 X-Forwarded-For 无效。

3. 事件与工具回调必须验签 ​

事件推送(X-Jlt-Event: org.member.* 等)和 AI 工具调用 (X-Jlt-Event: tool_call)走同一套签名协议,同一份验签代码两处复用。 不要"反正内网就跳过验签"——验签同时防伪造与防重放:

js
import crypto from "node:crypto";

const ts = req.header("X-Jlt-Timestamp") || "";
const sig = req.header("X-Jlt-Signature") || "";
// 1) 用原始请求体字节(不要 re-serialize 过的 JSON)
const expected = crypto
  .createHmac("sha256", APP_SECRET)
  .update(ts + "." + rawBody.toString("utf8"))
  .digest("hex");
// 2) 常量时间比较 + 时间窗
if (
  !sig ||
  !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)) ||
  Math.abs(Date.now() / 1000 - Number(ts)) > 3600
) {
  return res.status(401).end();
}

再按 X-Jlt-Event-Id 做幂等(重试会重发同一 id)。

4. authCode 的纪律 ​

  • 一次性 + 5 分钟:用后即弃,绝不缓存复用
  • 换到的身份建你自己的会话(cookie/JWT),不要每次请求都现换
  • org_role 只来自平台返回,不要让页面上报"我是管理员"

5. 页面侧 ​

  • 传输必须是 https(仅 localhost 调试放行 http)
  • 只通过 jlt.js 与容器通信;不自己仿写 postMessage 协议
  • 免登参数(jlt_auth_code 等)只从 URL fragment(旧版壳为 query)读取一次并 清掉,不写进 localStorage
  • 页面里渲染组织数据(成员列表等)注意 XSS:现代框架默认转义即可,避免 dangerouslySetInnerHTML / 手拼 HTML

6. 数据最小化 ​

  • contacts 能力拿到的是全量通讯录——只在你确实需要时申请,展示时遵守 最小必要
  • im 会打扰成员:只发与用户相关的通知,内容里标明来源与操作入口
  • ai 花的是组织积分:给调用设预算与日志,异常消耗可回溯
  • db 是只读直连组织的库——查询结果可能含敏感行,按需 SELECT、别整表拉回
  • ai_tools 让模型主动调你的服务——工具描述里别诱导模型滥用(比如"任何时候 都先调我"),回调按 user_id 自行校验业务权限
  • local 在用户设备上发起请求——尊重确认弹窗,不要高频调用刷弹窗,目标域名 收敛到你自己的服务
  • ai+local 同时授予会解锁 jlt.agent——这是开放能力里爆炸半径最大的一项: 模型能用用户已启用的全部技能/系统工具(文件/Shell/代码/本地数据库),不要因为 平台每轮都弹确认就递还给用户自己判断——你自己的页面也应该在发起 agent 调用前 说清楚这一轮要让它做什么,别拼一个模糊的 prompt 让用户无从预测后果;不需要完整 Agent 能力时优先用 localFetch/browserExtract/db 这类精确能力
  • 隔离浏览器会话(browserOpen…)不带用户登录态,但仍会在用户电脑上多步交互未确认 的网站——用完及时 browserClose(),不要长时间占着会话不关(会话数有全局上限, 占用会影响其它应用)

7. 本机文件访问与分享(v1.6.0) ​

chooseFile/saveFileAs/share 都不需要申请任何 scope——许可边界是 用户在系统原生对话框/选人组件里的那次明确操作,不是我们发的权限。但这不 代表可以随便调用:

  • chooseFile/saveFileAs 会弹出真实的系统对话框——不要在用户没有发起 相关操作(比如没点"导入文件"按钮)时主动调用,那样体验上等同于突然弹 一个文件选择框,用户会困惑甚至误触。
  • share 发出的消息是以用户自己的身份发给他选的同事——内容要跟用户 当前正在做的事强相关(比如分享正在协作的文档链接),不要塞入营销/推广 文案;这类消息对收件人而言看起来就是"同事发的一句话",滥用会破坏用户 对你应用、乃至对同事的信任。
  • share 仍然需要 im 能力(即使发起方是用户本人)——跟 POST /im/send 共用同一道闸门,见能力(scope)说明。

上线前 Checklist ​

项检查
secret 存放仅服务器环境变量/密钥服务,未出现在任何前端产物
IP 白名单已配置后端出口 IP(多台机器用 CIDR)
事件/工具验签验签 + 时间窗 + event_id 幂等都在(tool_call 同款)
密钥轮换演练过一次,知道更新配置的流程与生效时间
会话独立应用会话与机灵兔登录态完全分离,authCode 未被缓存
db 查询只 SELECT 业务必需的列/行,结果不回传敏感字段给模型或页面
本地能力调用频率克制(每次都弹确认),目标域名收敛
agent/浏览器会话不滥用 ai+local 组合权限;会话用完即时 browserClose()
本机文件/分享chooseFile/saveFileAs 只在用户主动操作时调用;share 内容与当前操作相关,不夹带营销文案
scope只勾了用得到的能力;若要发布到应用市场,已为每个 scope 填写用途说明并通过平台审核(见能力(scope)说明)

机灵兔开放平台 · 组织应用开发者文档