feat(api): mint workspace-scoped worker tokens for backend callbacks
**PR:**feat(api): mint workspace-scoped worker tokens for backend callbacks (#4156)
source_automation: “voyager-merged-pr” run_date: “2026-07-02” anchor_pr_number: 4156 pr_number: 4156 pr_title: “feat(api): mint workspace-scoped worker tokens for backend callbacks” pr_url: “https://github.com/adastralab-ai/voyager/pull/4156 ↗” author: “Horcrux / magicismight” merged_at: “2026-07-02T02:59:44Z” modules: [“backend/go/internal/core/workertoken”,“backend/go/pkg/gin/middleware”,“backend/go/apps/api/handler”,“backend/go/apps/api/contract_test”,“.github/actions/be-deploy”] files_changed: 17 learning_tags: [“worker-token”,“workspace-scope”,“api-authz”,“tenant-isolation”,“gin-middleware”,“contract-test”] business_line: null related_prs: [] line_stage: null open_questions: [] feishu_doc_url: “https://my.feishu.cn/docx/Q2mddaXUBosKaHxD50kcAZ2in2f ↗”#
正文来源:飞书学习文档 ↗。以下为通过个人 Feishu API 获取并转换后的完整 Markdown 正文。
今日选择#
**PR:**feat(api): mint workspace-scoped worker tokens for backend callbacks (#4156)
**作者:**Horcrux / magicismight
**Merge 时间:**2026-07-02 12:59:44 Australia/Melbourne(2026-07-02T02:59:44Z)
链接:https://github.com/adastralab-ai/voyager/pull/4156 ↗
**模块:**backend/go internal/core/workertoken, gin middleware, public asset access, API contract tests, deploy secret injection
为什么值得学#
- 它把“后台 worker 代表某个 workspace 回调 API”建模成短期 capability,而不是伪造用户 session 或把长期共享 secret 分发给 worker。
- 鉴权被拆成两段:middleware 只把已验证 token 转成 request scope,真正的资源授权仍由 handler 按 workspace 匹配决定。
- 失败路径选择了 additive/fail-through:无 token 或无效 token 不在全局 middleware 里 blanket abort,而是交给下游现有 authz 返回 401/403/404。
- 测试没有只测 token round trip,而是用 contract test 覆盖真实 API 行为:同 workspace 可读、跨 workspace 不可读、匿名无 token 不可读。
关键代码#
1. Token 只携带 workspace capability#
type claims struct {
jwt.RegisteredClaims
WorkspaceID string `json:"workspace_id"`
}
func (s *Signer) Mint(workspaceID uuid.UUID, ttl time.Duration) (string, error) {
if len(s.secret) == 0 {
return "", errors.New("worker token secret not configured")
}
now := time.Now()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims{
RegisteredClaims: jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(ttl)),
},
WorkspaceID: workspaceID.String(),
})
return token.SignedString(s.secret)
}plaintext设计点:claim 里没有 user id、role 或 arbitrary permission,只表达“这个 job 可访问哪个 workspace”。这降低了 token 被滥用时的语义范围,也避免 worker 侧理解用户权限模型。
2. Middleware 只注入 scope,不直接授权#
workspaceID, err := signer.Verify(strings.TrimPrefix(header, bearerPrefix))
if err != nil {
c.Next()
return
}
ctx := reqctx.WithWorkerScope(c.Request.Context(), &reqctx.WorkerScope{
WorkspaceID: workspaceID,
})
c.Request = c.Request.WithContext(ctx)
c.Next()plaintext设计点:全局 middleware 不知道具体 endpoint 的资源语义,所以它只解析 capability。无效 token 继续走下游鉴权,避免一个全局中间件改变所有 route 的错误语义。
3. Handler 做 workspace 边界检查#
if scope := reqctx.GetWorkerScope(c.Request.Context()); scope != nil {
if scope.WorkspaceID != workspaceID {
return uuid.UUID{}, bizerrors.NotFound("workspace not found", nil)
}
return workspaceID, nil
}plaintext设计点:worker token bypass 的不是资源边界,而是 membership/file-link 这类用户路径。跨 workspace 被伪装成 NotFound,和现有防枚举策略一致。
4. Contract test 覆盖租户隔离#
t.Run("grants the scoped workspace", func(t *testing.T) { ... http.StatusOK ... })
t.Run("denies a token scoped to another workspace", func(t *testing.T) { ... http.StatusNotFound ... })
t.Run("denies an anonymous caller with no token", func(t *testing.T) { ... require.NotEqualf(t, http.StatusOK, ...) })plaintext设计点:这里测的是公开 asset API 的最终行为,不只是 workertoken 包的纯函数。对这种跨鉴权层改动,行为测试比 mock middleware 更有价值。
和最近学习记录的关系#
本地学习日志此前为空,所以没有看到可关联的昨日或近 7-14 条记录。今天这条后续可以和 cover render、asset/font 获取、worker callback auth、workspace isolation 相关 PR 串起来看。
我会怎么吸收#
- 后台 job 需要访问用户/租户资源时,优先建模成短期、窄作用域 capability,而不是复用用户 session。
- 全局 middleware 适合做“解析和注入上下文”,资源授权尽量留在懂业务语义的 handler/service 层。
- 鉴权类 PR 的测试要覆盖真实入口和负例,尤其是跨租户 token、匿名请求、过期或错误 secret 这类边界。
边界 / 风险#
- PR 描述里明确 token 是 stateless 且不可提前撤销,所以 TTL 必须足够短;如果未来复用到长任务或更高权限写操作,需要重新评估撤销和审计。
- middleware 对无效 token fail-through,优点是兼容现有 authz,代价是排查 worker token 失败时需要从下游 401/403/404 和日志中定位。
- deploy 侧 secret 注入是 fail-open,适合渐进上线;但真正接入 worker 之前要确认各环境
WORKER\_TOKEN\_SECRET已存在,否则 mint 会报 “worker token secret not configured”。
候选说明#
2026-07-02 查到 22 个 merged PR,其中 20 个 base 是 main;2026-07-01 查到约 28 个 merged PR。最终选择 #4156,因为它涉及 API 鉴权边界、tenant isolation、middleware 与 handler 分层、secret 配置和 contract test,学习价值高于纯文案、样式、路由或小修复类 PR。由于学习日志为空,没有因为去重跳过候选。