From 9a0d2a9ab10c34218958fd1a0ec382af1733392a Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Thu, 13 Aug 2026 21:50:11 +0530 Subject: [PATCH 1/2] RELEASE @W-19079373@ Prevent PR title injection in validate-pr workflow Hardens the validate-pr GitHub Actions workflow against PR-title script injection (CWE-94) by passing the untrusted PR title and base ref through environment variables instead of inline ${{ }} interpolation, and adds a least-privilege 'permissions: contents: read' block. CI-only change (.github/workflows/validate-pr.yml); no runtime/package changes. Port of the fix already merged to dev (#2077) onto main. Fixes W-19079373. --- .github/workflows/validate-pr.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index 5c390ba1d..8e195dc95 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -3,6 +3,11 @@ on: pull_request: types: [edited, opened, reopened, synchronize] +# Principle of least privilege: this workflow only needs to read repository +# contents. Restricting the token limits the blast radius of any compromised step. +permissions: + contents: read + jobs: # We need to verify that the Pull Request's title matches the desired format. verify_pr_title: @@ -13,10 +18,16 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Verify PR Title + # Pass untrusted values (the PR title) via the environment rather than + # direct ${{ }} interpolation, so they are treated as data and cannot + # inject shell commands. + env: + PR_TITLE: ${{ github.event.pull_request.title }} + BASE_REF: ${{ github.base_ref }} run: | - title="${{ github.event.pull_request.title }}" + title="$PR_TITLE" title_upper=$(echo "$title" | tr '[:lower:]' '[:upper:]') - base_ref="${{ github.base_ref }}" + base_ref="$BASE_REF" # Define regex patterns for different types of PR titles MAIN2DEV_REGEX="^MAIN2DEV[[:space:]]*:?[[:space:]]*@W-[[:digit:]]{8,9}@.*MERGING.+[[:digit:]]{1,2}\.[[:digit:]]{1,2}\.[[:digit:]]{1,2}.*" From 28a6718c87cf82ab9078a5c8e2042e379dfa8458 Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Thu, 13 Aug 2026 22:36:31 +0530 Subject: [PATCH 2/2] CHANGE @W-19079373@ Also pass head_ref via env in validate-pr workflow Extends the PR-title injection hardening to the branch-ref checks: passes github.head_ref through an env var (HEAD_REF) and replaces the two inline ${{ startsWith(...) }} expressions with shell glob matches (== m2d/* and == release-*). This removes the last ${{ }} interpolations from the run block so all untrusted inputs are handled uniformly as data. Behavior is unchanged; verified the glob matches are equivalent to the prior startsWith() checks. Addresses review feedback on #2079. --- .github/workflows/validate-pr.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index 8e195dc95..849ead8eb 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -18,16 +18,18 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Verify PR Title - # Pass untrusted values (the PR title) via the environment rather than - # direct ${{ }} interpolation, so they are treated as data and cannot - # inject shell commands. + # Pass untrusted values (the PR title and branch refs) via the + # environment rather than direct ${{ }} interpolation, so they are + # treated as data and cannot inject shell commands. env: PR_TITLE: ${{ github.event.pull_request.title }} BASE_REF: ${{ github.base_ref }} + HEAD_REF: ${{ github.head_ref }} run: | title="$PR_TITLE" title_upper=$(echo "$title" | tr '[:lower:]' '[:upper:]') base_ref="$BASE_REF" + head_ref="$HEAD_REF" # Define regex patterns for different types of PR titles MAIN2DEV_REGEX="^MAIN2DEV[[:space:]]*:?[[:space:]]*@W-[[:digit:]]{8,9}@.*MERGING.+[[:digit:]]{1,2}\.[[:digit:]]{1,2}\.[[:digit:]]{1,2}.*" @@ -35,7 +37,7 @@ jobs: PR_INTO_DEV_OR_RELEASE_REGEX="^(FIX|CHANGE|NEW)([[:space:]]*\([^)]+\))?[[:space:]]*:?[[:space:]]*@W-[[:digit:]]{8,9}@.+" # Validate PR title based on base_ref and head_ref - if [[ "$base_ref" == "dev" && "${{ startsWith(github.head_ref, 'm2d/') }}" == "true" ]]; then + if [[ "$base_ref" == "dev" && "$head_ref" == m2d/* ]]; then if [[ ! "$title_upper" =~ $MAIN2DEV_REGEX ]]; then echo "::error::Invalid PR title: '$title'. Please follow the format: Main2Dev @W-XXXXXXXX@ Merging.*\d+\.\d+\.\d+" exit 1 @@ -45,7 +47,7 @@ jobs: echo "::error::Invalid PR title: '$title'. Please follow the format: RELEASE @W-XXXXXXXX@ Summary" exit 1 fi - elif [[ "$base_ref" == "dev" || "${{ startsWith(github.base_ref, 'release-') }}" == "true" ]]; then + elif [[ "$base_ref" == "dev" || "$base_ref" == release-* ]]; then if [[ ! "$title_upper" =~ $PR_INTO_DEV_OR_RELEASE_REGEX ]]; then echo "::error::Invalid PR title: '$title'. Please follow the format: FIX|CHANGE|NEW (__) @W-XXXXXXXX@ Summary" exit 1