news 2026/9/24 23:31:17

Cloudflare OS Slack Gatekeeper 完全指南:基于 Cloudflare Workers 的只读 Slack 工作区安全访问

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cloudflare OS Slack Gatekeeper 完全指南:基于 Cloudflare Workers 的只读 Slack 工作区安全访问
  • 人工智能
  • AI 应用
  • AI Agent
  • Agent 沙箱
  • AI 安全治理

【免费下载链接】cloudflare-os

Agent workspace built on Cloudflare Workers for creating documents, building apps, and running agents with your company’s context and systems.

项目地址:https://gitcode.com/gh_mirrors/cl/cloudflare-os
点击查看免费下载

本篇技术指南以 packages/gatekeeper-slack/README.md 为骨架,深入讲解 Cloudflare OS 中Slack gatekeeper的设计与实战:它如何以独立的 Cloudflare Worker 运行、通过 OAuth 2.0 用户令牌(xoxp-…)为 Agent 提供对 Slack 工作区(频道、私信、线程、成员、搜索)的只读访问,以及如何按工作区 / 会话 / 线程三种粒度授权与路由。读完本文,你将掌握 Slack 应用的 OAuth 配置要点、三种资源粒度的作用域映射、会话 API 的使用方法,以及底层令牌轮换、越权防护与观察者校验的实现原理。

一、定位:一个只读的 Slack 访问中介

Slack gatekeeper 是 Cloudflare OS 中众多 gatekeeper 之一,职责是"中介"(mediate)一个 Gadget 对用户 Slack 工作区的访问。它的三个核心设计约束是:

  • 只读:绝不发送或修改 Slack 数据,源码中以unreachableAction()强制实现(见 slack.ts),三个 gatekeeper 的applyAction/rejectAction/revertAction均不可达;
  • 独立 Worker:作为自己的 Cloudflare Worker 运行,由后端通过GATEKEEPER_SLACK绑定自动发现;
  • 用户视角:使用用户令牌(user token,xoxp-…)而非机器人令牌(bot token),因此 Agent 看到的正是连接用户所能看到的内容——包括私有频道、私信与搜索。

从源码结构看,该包由slack.ts(Worker 入口、OAuth 流程、Durable Object、gatekeeper 实现)、slack-api.ts(Slack Web API 客户端)、types.d.ts(Agent 面向的会话 API 类型)与configurator/(三种授权配置 UI)组成,依赖@gadgets/gatekeeper-kit(连接握手、凭据暂存)、@gadgets/workshop-shared/gatekeeper(gatekeeper 框架契约)与@gadgets/configurator-ui(配置器 UI 组件)。

二、认证:OAuth 2.0 用户令牌与 Slack 应用配置

2.1 为什么用 user token

README 明确指出,认证采用 OAuth 2.0,通过user_scope请求用户令牌而非机器人令牌。这样 Agent 的访问边界与连接用户完全一致——包括私有频道、直接消息与全局搜索。这一选择也贯穿到令牌交换的实现:exchangeAuthCode 在换取授权码时从响应嵌套的authed_user中提取用户令牌、刷新令牌、授权 scope、用户 ID 与团队 ID,并在缺少authed_user.access_token时抛出"请确保应用请求 user scope(user_scope)"的明确错误。

2.2 创建 Slack 应用并注入凭据

在 Slack 应用管理后台(https://api.slack.com/apps)创建应用后,将客户端凭据注入 Worker 环境变量:

变量用途
CLIENT_IDSlack OAuth 应用客户端 ID(生产环境通过 wrangler secrets /.dev.vars注入,不写入 wrangler.jsonc)
CLIENT_SECRETSlack OAuth 应用客户端密钥
BASE_URL公共 Worker URL(不含尾部斜杠);本地开发默认http://localhost:8787/gatekeeper/slack

本地开发时无需手动设置CLIENT_ID/CLIENT_SECRET:run-dev-server.ts 将根目录.dev.vars中的SLACK_CLIENT_ID/SLACK_CLIENT_SECRET映射为这两个变量,这也是包内dev脚本提示"在根目录运行pnpm dev-server"的原因(见 package.json)。

BASE_URL的解析逻辑见 slack.ts:getBaseUrl会去掉尾部斜杠,getBasePath提取其路径段,Worker 的fetch处理器会校验请求路径必须匹配该路径,否则抛错拒绝。

2.3 应用配置要点

按 README 与源码,Slack 应用必须正确配置三项:

  1. Redirect URL:必须与<BASE_URL>/oauth完全一致。本地开发默认即http://localhost:8787/gatekeeper/slack/oauth——注意 Worker 的 OAuth 回调处理器正是监听相对路径/oauth(见 slack.ts),并用getBaseUrl(env) + "/oauth"作为redirect_uri传给 Slack。
  2. 启用令牌轮换(OAuth & Permissions → Token Rotation):启用后令牌短期有效(约 12 小时),通过oauth.v2.access?grant_type=refresh_token自动刷新;未启用的传统长效令牌作为兜底按原样使用。轮换令牌是单次使用的,这正是 UserAccount DO 用#credentialUpdate串行化刷新、重连与吊销操作的原因。
  3. 申请所需 User Token Scopes:按授权资源粒度申请(见下节)。users:read始终被请求,用于已连接账户展示与用户名解析(源码中即IDENTITY_SCOPES = ["users:read"],见 slack.ts)。

2.4 令牌生命周期管理(源码级)

令牌的存取与刷新全部收敛在UserAccountDurable Object(SQLite 存储,见 wrangler.jsonc)中:

  • 换取与暂存acceptAuthCode完成授权码交换后写入accessToken/refreshToken/grantedScopes/userId/teamId;重连(reconnect)时新凭据先经stageCredentials暂存,待 Workshop 确认浏览器持有者后才由commitReconnect提交生效,期间绑定中的 Gadget 继续使用旧令牌(见 slack.ts);
  • 定时刷新getAccessToken在令牌过期前 5 分钟(ACCESS_TOKEN_EXPIRY_SAFETY_MS)触发刷新;刷新失败(invalid_refresh_token/token_expired/invalid_grant/token_revoked)时回调credentialsExpired通知后端并要求用户重新认证,见 slack-api.ts;
  • 自动清理:连接流程若从未完成,setCallback会设置 1 小时闹钟(alarm)自毁;revoke会同时吊销 access 与 refresh 令牌并清空存储(见 slack.ts);
  • Nonce 防重放:32 字节加密随机 nonce 经历"initiation → oauth"两阶段,每阶段 10 分钟过期,用crypto.subtle.timingSafeEqual常量时间比较校验,OAuth state 在换取前即被消费,防止回调重放(见 slack.ts)。

三、资源粒度与授权:三种 URL 模式、三类会话

访问按三种粒度授予,每种可授权资源映射到一个 URL 模式,同时驱动同意环节(申请哪些 OAuth scope)与路由环节(把资源 URL 路由到哪个 gatekeeper 类):

粒度URL 模式会话类型
整个工作区https://*(全实例兜底)SlackWorkspaceSession
单个会话(频道 / 私信 / 群组私信)https://app.slack.com/client/:teamId/:conversationIdSlackConversation
单个线程https://*.slack.com/archives/:conversationId/:messageIdSlackThread

工作区授权复用框架的账户级https://*模式;更具体的会话与线程 URL 优先。频道与私信共用同一种"Conversation"授权。源码中三种SupportedResource定义与SUPPORTED_RESOURCES列表见 slack.ts。

3.1 各资源的用户令牌作用域

资源申请的用户令牌作用域
工作区team:read、会话读取类 scope、search:read
会话会话读取类 scope、search:read
线程channels:historygroups:historyim:historympim:history
始终申请users:read

其中"会话读取类 scope"指channels/groups/im/mpim各自的:read:history,即channels:readchannels:historygroups:readgroups:historyim:readim:historympim:readmpim:history八项(源码常量CONVERSATION_READ_SCOPES,见 slack.ts)。

作用域与资源的映射有完整的双向换算逻辑(见 slack.ts):

  • resourceUrlPatternsToScopes:把要授权的资源 URL 模式换算成 OAuth 请求的作用域集合(users:read恒在);
  • grantedResourcesFromScopes:授权回调返回的grantedScopes反向换算成已授予的资源——只有全部必需 scope 都授予时该资源才暴露,避免"部分授权"造成的越权缝隙;
  • 未知的资源 URL 模式会直接抛错拒绝(validateResourceUrlPatterns)。

3.2 配置器 UI:三种授权交互

授权环节由三套配置器 UI 支撑(源码在 configurator/):

  • 工作区(workspace-configurator-ui.tsx):无需任何输入即就绪,资源 URL 自动解析为https://app.slack.com/client/<teamId>
  • 会话(conversation-configurator-ui.tsx):提供自动补全下拉框,可搜索频道 / 私信 / 群组私信;后端 ConversationConfiguratorUI 分页拉取用户会话并本地过滤,最多扫描 5 页、每页 200 条、返回上限 100 个选项;
  • 线程(thread-configurator-ui.tsx):要求粘贴 Slack 消息链接(https://<workspace>.slack.com/archives/C…/p…),并校验主机名必须以.slack.com结尾、路径为/archives/:conversationId/:messageId且 ID 符合[CDG][A-Z0-9]+p[0-9]+格式。

四、会话 API:Agent 的只读能力面

完整类型定义见 types.d.ts,这是 Agent 面向的公开契约。三类会话的能力如下:

4.1SlackWorkspaceSession(整个工作区)

方法说明
getInfo()获取工作区元数据(team ID、名称、域名)
listChannels()列出连接用户所在的公开与私有频道(分页Cursor
listDirectMessages()列出连接用户参与的私信与群组私信(分页Cursor
listUsers()列出工作区成员(分页Cursor
getUser(userId)按 Slack 用户 ID 查询单个用户
getConversation(conversationId)获取指定会话(频道或私信)的能力对象;无权限则抛错
search(query)按 Slack 搜索语法跨工作区搜索消息(如from:@bob in:#engineering budget

4.2SlackConversation(单个会话)

方法说明
getInfo()会话元数据(类型、名称、话题、成员数等)
members()列出会话成员(分页Cursor);1:1 私信可能只返回连接用户本人,识别对方请用getInfo().peer
listMessages()列出会话消息(最新在前,分页Cursor);线程回复不混排,需用getThread()
getThread(threadTs)获取包含指定消息ts的线程能力(根消息或任一回应的ts均可)
search(query)会话内搜索;无论查询怎么写,结果都硬性限制在本会话内(任何in:限定词都无法扩大范围)

会话内搜索的安全边界值得强调:实现上先把查询改写为in:#<频道名> <query>作为搜索提示,但真正的权威边界是服务端按restrictChannelId过滤结果——searchMessages会丢弃所有channel.id !== restrictChannelId的匹配(见 slack-api.ts)。README 所称"硬限制(hard-restricted)"即源于此。

4.3SlackThread(单个线程)

方法说明
getRoot()获取线程根(父)消息
listReplies()列出最多 1,000 条线程消息(含根消息,旧在前)

listReplies的实现限制为最多 20 页、每页 50 条(MAX_REPLY_PAGES/HISTORY_PAGE_SIZE),超出部分截断(见 slack.ts)。

4.4 数据模型与分页约定

  • 列表 / 搜索方法返回前向分页Cursor对象:反复调用next()直到返回null,使用完毕(含提前结束)需 dispose(见 types.d.ts)。SlackCursor内部串行化并发的next()调用,且每一页在返回给调用方之前都会经过授权检查(见 slack.ts);
  • 条目类型做了能力捆绑:SlackConversationEntry把会话元数据与可读该会话的能力对象打包,SlackMessageEntry把消息与其线程能力打包,取到即可直接深入,无需二次查找(见 types.d.ts);
  • 已知提及(mention)会渲染为可读名称:resolveText@user#channel<!here><!subteam^…>、链接与 HTML 实体统一解析为可读文本(见 slack-api.ts),作者与提及用户会批量预取并缓存;
  • 消息时间戳(ts)是线程的 ID;Slack permalink 中的p<秒><微秒>编码可还原为tsmessageIdToTs,见 slack.ts)。

五、底层原理:路由、授权与协作防护

5.1 从资源 URL 到 gatekeeper 类

SlackUserImpl.getGatekeeperClassFor解析 URL 并路由(见 slack.ts):

  • 主机名以.slack.com结尾且路径为/archives/<conversationId>/<messageId>SlackThreadGatekeeperImplthread_ts参数或消息 ID 解码确定线程根);
  • 主机名为app.slack.com且路径为/client/<teamId>/<conversationId>SlackConversationGatekeeperImpl
  • 其余app.slack.com/client/<teamId>SlackWorkspaceGatekeeperImpl
  • 无法识别的 URL 直接抛"Unsupported Slack resource URL"。

会话对象(SlackWorkspaceSessionImpl等)由各 gatekeeper 的startSession(approvalQueue)创建,并持有 API 客户端、批准队列与(工作区场景下)观察者追踪器(见 slack.ts)。

5.2 观察者(Observer)校验:协作时如何防止越权

Cloudflare OS 支持把已读取的数据分享给协作观察者。Slack gatekeeper 的观察者校验完全基于观察者自己的令牌,由SlackVerifier回答两个问题(见 slack.ts):

  • getTeamId():观察者令牌所属工作区,用于确认其是同一工作区成员;
  • hasConversationAccess(id):以观察者令牌调用conversations.info——公开频道对任何工作区成员可解析,私有频道 / 私信 / 群组私信仅对成员 / 参与者可解析,从而忠实执行 Slack 的 ACL;会话 ID 全局唯一,也顺带拒绝了跨工作区会话。

校验策略因绑定粒度而异(见 slack.ts):

  • 会话 / 线程绑定:采用"ACL 检查(单单元)"策略。绑定即单个会话(线程继承其会话的 ACL),只需在addObserver时用观察者自己的令牌确认其可读该会话;之后读取的内容不可能超出该会话,因此不追踪观察者,removeObserver为空操作;
  • 工作区绑定:采用"按会话的数据集追踪"策略。一个工作区绑定跨越大量 ACL 各异的会话,因此 gatekeeper 记录 Gadget 实际观察过哪些会话(trackedConversation:…,状态pendingobserved),addObserver要求工作区成员身份 + 对所有已观察会话的访问权;当首次观察新会话时(#prepareConversationObservation),会排除所有无权访问它的现有观察者,且观察被批准队列拦截时该会话保持pending、不会记为已泄露。校验的最终布尔结果只回传给 Slack gatekeeper 自身,因此可以信任。

5.3 每次读取都经过批准队列

所有暴露会话身份或内容的读取都经由authorizeConversationObservation走批准队列:工作区绑定下它会先做数据集追踪再授权,单单元绑定下则是普通授权(见 slack.ts)。例如listMessages每页都会提交"Read a page of N messages from conversation "的描述供批准;工作区级别的元数据读取(成员目录等)任何成员可见,会话 ID 列表为空时直接授权。

5.4 稳健的 API 客户端

slack-api.ts 中的客户端包含工程细节:

  • 限流重试:对 429 响应最多重试 2 次,按Retry-After头等待,上限 30 秒(RATE_LIMIT_MAX_RETRIES/RATE_LIMIT_MAX_WAIT_MS);
  • 并发上限:所有子请求扇出(批量预取用户、批量校验观察者)控制在 5 路并发以内,低于 Workers 6 路并发请求上限(MAX_CONCURRENT_REQUESTS,见 slack.ts),并采用"逐批Promise.all、首个失败即整体失败"的fail-closed语义;
  • 错误映射SlackApiError把 Slack 错误码映射为可读消息,并区分isAccessError(令牌用户无权看到该资源,如channel_not_foundnot_in_channelno_permission)与isAuthError(令牌过期或吊销,如invalid_authtoken_expired)——观察者校验正是靠这一区分把"无法证明有权限"保守判定为"无权限"(见 slack-api.ts);
  • permalinks:通过无需额外 scope 的auth.test获取工作区主机,为消息补齐https://<host>/archives/…链接(线程消息附带thread_tscid查询参数,恰好匹配线程资源 URL 模式)。

5.5 搜索的页式游标适配

Slack 的search.messages是基于页码而非游标的接口,客户端把页码编码进游标字符串(String(page + 1)),并根据paging.pages判断是否还有下一页(见 slack-api.ts)。

六、构建与部署

包内构建命令(见 package.json):

# 构建(含 capnweb RPC 校验代码生成) pnpm exec vp run -F @gadgets/slack-gatekeeper build

相关脚本说明:

  • dev:包内不可直接启动,须在仓库根目录运行pnpm dev-server(由 run-dev-server.ts 统一拉起各 gatekeeper 并完成环境变量映射);
  • deploy:先执行build:configuratorwrangler deploy
  • 部署前的类型校验由 wrangler.jsonc 的构建钩子完成:pnpm exec capnweb-validate build --out .wrangler/validate(见 wrangler.jsonc)。

运行时形态(见 wrangler.jsonc):Worker 需要 SQLite 持久化,迁移声明了UserAccountSlackWorkspaceGatekeeperImplSlackConversationGatekeeperImplSlackThreadGatekeeperImpl四个 Durable Object 类。部署时把CLIENT_ID/CLIENT_SECRET作为 wrangler secrets 注入(它们刻意不写入 wrangler.jsonc)。

七、实践要点速查

  1. 最小配置路径:创建 Slack 应用 → 配置 Redirect URL 为<BASE_URL>/oauth→ 开启 Token Rotation → 按需勾选 User Token Scopes → 注入CLIENT_ID/CLIENT_SECRETpnpm dev-server本地联调;
  2. 选择授权粒度:需要频道 / 私信 / 搜索全览选工作区(SlackWorkspaceSession);只需单一频道或私信选会话(SlackConversation);只读一条讨论串选线程(SlackThread),scope 最小、暴露面最小;
  3. 记住只读边界:三类会话都无写入 / 操作能力,getAutoApprovableActions恒为空,任何 action 调用都会抛错;
  4. 分页与释放:所有Cursor都要next()null并 dispose;线程回复上限 1,000 条;搜索查询非空且 ≤ 1,000 UTF-8 字节;
  5. 会话内搜索不可逃逸SlackConversation.search无论查询写什么,结果都由服务端按会话 ID 硬过滤。

延伸阅读

  • 连接流程与页面:@gadgets/gatekeeper-kit 的 connect-pages / credential-stage
  • 观察者与授权框架契约:workshop-shared 的 gatekeeper 模块(GatekeeperUserApprovalQueueCursor等)
  • 其他 gatekeeper 对照:gatekeeper-google、gatekeeper-github、gatekeeper-confluence
  • 人工智能
  • AI 应用
  • AI Agent
  • Agent 沙箱
  • AI 安全治理

【免费下载链接】cloudflare-os

Agent workspace built on Cloudflare Workers for creating documents, building apps, and running agents with your company’s context and systems.

项目地址:https://gitcode.com/gh_mirrors/cl/cloudflare-os
点击查看免费下载

相关推荐

上一篇:Navicat Mac版无限试用重置终极指南:3种简单方法实现永久免费使用
下一篇:Klavis Google Slides MCP Server 实战指南:基于 MCP 协议创建、编辑与管理演示文稿

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 23:30:57

像素风地图外轮廓描边实战:从方块数据到干净Canvas边缘

做像素风地图、独立游戏关卡编辑器、或者那种“拿一堆方块随机拼出一个岛再描边”的小工具时&#xff0c;我估计你大概率遇到过这个需求&#xff1a;以 120120 为单位的小方块&#xff0c;随机拼接成一块图形&#xff0c;最后给整个图形生成一条干净的描边。这个活儿听起来简单…

作者头像 李华
网站建设 2026/9/24 23:30:57

基于Django的农机租赁平台开发:订单状态机与并发控制实战

做农机租赁平台这个项目之前&#xff0c;我在农业信息化方向已经摸爬滚打了几年&#xff0c;但真正让我下定决心用Python把整套收割机租赁系统从零搭起来的&#xff0c;是一次在河南调研时看到的场景&#xff1a;收割季来临&#xff0c;种粮大户在村口蹲着等跨区作业的农机队&a…

作者头像 李华
网站建设 2026/9/24 23:30:32

CUA智能体实战:从多模态屏幕感知到自动操作的核心技术拆解

1. "cua"的三重身份&#xff1a;先从热搜词聊到技术主线 最近几个技术交流群和社交平台上&#xff0c;“cua”这三个字母的出镜率突然高了起来。有人把它当拟声词刷&#xff0c;说“cua的一下就完成了”&#xff0c;有人拿着某个名字很像的开源项目来问&#xff0c;但…

作者头像 李华
网站建设 2026/9/24 23:29:49

DeepSeek Harness:本地AI工作流编排引擎实战指南

1. 为什么“弃用Claude”不是情绪化选择&#xff0c;而是本地工作流演进的必然节点我从去年初开始把Claude作为主力模型接入日常知识管理、文档润色和代码辅助流程&#xff0c;用的是官方API加自建中间层路由。前两周体验确实惊艳——长上下文理解稳&#xff0c;逻辑链路清晰&a…

作者头像 李华
网站建设 2026/9/24 23:28:51

Agent技能体系:从对话到任务执行的关键工程实践

这几年大模型应用里最热的一个词&#xff0c;除了 RAG、Fine-tuning&#xff0c;就是 Agent。而真正上手做 Agent 的人&#xff0c;很快会撞上一个共同的坎&#xff1a;模型知道怎么聊天&#xff0c;但不知道怎么"干活"。你让它调个接口&#xff0c;它编一个不存在的…

作者头像 李华
网站建设 2026/9/24 23:28:10

胰腺病变分割数据集实战:210张训练图与可视化脚本

简介&#xff1a;本资源为胰腺病变图像分割数据集&#xff0c;面向医学图像分割方向的研究者、算法工程师及深度学习学习者&#xff0c;用于训练和评估二类别分割模型&#xff08;背景与病变区域&#xff09;。包内按训练集与测试集组织&#xff0c;训练集约210张图像及对应mas…

作者头像 李华