From 45ebf723d3ac5dda7bd7691876a5adc338c60d91 Mon Sep 17 00:00:00 2001 From: bgagent Date: Wed, 16 Sep 2026 12:50:35 -0400 Subject: [PATCH] fix(ci): clear false-positive untrusted-checkout-toctou via rename + disclose fork@sha (#896) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeQL alert #35 (actions/untrusted-checkout-toctou/critical) on integ.yml is a false positive: the checkout is already pinned to github.event.workflow_run.head_sha, an immutable commit SHA. The alert is an artifact of CodeQL's field-name heuristic — 'head_sha' matches the mutable-ref regex /(head|branch|ref)/ on the 'head' substring, so the SHA-pinned checkout is misclassified as a MutableRefCheckoutStep. Clear it at the source rather than dismissing it: - Rename the resolve job output head_sha -> commit_sha at all six sites (output decl, both $GITHUB_OUTPUT writes, checkout ref, integ + report HEAD_SHA env). 'commit_sha' matches /(sha|commit)/ only, so CodeQL classifies the step as SHACheckoutStep and the alert resolves to fixed. No value or control-flow change — the two human gates are untouched. - Document at the output declaration that the name is load-bearing, so a future rename does not silently reintroduce the alert. Also fix the one load-bearing gap the alert surfaced: add a top-level run-name so the fork repo and head SHA are visible in the run list and the deploy environment approval prompt. For workflow_run, GitHub attributes the run to the base repo, so the approver otherwise cannot see which fork/commit they are authorizing without opening the resolve job logs. Validated: zizmor --offline passes; YAML parses. --- .github/workflows/integ.yml | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/integ.yml b/.github/workflows/integ.yml index e1a224bad..4703221bd 100644 --- a/.github/workflows/integ.yml +++ b/.github/workflows/integ.yml @@ -1,5 +1,14 @@ name: integ +# Surface the fork and commit under test in the run list and the `deploy` +# environment approval prompt. For workflow_run, GitHub attributes the run to the +# base repo and github.sha is base main, so without this the approver cannot see +# which fork/commit they are authorizing to run with the deploy role — they would +# have to open the resolve job logs. Empty on workflow_dispatch (no workflow_run +# event), which GitHub renders as a bare "integ —"; the manual path is main-only. +run-name: >- + integ — ${{ github.event.workflow_run.head_repository.full_name }}@${{ github.event.workflow_run.head_sha }} + # Phase-0 deploy-then-verify integration tests (issue #236). integ-runner # deploys a trimmed Task API stack into the shared account behind # secrets.AWS_ROLE_TO_ASSUME, runs the create-and-persist smoke assertions, then @@ -56,7 +65,14 @@ jobs: pull-requests: read outputs: applicable: ${{ steps.decide.outputs.applicable }} - head_sha: ${{ steps.decide.outputs.head_sha }} + # Load-bearing name: this value is an immutable commit SHA + # (github.event.workflow_run.head_sha), and the checkout below pins to it. + # CodeQL's untrusted-checkout heuristic classifies a checkout `ref` by the + # field name — a name matching /(head|branch|ref)/ is treated as a mutable + # ref (false-positive TOCTOU alert), while /(sha|commit)/ is treated as an + # immutable SHA. Keep this `commit_sha`; renaming it back to `head_sha` + # reintroduces CodeQL alert #35 even though the value is unchanged. + commit_sha: ${{ steps.decide.outputs.commit_sha }} head_repo: ${{ steps.decide.outputs.head_repo }} steps: - name: Resolve applicability and post pending status @@ -76,14 +92,14 @@ jobs: # (the job's own checkout defaults). Mark applicable, skip status post. if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then echo "applicable=true" >> "$GITHUB_OUTPUT" - echo "head_sha=${GITHUB_SHA}" >> "$GITHUB_OUTPUT" + echo "commit_sha=${GITHUB_SHA}" >> "$GITHUB_OUTPUT" echo "head_repo=${REPO}" >> "$GITHUB_OUTPUT" echo "Manual dispatch — running integ against ${GITHUB_SHA}." exit 0 fi HEAD_SHA="$WF_HEAD_SHA" - echo "head_sha=${HEAD_SHA}" >> "$GITHUB_OUTPUT" + echo "commit_sha=${HEAD_SHA}" >> "$GITHUB_OUTPUT" echo "head_repo=${WF_HEAD_REPO}" >> "$GITHUB_OUTPUT" # Track whether we've posted a terminal integ-smoke status. If the job @@ -195,7 +211,7 @@ jobs: # code to run with the deploy role — the approver MUST review # cdk/test/integ/** changes before approving. repository: ${{ needs.resolve.outputs.head_repo }} - ref: ${{ needs.resolve.outputs.head_sha }} + ref: ${{ needs.resolve.outputs.commit_sha }} persist-credentials: false - name: Configure AWS credentials @@ -236,7 +252,7 @@ jobs: # needed to stay CloudFormation-valid. - name: Run integ tests (deploy → assert → destroy) env: - HEAD_SHA: ${{ needs.resolve.outputs.head_sha }} + HEAD_SHA: ${{ needs.resolve.outputs.commit_sha }} RUN_NUMBER: ${{ github.run_number }} run: | set -euo pipefail @@ -267,7 +283,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }} - HEAD_SHA: ${{ needs.resolve.outputs.head_sha }} + HEAD_SHA: ${{ needs.resolve.outputs.commit_sha }} INTEG_RESULT: ${{ needs.integ.result }} run: | set -euo pipefail