
Loop Library的GitHub OAuth投票实现HMAC签名nonce绑定与Fail-Closed灰度发布解析【免费下载链接】loop-libraryA library of practical AI-agent loops and an installable skill for finding, adapting, and designing repeatable agent workflows.项目地址: https://gitcode.com/gh_mirrors/lo/loop-libraryLoop Library 是一个收集和分享可复用 AI Agent 工作流Loop的开源库它让用户用 GitHub 账号登录后就能给优质 Loop 投票。这篇文章拆解它的投票系统为什么 OAuth 的 state 要用 HMAC 签名、nonce 如何绑定到浏览器标签页、以及为什么发布开关VOTING_UI_ENABLED必须是精确的true才生效Fail-Closed 灰度发布。一、30 秒看懂投票功能的设计约束 在写代码之前先理解 Loop Library 面临的三个坑它们直接决定了后面所有的安全设计代理会剥掉 Cookie。站点部署在 here.now 代理后面浏览器 Cookie 无法可靠透传所以会话不能做在 Cookie 里。代理会剥离 Origin 头。跨站防护不能只靠 Origin 白名单必须还有一道签名令牌。OAuth 回调是 GET 请求没法带自定义头或 JSON body所以把会话安全地交还给浏览器需要一点巧思。因此投票系统的核心思路是不用 Cookie、不用 URL 传令牌而是用一次性 nonce HMAC 签名 state 只出现在 JSON body 里的签名会话令牌三层配合。相关实现在 auth-votes.js数据存储实现在 vote-store.js。二、OAuth 完整流程浏览器 nonce 与 HMAC 签名 state第 1 步浏览器自己生成 nonce访问/auth/github时如果 URL 里没有client_nonceWorker 不会直接跳转而是返回一个极简的桥接页no-store禁止缓存。这个页面用crypto.getRandomValues生成 32 字节随机数转成 URL 安全的 Base64 后存进sessionStorage键名ll_oauth_nonce再带着它请求真正的 OAuth 起点。关键点nonce 由用户自己的浏览器生成且只存在于当前标签页的 sessionStorage。这就是后面防 CSRF 的锚点。第 2 步nonce 被 HMAC 签名进 state服务端收到合法 nonce32–128 位字母数字格式不合法直接 400后构造一个短命10 分钟有效的 state 值内容包含v: 1、provider: github、clientNonce、经过白名单校验的returnTo路径、过期时间exp整体做 Base64Url 编码再追加SESSION_SECRET计算的HMAC-SHA256 签名最后得到载荷.签名两段式结构作为state参数送往 GitHub 授权页校验时服务端用timingSafeEqual做恒定时间比较防止时序侧信道泄露签名信息。任何篡改——包括改returnTo到外部站点开放重定向——都会让签名校验失败。三、回调页为什么必须走HTML 桥才能保存会话 回调/auth/callback/github拿到code和state后Worker 用 OAuth code 换 access token再查 GitHub 用户资料最终生成一个30 天有效期的签名会话令牌。但这个令牌不能直接放进 URL会泄露到历史记录和 Referer也不能写 Cookie代理剥 Cookie。于是回调返回一个Cache-Control: no-store的 HTML 桥接页内嵌一段小脚本完成最后一步nonce 绑定校验读取并立即删除sessionStorage 里的ll_oauth_nonce一次性使用只有当存储的 nonce state 里签名的 nonce时才把会话令牌写入sessionStorage键名ll_session校验失败或脚本执行出错 → 一律跳转到auth_errorinvalid_state的错误页绝不保存令牌这就是Fail-Closed在登录侧的体现任何异常都默认拒绝。桥接页的 CSP 也只允许自身来源与内联脚本禁止任何外链资源。四、投票 API身份只来自签名令牌 ️投票端点POST /api/loops/{slug}/vote的防作弊链条防线行为失败结果签名会话令牌身份、用户名、voterKey 全部从令牌推导不接受请求体伪造401Origin 检查显式的不信任 Origin 直接拒绝缺失 Origin代理剥离放行靠令牌兜底403发布状态只对目录中已发布的 Loop 投票404请求体限长请求体上限 1024 字节投票值只允许 -1/0/1400 / 413投票数据存在专用 SQLite Durable ObjectVOTE_STORE里主键是(loop_slug, voter_key)同一账号每个 Loop 只有一票可随时改票或撤销。存储不可用时返回 503 而不是静默丢弃——同样是 Fail-Closed。完整端到端行为登录→投赞成→改反对→撤销→查询都有自动化测试覆盖见 auth-votes.test.js。五、Fail-Closed 灰度发布VOTING_UI_ENABLED为什么必须是精确的 true投票 UI 的前后端发布顺序很敏感Worker含认证路由必须先上线站点壳才能暴露投票按钮。为此项目把上线开关做成了默认关闭、显式开启部署阶段先把VOTING_UI_ENABLED设为false部署 Worker投票控件在页面上渲染为隐藏/禁用冒烟测试在正式域名完整跑一遍 GitHub 登录 → nonce 绑定回调 → 会话查询 → 投票 → 刷新 → 登出放量阶段确认无误后才把配置提交为精确字符串true只重新部署 Worker——已发布的站点立即显示投票功能无需再次发布站点服务端判断只认env.VOTING_UI_ENABLED true值为TRUE、缺失、或任何其他拼写前端拿到的都是uiEnabled: false。测试用例专门验证了这一点见 auth-votes.test.js 中 voting UI is fail-closed unless the launch flag is exactly true 一节。这套规则完整记录在 AGENTS.md 的 Authenticated voting 章节投票配置说明也在 README.md 的维护者笔记里。六、关键文件导航 想继续深入按这个顺序读即可认证与投票路由OAuth 起点、回调桥、HMAC、Origin 校验loop-library/worker/src/auth-votes.js投票存储SQLite 表结构、改票/撤票loop-library/worker/src/vote-store.js端到端测试登录、投票、Fail-Closed 场景loop-library/worker/test/auth-votes.test.jsWorker 部署配置VOTE_STOREDurable Object 绑定loop-library/worker/wrangler.jsonc发布与灰度操作规程AGENTS.md七、给想自建登录投票功能的三条经验 ✅代理/网关环境里别依赖 Cookie优先签名令牌 仅 body 传输的模型令牌永远不出现在 URL 里。state 不只是随机数把 nonce、回跳地址、过期时间一起签名回调侧再做一次 nonce 绑定校验才能同时挡住 CSRF、开放重定向和令牌窃取。发布开关要 Fail-Closed默认值、大小写、缺失值全部视为关闭用一个精确字符串作为唯一开启条件灰度放量就变成改一个配置 重部署后端的安全动作。【免费下载链接】loop-libraryA library of practical AI-agent loops and an installable skill for finding, adapting, and designing repeatable agent workflows.项目地址: https://gitcode.com/gh_mirrors/lo/loop-library创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考