From 1abebabab4a09b4aef5827ffbceacdcaf2af5403 Mon Sep 17 00:00:00 2001 From: liangshuo-1 <266696938+liangshuo-1@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:22:06 +0800 Subject: [PATCH] fix(ci): validate static workflow identity --- .github/workflows/semantic-review.yml | 46 +++++++++++------------- scripts/semantic-review-workflow.test.sh | 15 ++++++-- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/.github/workflows/semantic-review.yml b/.github/workflows/semantic-review.yml index 2fcf298cde..c18ecdb78b 100644 --- a/.github/workflows/semantic-review.yml +++ b/.github/workflows/semantic-review.yml @@ -25,19 +25,16 @@ jobs: with: script: | const run = context.payload.workflow_run; - if (run.name !== "CI") throw new Error(`unexpected workflow name: ${run.name}`); - let workflowPath = run.path || ""; - if (!workflowPath) { - const workflowId = Number(run.workflow_id || 0); - if (!Number.isInteger(workflowId) || workflowId <= 0) throw new Error("missing workflow id"); - const { data: workflow } = await github.rest.actions.getWorkflow({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: workflowId, - }); - workflowPath = workflow.path || ""; - } - if (workflowPath !== ".github/workflows/ci.yml") throw new Error(`unexpected workflow path: ${workflowPath}`); + const workflowId = Number(run.workflow_id || 0); + if (!Number.isInteger(workflowId) || workflowId <= 0) throw new Error("missing workflow id"); + const { data: workflow } = await github.rest.actions.getWorkflow({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: workflowId, + }); + if (workflow.name !== "CI") throw new Error(`unexpected workflow name: ${workflow.name}`); + if (workflow.path !== ".github/workflows/ci.yml") throw new Error(`unexpected workflow path: ${workflow.path}`); + if (run.path && run.path !== workflow.path) throw new Error(`workflow path mismatch: ${run.path}`); if (run.event !== "pull_request") throw new Error(`unexpected event: ${run.event}`); if (run.repository.id !== context.payload.repository.id) throw new Error("repository id mismatch"); if (run.repository.full_name !== context.payload.repository.full_name) throw new Error("repository name mismatch"); @@ -253,19 +250,16 @@ jobs: with: script: | const run = context.payload.workflow_run; - if (run.name !== "CI") throw new Error(`unexpected workflow name: ${run.name}`); - let workflowPath = run.path || ""; - if (!workflowPath) { - const workflowId = Number(run.workflow_id || 0); - if (!Number.isInteger(workflowId) || workflowId <= 0) throw new Error("missing workflow id"); - const { data: workflow } = await github.rest.actions.getWorkflow({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: workflowId, - }); - workflowPath = workflow.path || ""; - } - if (workflowPath !== ".github/workflows/ci.yml") throw new Error(`unexpected workflow path: ${workflowPath}`); + const workflowId = Number(run.workflow_id || 0); + if (!Number.isInteger(workflowId) || workflowId <= 0) throw new Error("missing workflow id"); + const { data: workflow } = await github.rest.actions.getWorkflow({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: workflowId, + }); + if (workflow.name !== "CI") throw new Error(`unexpected workflow name: ${workflow.name}`); + if (workflow.path !== ".github/workflows/ci.yml") throw new Error(`unexpected workflow path: ${workflow.path}`); + if (run.path && run.path !== workflow.path) throw new Error(`workflow path mismatch: ${run.path}`); if (run.event !== "pull_request") throw new Error(`unexpected event: ${run.event}`); if (run.conclusion !== "success") throw new Error(`unexpected conclusion: ${run.conclusion}`); if (run.repository.id !== context.payload.repository.id) throw new Error("repository id mismatch"); diff --git a/scripts/semantic-review-workflow.test.sh b/scripts/semantic-review-workflow.test.sh index 9e664ce697..2b5e118afd 100644 --- a/scripts/semantic-review-workflow.test.sh +++ b/scripts/semantic-review-workflow.test.sh @@ -176,7 +176,15 @@ if ! grep -Fq "if: always() && github.event.workflow_run.conclusion == 'success' exit 1 fi -require_in_step "$summary_verify_step" 'workflowPath !== ".github/workflows/ci.yml"' "PR quality summary must verify the triggering workflow path" +if grep -Fq 'run.name !== "CI"' "$workflow"; then + echo "semantic-review must not use the dynamic workflow run name as workflow identity" >&2 + exit 1 +fi + +require_in_step "$summary_verify_step" 'github.rest.actions.getWorkflow' "PR quality summary must resolve static workflow metadata" +require_in_step "$summary_verify_step" 'workflow.name !== "CI"' "PR quality summary must verify the static workflow name" +require_in_step "$summary_verify_step" 'workflow.path !== ".github/workflows/ci.yml"' "PR quality summary must verify the static workflow path" +require_in_step "$summary_verify_step" 'run.path && run.path !== workflow.path' "PR quality summary must reject workflow path metadata mismatches" require_in_step "$summary_verify_step" 'run.event !== "pull_request"' "PR quality summary must only handle pull_request workflow_run events" require_in_step "$summary_verify_step" 'run.repository.id !== context.payload.repository.id' "PR quality summary must verify workflow_run repository id" require_in_step "$summary_verify_step" 'const targetHeadSha = run.head_sha' "PR quality summary must use the CI run head SHA as the verified PR head" @@ -201,7 +209,10 @@ require_in_step "$summary_publish_step" 'CI_QUALITY_SUMMARY_BASE_SHA' "PR qualit require_in_step "$summary_publish_step" 'CI_QUALITY_SUMMARY_RUN_ID' "PR quality summary publisher must receive verified workflow run id" require_in_step "$summary_publish_step" 'require("./scripts/ci-quality-summary-publish.js")' "PR quality summary publisher must use the shared CI publisher script" -require_in_step "$verify_step" 'workflowPath !== ".github/workflows/ci.yml"' "semantic-review must verify the triggering workflow path" +require_in_step "$verify_step" 'github.rest.actions.getWorkflow' "semantic-review must resolve static workflow metadata" +require_in_step "$verify_step" 'workflow.name !== "CI"' "semantic-review must verify the static workflow name" +require_in_step "$verify_step" 'workflow.path !== ".github/workflows/ci.yml"' "semantic-review must verify the static workflow path" +require_in_step "$verify_step" 'run.path && run.path !== workflow.path' "semantic-review must reject workflow path metadata mismatches" require_in_step "$verify_step" 'run.repository.id !== context.payload.repository.id' "semantic-review must verify workflow_run repository id" require_in_step "$verify_step" 'run.event !== "pull_request"' "semantic-review must only handle pull_request workflow_run events" require_in_step "$verify_step" 'run.conclusion !== "success"' "semantic-review must only consume successful CI runs"