全部笔记

Worker 的 Secret 不能穿过浏览器边界

区分部署配置、运行时绑定和公开构建变量,建立可轮换的密钥管理流程。

某个 API 密钥没有提交进 Git,却被打包到了客户端 JavaScript。这仍然是泄漏。浏览器收到的所有资源都应视为公开内容,变量名里有 SECRET 不会改变这条边界。

本文假设 Worker 需要调用第三方服务,网页只访问本站接口。静态网站若不需要服务端能力,最好不要增加密钥与代理层;不必要的秘密是最容易消除的风险。

先画出凭据经过的每个执行环境

一个 Astro 项目可能同时存在本地开发、构建进程、Worker 运行时和浏览器。构建变量与运行时绑定不一定共享相同 API,也不具有相同生命周期。第三方令牌若只用于运行时代理,就不应为了方便先放进构建环境,再让组件间接读取。减少能够接触凭据的进程,比要求每个调用点永远不犯错更可靠。

构建阶段调用第三方获取公开数据是另一种需求。此时凭据只用于取数,最终产物必须仅包含允许公开的数据。第三方返回的调试字段、用户标识或签名地址可能随原始 JSON 一起被序列化。应在服务端转换成明确 DTO,再交给页面,不用展开运算符把全部上游对象传给岛屿。隐藏密钥不等于隐藏返回数据。

环境隔离也要落到权限。预览令牌可以读取测试内容,但不允许写生产资源;本地凭据可以有独立限额和撤销路径。若第三方不提供细粒度权限,必须在代理层限制操作,同时承认上游凭据一旦泄漏仍然有较大权限。不要用应用代码中的 if 判断冒充凭据本身的最小授权。

export default {
  async fetch(request, env) {
    const upstream = await fetch('https://api.example.com/data', {
      headers: { Authorization: `Bearer ${env.API_TOKEN}` },
    });
    if (!upstream.ok) {
      return new Response('Upstream unavailable', { status: 502 });
    }
    return new Response(await upstream.text(), {
      headers: { 'Content-Type': 'application/json' },
    });
  },
};

示例只展示读取绑定的边界,生产中还需验证请求来源与参数、限制调用频率,并筛选返回字段。把密钥藏在 Worker 并不意味着接口不会被滥用:匿名代理可能成为攻击者消耗第三方配额的入口。

Astro 的公开环境变量通常会进入客户端构建,不能使用它们承载服务端凭据。构建时使用秘密也有风险,例如把服务端调用结果原样写入 HTML,或在错误日志中打印请求头。

配置值与秘密使用不同工具

公开域名、功能开关、资源 ID 可以进入版本化配置;令牌、密码与签名密钥应使用平台 Secret 管理。局部开发文件必须被忽略,并提供不含值的示例文件说明所需名称。

npx wrangler secret put API_TOKEN

使用交互输入或受控 CI 注入,避免把秘密写在命令行参数中并进入 shell 历史。不同环境使用不同凭据,预览部署不应获得生产数据库或支付权限。最小权限要在第三方账户中实现,不是仅写在项目 README。

平台账户管理者能够配置 Secret,应用代码能够在运行时读取绑定,二者不是同一份权限。降低开发者账户权限、保护部署令牌和审查生产代码,都属于秘密边界。即使平台不允许直接查看已经保存的值,拥有部署权限的人仍可能部署代码把它返回出去,因此“控制台无法读取”不等于“部署者无法获得”。

团队交接时,记录谁可以修改绑定、谁可以部署、谁可以管理上游令牌。离职或账户受损时要撤销这些能力,并根据风险轮换秘密。只删除某人的 GitHub 权限,却保留其 Cloudflare 或供应商访问,会留下旁路。个人项目同样需要保护机器和账户,生产令牌不应散落在聊天记录和临时笔记。

CI 使用的部署凭据与 Worker 使用的业务凭据也应分离。前者负责上传和配置,后者负责调用上游;把管理员令牌塞进运行时以便应用“自动管理自己”,会显著扩大损害范围。预览流水线尤其不应获得修改生产 Secret 的能力。自动化方便不是突破环境隔离的理由。

对于只需要验证某个绑定存在的诊断,返回布尔值和非敏感版本即可。不要建立“查看所有环境变量”的管理接口,即使暂时需要密码保护,它也制造集中泄漏点。诊断输出可以在编译时禁用或限定授权,但最稳妥的是从设计上不输出实际值。记录必要状态与暴露秘密没有必然关系。

应急演练可以使用测试环境的假凭据模拟撤销:旧值失效后接口返回受控错误,配置新值并部署后恢复,日志不出现任意一把值。演练验证工具权限、配置路径和错误体验,而不需要真的中断生产。把恢复所需步骤跑通以后,事故中就不必临时猜测命令或复制令牌到不安全位置。

代理接口需要自己的使用契约

把上游令牌放在 Worker 中后,浏览器仍然能够调用该 Worker。若匿名端点接受任意上游路径和请求体,它就变成带账户额度的公共代理。应固定上游来源、允许操作和字段,验证请求体大小,设置速率与预算限制。来源头不是可靠身份认证,非浏览器客户端可以直接请求,CORS 也不是服务端调用的权限系统。

需要用户身份的业务接口必须验证会话,并根据用户权限决定可读取资源。公开查询接口则需要限定可公开数据,不能为了匿名方便返回上游全部结果。上游失败时只暴露必要错误类别,详细诊断留在受控日志中。不要把上游认证错误中的请求头或完整响应体转发到浏览器。

上游地址若受输入影响,还要防止 SSRF。固定域名与明确路由映射比允许任意 URL 更安全;重定向后的来源同样需要考虑。查询参数采用结构化 API 生成,不拼接用户原始字符串。秘密边界与输入边界在代理接口相遇,任何一边缺失都会让“密钥没有暴露”变成不完整的安全结论。

轮换要避免一次性中断

先创建新凭据并配置 Worker,验证新版本成功,再撤销旧凭据。若第三方支持双密钥,可以有短暂重叠期;若不支持,就需要明确维护窗口和回滚路径。切换期间记录密钥版本标识,不能记录实际值。

怀疑泄漏时,删除 Git 文件并不足以处理历史副本。先撤销凭据,再检查访问日志、权限范围与异常使用。历史重写可以减少继续传播,却无法保证别人没有保存旧内容。秘密泄漏的响应必须以失效为核心。

假设供应商支持两把同时有效的令牌。先创建权限相同或更窄的新令牌,配置目标环境,部署并运行受控请求,再观察正常流量是否使用新版本。确认后撤销旧令牌,再验证旧值确实失效。记录的是版本标识与操作时间,不保存实际令牌到轮换文档中。短暂重叠期需要上限,不能因担心回滚而永远保留旧密钥。

若供应商只允许一把有效令牌,需要提前安排切换窗口,明确新值获取、平台配置和验证的顺序。回滚不能简单恢复已经撤销的旧值,应准备重新签发或修复配置的路径。对签名密钥还要区分生成新签名和验证旧签名的兼容期,具体取决于协议;不把通用 API 令牌轮换流程直接套用到所有密钥类型。

怀疑泄漏时优先撤销可能导致短暂服务中断,但继续使用已暴露凭据可能扩大损失。决策应依据权限和风险,不以“不确定有没有被用”为理由拖延。保存相关访问证据,确认异常时间段与受影响资源,再恢复服务。秘密在 Git 历史、日志和缓存中出现时,都应假设副本不可彻底回收。

秘密管理记录应支持恢复与交接

维护名称、用途、环境、供应商权限、负责人和轮换条件,不维护实际值。运行时缺少绑定应明确失败,不退回硬编码默认令牌,也不把 undefined 当成普通上游凭据反复重试。健康检查可以验证配置是否存在,不能把值返回给检查者。权限变化或人员离开时,根据清单撤销相应访问,而不是在整个代码库猜测哪里用过令牌。

供应商额度是另一种风险边界。即使合法用户访问,异常重试也可能快速消耗预算。为上游调用设置超时、有限重试与错误分类,认证失败不要盲目重试。监控调用量与费用能够发现代理滥用,但警报不替代接口限制。恢复流程同时确认新凭据正常、旧凭据失效和浏览器没有得到秘密,三项缺一项都不算轮换完成。

用一条调用链检查数据与权限的缩减

浏览器提交文章 ID,Worker 验证当前用户、查找允许访问的资源,再向固定上游请求数据。每一跳都应缩减而不是扩大权限:浏览器不能选择上游 URL,Worker 不把用户输入直接变成账户级操作,上游结果只投影成用户有权读取的字段。密钥属于服务端只是起点,这条调用链才决定访问者能借它做什么。

例如上游返回完整客户记录,而页面只显示公开项目名称。应先在 Worker 中生成最小结果对象,再序列化为 JSON。把完整记录交给浏览器后用 CSS 隐藏邮箱,已经跨过保密边界。类似地,错误响应可能包含账户 ID、请求 URL 或供应商诊断数据,公开接口只返回需要的错误类别与关联 ID。私密详细信息留在受控日志,并接受日志自身的脱敏检查。

权限判断必须使用可信会话或其他明确认证方式,不能把 Origin 头当作用户身份。CORS 限制浏览器脚本读取响应,但非浏览器客户端依然可以直接调用端点。公开代理若按 IP 限流,也需要认识共享网络与攻击者分布访问的限制。限流控制消耗,授权控制资源,预算控制账户损失,这三项职责不能互相替代。

测试使用两个合成用户和两份资源,确认甲不能借代理读取乙的数据。再尝试未知上游路径、超大请求和不允许字段,确认拒绝发生在上游调用之前。可以让测试上游记录调用次数与合成参数,证明失败请求没有消耗第三方操作。不要为了验证隔离而查询真实其他用户资料,合成数据足以覆盖业务边界。

将凭据轮换视为版本兼容问题

供应商允许双令牌时,新旧 Worker 版本可能在传播期间同时运行。新版本读取新绑定,旧版本是否仍能读取旧绑定取决于平台配置与版本模型,不能假设部署一个新值就让所有旧实例同步切换。轮换计划需要核对实际平台行为,明确何时撤销旧令牌才能兼顾风险和可用性。版本号记录的是使用关系,不存储真实秘密。

签名密钥与 API 令牌的兼容要求不同。API 令牌往往只用于向外请求,签名密钥还涉及旧令牌验证。生成新签名可以立即改用新密钥,验证旧签名可能需要有限兼容窗口与密钥标识。协议应支持安全的密钥选择,不能根据任意用户输入读取文件或动态获取未经批准的密钥。旧签名的最长寿命决定窗口上界,而不是维护者“以后再清理”的习惯。

轮换过程中如果新凭据权限过窄,应先识别实际需要的操作,不直接恢复管理员权限。测试至少包含允许操作成功、禁止操作失败和旧凭据撤销后不可用。仅查询健康接口成功,不能证明真实业务具备必需权限。观察认证错误与额度变化,但不要把供应商返回的全部诊断体复制到公共报告。

怀疑泄漏时,兼容窗口可能需要缩短或立即关闭。恢复流程与正常轮换不能完全相同,因为继续保留旧令牌会让攻击者仍可使用。先按风险隔离,再恢复有限功能,并告知哪些操作暂时不可用。一个可用但仍受损的凭据不是回滚资产,不能为了恢复二百响应继续启用它。

给开发工具与依赖同样的秘密审查

Secret 不在客户端中,却可能出现在开发工具缓存、异常追踪附件或测试快照。读取完整 env 对象再传给通用调试函数,容易让业务代码之外的层记录它。审查统一日志中间件、错误处理器和遥测 SDK 的自动捕获配置,采用字段允许列表,而不是仅在一个接口里删除 Authorization。

本地工具需要秘密时,使用限定权限的开发凭据,并避免项目脚本自动复制生产配置。构建插件、包安装脚本和第三方 CLI 运行在能够读取环境的进程中,增加依赖也意味着扩大接触秘密的代码集合。CI 可以将取数、构建与部署阶段分开,仅在需要的阶段注入对应凭据。隔离阶段不能消除所有供应链风险,但能减少无关工具获得权限的机会。

测试快照应使用明确假值,既能检查脱敏,也不会造成真实凭据副本。若扫描发现真实值,报告命中文件与类型,不在终端再次打印全文。临时文件同样进入清理与权限管理,避免把配置备份放在可公开访问的输出目录。文件 Git 忽略不控制本机其他进程权限,更不控制部署工具扫描哪些目录。

从账户配额推导代理的资源预算

匿名查询接口可能没有数据泄漏,但攻击者可以消耗供应商额度。先确定每个合法任务最多触发几次上游调用,哪些失败允许重试以及重试上界。认证失败不应持续重试,超时也不一定意味着上游未执行。对于有费用或副作用的操作,应查询结果或使用供应商支持的幂等机制,不能把有限重试当作重复收费的完整解决方案。

为总预算和单用户消耗设置不同观察维度。共享 IP 限制可能影响合法用户,账户级预算可以保护总损失;达到预算时提供明确状态,而不是无限排队让所有请求超时。告警应在额度耗尽前触发,但阈值来自实际账户能力和业务需求,不套用一个固定数字。记录异常类别与凭据版本,帮助判断是公开代理被滥用、代码重试回归还是供应商故障。

恢复后检查三条证据:新凭据仅执行预期权限,旧凭据已经失效,浏览器与可下载产物不含秘密。再确认预算保护与错误响应仍然工作。密钥轮换解决已暴露值的生命周期,输入限制和资源预算解决代理继续被使用的范围,二者一起维护才能避免下一次同类事故。

在产物与日志两处检查

构建后检查 HTML、JS、source map 和配置产物是否出现密钥名称或测试哨兵。使用专门的假值进行测试,不要为了搜索而把真实密钥打印到终端或 CI 输出。依赖安全扫描可以补充检查,但不能识别所有业务凭据。

日志同样需要审计:不要记录完整请求、所有环境变量或第三方响应中的认证信息。错误应带关联 ID 和非敏感上下文。测试上游失败时,公开响应不应暴露令牌、内部地址或堆栈。

验收不仅是接口返回 200,还包括浏览器网络请求里没有第三方凭据、预览环境权限有限、撤销旧令牌后新版本正常,以及未授权调用受到限制。秘密管理的难点是整个生命周期,而不是把一个字段改叫 Secret。

扫描还要覆盖凭据的可逆变形。一个调试器可能把假令牌写入 URL 编码后的查询,另一个把认证字段序列化成 Base64;原值搜索没有命中,并不代表值没有交付。用独特假值分别走这两条合成路径,确认测试能识别产物中的变形。不要为覆盖更多格式把真实令牌及其编码批量写进扫描规则文件,那会制造新的秘密副本。

并非所有凭据派生物都与原值风险相同。服务端创建的短期签名下载地址可能不含原令牌,却仍授予某个对象的访问能力。检查器不能只寻找 API_TOKEN;接口契约还需列出哪些返回字段代表权限,以及它们的对象范围、有效期和可转交性。公开分享页若得到不该公开的签名地址,即使没有第三方密钥,保密边界依然失效。

验收让无权合成用户尝试取得受控对象地址,确认拒绝发生在签名之前;允许用户取得地址后,再按协议检查过期与对象范围。若服务不能撤销已发出的签名能力,如实记录剩余窗口,而不是声称轮换 API 令牌会自动撤销全部派生链接。原始凭据不泄漏与派生授权不越界,是两份需要分别证明的结果。

参考:Workers Secrets、Astro 环境变量。