From 9fb020311a4665a2360118993bbae8775192cb8b Mon Sep 17 00:00:00 2001 From: chaodu-agent Date: Mon, 13 Jul 2026 19:14:13 -0400 Subject: [PATCH] fix(ci): eliminate script injection in pending-decision.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reusable workflow interpolated ${{ inputs.pr_number }} and ${{ inputs.decision }} directly into the github-script JS source: const prNumber = ${{ inputs.pr_number }}; const decision = '${{ inputs.decision }}'; inputs.decision is a free-form string (only documented by a comment as "maintainer" or "contributor", not enforced by a choice type or any validation). A crafted decision value could break out of the single-quoted JS string literal and execute arbitrary code inside the github-script sandbox, which holds pull-requests:write + issues:write permissions. inputs.pr_number is typed as `number`, which GitHub Actions coerces at the workflow-call boundary, but relying on the caller's input schema alone is fragile defense — the value is still substituted as raw text before the JS parser ever sees it. This workflow currently has no caller in the repo (workflow_call with no invoking workflow found), so there's no live exploit path today. Fixing it now since it's a footgun for whoever wires it up next. Fix: pass both inputs through env: (the pattern used elsewhere in this repo's workflows, and the one GitHub's own security hardening guide recommends), and read them via process.env inside the script instead of string-interpolating into the JS source. Number() replaces the implicit number coercion; process.env values are never eval'd as code regardless of content, closing the injection regardless of the input's declared type. --- .github/workflows/pending-decision.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pending-decision.yml b/.github/workflows/pending-decision.yml index 9868234a0..478cea5fd 100644 --- a/.github/workflows/pending-decision.yml +++ b/.github/workflows/pending-decision.yml @@ -18,12 +18,15 @@ jobs: issues: write steps: - uses: actions/github-script@v7 + env: + PR_NUMBER: ${{ inputs.pr_number }} + DECISION: ${{ inputs.decision }} with: script: | const MAINTAINER = 'pending-maintainer'; const CONTRIBUTOR = 'pending-contributor'; - const prNumber = ${{ inputs.pr_number }}; - const decision = '${{ inputs.decision }}'; + const prNumber = Number(process.env.PR_NUMBER); + const decision = process.env.DECISION; const addLabel = decision === 'maintainer' ? MAINTAINER : CONTRIBUTOR; const removeLabel = decision === 'maintainer' ? CONTRIBUTOR : MAINTAINER;