十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

修复 Fork PR 标签自动化:hydra-ai 中基于 `pull_request_target` 的安全标签工作流改造实践

修复 Fork PR 标签自动化:hydra-ai 中基于 `pull_request_target` 的安全标签工作流改造实践 修复 Fork PR 标签自动化hydra-ai 中基于pull_request_target的安全标签工作流改造实践【免费下载链接】hydra-aiGenerative UI SDK for React项目地址: https://gitcode.com/GitHub_Trending/hy/hydra-ai导读本指南围绕 hydra-ai 仓库中的自动化标签工作流automation-labels.yml在外部贡献者 Fork 提交的 PR上无法生效的问题展开系统讲解其根因GitHub Actions 对 fork PR 授予只读GITHUB_TOKEN、安全权衡pull_request_target的pwn request风险、以及最终的GitHub API-only 改造方案用actions/github-script调用 REST API 替代tj-actions/changed-files checkout。读完本文你将掌握一套可复制的安全标签工作流模式既能给 fork PR 自动打标签又保证不执行任何来自 PR 的代码同时理解其中每一步的底层原理与取舍。背景fork PR 标签失败问题的提出在 hydra-ai 的 plans/fix-fork-pr-labeling.md 中记录了这样一个具体问题当外部贡献者从 fork 仓库提交 PR 时automation-labels.yml工作流无法为这些 PR 自动应用标签。受影响的 PR 包括 #1904、#1903、#1901、#1892、#1889、#1888、#1887、#1886它们全部来自外部贡献者。这一问题直接影响开源协作效率——外部贡献者无法获得基于文件变更自动计算出的area标签仓库维护者也无法依赖这些标签做分类与分流。仓库本身是一个 TypeScript monorepo包含apps/、packages/、react-sdk/、cli/、showcase/、docs/等模块见 apps 与 packages 目录结构自动化标签对这样一个多模块仓库的 PR 分流尤其重要——改动落在哪个模块理应自动打上对应标签。问题根因pull_request事件的只读 Token 限制故障现象链路当外部贡献者从 fork 打开 PR 时工作流按如下链路失败工作流以pull_request事件触发GitHub 出于安全考虑防止恶意代码通过 fork PR 修改主仓库将GITHUB_TOKEN对 fork PR 的权限强制降级为只读工作流中的gh pr edit --add-label命令因此失败报错GraphQL: Resource not accessible by integration (addLabelsToLabelable)最终结果fork PR 上没有任何自动标签。声明权限为何无效计划文档特别强调了一个容易误解的点即便在 YAML 中显式声明permissionsGitHub 仍会针对 fork PR 覆盖为只读permissions: pull-requests: write # -- GitHub overrides this to read for fork PRs也就是说这是事件级别event-level的安全策略而不是工作流配置错误。对比仓库内其他工作流可以看到仓库对pull_request事件通常会显式收紧或声明权限例如 ci.yml 在顶层声明permissions: contents: readconventional-commits.yml 声明contents: writepull-requests: write——这些声明在同仓库分支 PR上有效但在 fork PR 上写权限会被 GitHub 静默回收。安全研究为什么不能简单换成pull_request_targettj-actions/changed-files的真实风险计划文档首先纠正了一个常见的想当然原方案曾假设tj-actions/changed-files是通过 GitHub API 获取变更文件列表的。这个假设是错误的。根据该 action 的官方文档Leverages either GitHubs REST API or Gits native diff command to determine changed files.其默认模式走的是git diff这要求先actions/checkout检出代码——而一旦在pull_request_target事件下检出 fork 分支的代码就等于把攻击者的代码引入了 runner。更严重的是供应链风险2025 年 3 月tj-actions/changed-files曾遭供应链投毒supply chain attack导致约 23,000 个仓库的 secrets 泄露。计划文档以此论证凡是需要提权运行的第三方 action都应该被重新审视。GitHub Security Lab 的安全准则计划文档引用了 GitHub Security Lab《Preventing pwn requests》的核心观点The reason to introduce thepull_request_targettrigger was to enable workflows to label PRs (e.g., needs review) or to comment on the PR. The intent is to use the trigger for PRs that do not require dangerous processing.其关键原则是When the PR contents are treated as passive data, i.e., not in a position of influence over the build/testing process, it is safe.换言之pull_request_target本身并不可怕可怕的是在这个拥有写权限的事件上下文里去执行来自 PR 的代码checkout fork 分支后跑 npm install / build / test。什么情况下pull_request_target是安全的计划文档总结了三个使pull_request_target安全的必要条件工作流文件来自基础分支base branch攻击者无法通过修改 fork 分支来篡改工作流定义不做 checkout 就没有代码执行没有actions/checkout恶意代码就没有机会运行上下文数据是安全的github.event中的 PR 标题、标签、作者都是元数据metadata不是可执行内容。方案对比表计划文档给出了三条路线的安全对比方案是否需要 Checkout第三方 Action安全风险现状pull_request tj-actions是是低只读 token标签打不上pull_request_target tj-actions是是高pwn request会执行 fork 代码pull_request_target GitHub API否否低纯 API只读元数据结论很清晰要获得给 fork PR 打标签所需的写权限pull_request_target几乎是必经之路但必须搭配API-only实现彻底去掉 checkout 与第三方 action。解决方案GitHub API-Only 改造改造总览对 .github/workflows/automation-labels.yml 的改造包含四个要点PR 相关触发事件由pull_request改为pull_request_targetissues:与push:触发器保持不变用actions/github-script调用 GitHub REST API 替代tj-actions/changed-files做变更文件检测只在确实需要读取基础分支配置的作业中保留 checkout在文件中添加安全说明注释防止未来改动引入危险步骤。第一步修改触发事件# Before on: pull_request: types: [opened, synchronize, reopened, edited, labeled, unlabeled] # After on: pull_request_target: types: [opened, synchronize, reopened, edited, labeled, unlabeled]注意保留issues:与push:触发器不变——它们分别负责 issue 标签同步和推送事件下的标签维护与本次改动无关。第二步用 GitHub API 替换tj-actions/changed-files改造前meta作业需要 checkout 代码并调用tj-actions/changed-files# Before (in meta job) - name: Check out code uses: actions/checkoutv4 - name: Determine Areas uses: tj-actions/changed-filesv47 with: files_yaml: ...改造后不再 checkout直接用actions/github-script调用 REST API 的pulls.listFiles接口获取文件清单并在脚本内部完成区域area检测# After (use GitHub API - no checkout for file detection) - name: Determine Areas via API id: files_changed uses: actions/github-scriptv7 with: script: | const { data: files } await github.rest.pulls.listFiles({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.payload.pull_request.number, per_page: 100 }); const changedPaths files.map(f f.filename); // Area detection logic (matching current files_yaml patterns) const areaPatterns { api: /^apps\/api\//, web: /^apps\/web\//, showcase: /^showcase\//, cli: /^cli\//, db: /^packages\/db\//, core: /^packages\/core\//, backend: /^packages\/backend\//, react-sdk: /^react-sdk\//, documentation: /\.(md|mdx)$|^docs\/|^devdocs\//, github_actions: /^\.github\/(workflows|actions)\//, config: /^packages\/(eslint|typescript)-config\/|turbo\.json|mise\.toml/ }; const areas []; for (const [area, pattern] of Object.entries(areaPatterns)) { if (changedPaths.some(p pattern.test(p))) { areas.push(area); } } core.setOutput(changed_keys, JSON.stringify(areas));这段脚本与仓库实际的目录结构完全对应apps/api、apps/web、showcase、cli、packages/db、packages/core、packages/backend、react-sdk都是真实存在的模块可对照根目录 apps、packages、react-sdk、cli、showcase 查看而docs/、devdocs/、.github/、turbo.json、mise.toml也是仓库内实际路径。这样基于路径前缀的正则检测就能在不检出任何代码的前提下仅凭 API 返回的文件路径完成区域判定并将结果通过core.setOutput输出给后续打标签步骤。从仓库现有实践看这种脚本内做判定的模式并不陌生仓库里已有不少用github-script或内联 shell 处理复杂逻辑的工作流例如 workflow-check.yml 中利用jq解析 JSON 做多作业结果汇总说明该改造方式与仓库现有自动化风格一致。第三步仅在需要的作业中保留 checkoutrepo-labels作业需要读取基础分支上的.github/labels.yml标签定义文件注意当前仓库中该文件路径是计划中的目标文件实际标签定义可能位于.github/下的其他配置例如 dependabot.yml 中引用的area: github actions、area: dependencies等标签命名规范可作参考因此它保留 checkout但这在pull_request_target下是安全的不传ref:参数时actions/checkoutv4检出的是基础分支代码而不是 fork 分支该作业只同步标签定义不执行任何业务代码。# SAFE - repo-labels job - name: Checkout uses: actions/checkoutv4 # No ref: parameter checks out base branch, not fork仓库中也有反向的对照案例release-please-sync-lockfile.yml 在pull_request事件下显式使用ref: ${{ github.head_ref }}检出 PR 分支——那是需要处理 PR 分支内容的场景而标签场景恰恰相反必须避免检出 fork 分支。第四步添加安全注释护栏计划文档要求在 YAML 顶部加入安全说明既解释为什么安全也列出未来禁止添加的步骤# SECURITY NOTE: This workflow uses pull_request_target to gain write permissions # for labeling fork PRs. This is safe because: # 1. We do NOT checkout fork code (no ref: parameter) # 2. We use GitHub API for file detection (no tj-actions/changed-files) # 3. We only read PR metadata (title, author, file paths) - never execute PR code # # DO NOT add any step that: # - Uses ref: ${{ github.event.pull_request.head.sha }} # - Runs npm install/test/build on checked-out code # - Executes scripts from the PR这份注释是给未来维护者的安全护栏——pull_request_target的最大风险在于后来者在不知情的情况下加上 checkout fork 分支 执行代码的步骤从而引入 pwn request 漏洞。各作业改造清单作业当前状态需要的改动meta使用 checkout tj-actions替换为 GitHub API 脚本repo-labels使用 checkout基础分支保持现状安全不含 fork 代码do-not-merge无 checkout保持现状安全sync-labels使用 checkout基础分支保持现状安全不含 fork 代码备选方案双工作流隔离模式如果希望获得最大程度的安全隔离计划文档还给出了一个备选方案Workflow 1pull_request事件只收集元数据上传为 artifactWorkflow 2workflow_run事件下载 artifact应用标签。其权衡如下更复杂需要两个工作流文件更慢两次独立运行artifact 处理需要仔细的校验逻辑。计划文档的结论是单工作流的 API-only 方案更简单且由于全程不执行 fork 代码安全性与双工作流方案相当因此推荐单工作流方案。验收标准与测试方案验收标准计划文档列出了改造完成的完整验收清单将 PR 相关触发器的pull_request改为pull_request_target用actions/github-script GitHub REST API 替换tj-actions/changed-files移除不需要 checkout 的作业中的 checkout 步骤在repo-labels和sync-labels中保留 checkout安全——仅基础分支添加说明pull_request_target用途的安全注释将所有 actions 固定到 SHA 并附带版本注释这与仓库现有实践一致例如 ci.yml 中actions/checkoutdf4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3的写法保持issues:触发器不变保持push:触发器不变用 fork PR 验证标签能被应用用内部 PR 验证无回归手动测试要求Fork PR 测试请外部贡献者打开 PR或使用测试 fork验证标签是否被应用内部 PR 测试从主仓库分支创建 PR验证标签功能仍然正常防止pull_request_target改动影响内部 PR 流程区域检测测试验证基于文件的标签与预期模式匹配。安全验证部署后需确认没有执行任何 fork 代码检查工作流运行显示的是基础分支 checkoutmain而非 fork 分支确认tj-actions/changed-files已不再被使用确认文件检测仅通过 GitHub API 调用完成。风险与缓解措施风险可能性影响缓解措施意外检出 fork 代码低严重安全注释 代码评审GitHub API 速率限制低低每请求 100 个文件足够API 文件数量上限3000极低低单 PR 不太可能在 monorepo 中改动 3000 个文件与 tj-actions 的模式匹配差异中低彻底测试文件模式匹配其中模式匹配差异风险值得注意tj-actions/changed-files的files_yaml与areaPatterns正则的表达能力不同例如正则要求路径前缀精确对应^apps\/api\/不会匹配apps/api-extra/因此迁移后必须用代表性 PR 回归验证区域标签的准确性。总结与关键要点本次改造的本质是在给 fork PR 打标签与不执行 fork 代码之间找到安全交集pull_request事件对 fork PR 只读无法打标签直接换成pull_request_target 第三方 action如tj-actions/changed-files会引入 pwn request 与供应链双重风险正确姿势是pull_request_target GitHub REST APIactions/github-script把 PR 内容严格当作被动数据处理只读元数据、不检出、不执行配套措施包括仅保留基础分支的 checkout、SHA 固定 actions、安全注释护栏、fork 与内部 PR 双路径回归测试。这套模式不仅适用于 hydra-ai 的标签自动化也适用于任何需要在 fork PR 上获得写权限打标签、留言、指派的 GitHub Actions 工作流是一份可直接复用的安全模板。参考资源仓库内参考计划文档plans/fix-fork-pr-labeling.md目标工作流.github/workflows/automation-labels.yml本次改造对象计划文档中描述为当前存在标签定义参考.github/dependabot.yml 中area: github actions、area: dependencies等标签命名权限声明范例.github/workflows/ci.ymlpull_request下只读权限SHA 固定范例.github/workflows/ci.yml检出 PR 分支的反向案例.github/workflows/release-please-sync-lockfile.yml脚本内做判定的既有实践.github/workflows/workflow-check.yml外部参考计划文档原始引用计划文档原样引用了以下外部资料用于支撑论证本文不再展开GitHub: Events that trigger workflowsGitHub Security Lab: Preventing pwn requestsGitHub REST API: List pull request filestj-actions/changed-files 2025 年 3 月供应链投毒事件的公开分析说明本文所有实现细节、触发事件、权限行为与安全结论均以 plans/fix-fork-pr-labeling.md 及仓库现有工作流源码为依据其中automation-labels.yml与.github/labels.yml为计划文档描述的目标文件改造落地前需在仓库中确认其当前形态。【免费下载链接】hydra-aiGenerative UI SDK for React项目地址: https://gitcode.com/GitHub_Trending/hy/hydra-ai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表