From ea29c39e2e13ebc4488bbb62051964ad9964d69d Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Sun, 17 May 2026 01:00:01 -0700 Subject: [PATCH] =?UTF-8?q?fix(ci):=20harden=20workflow=20security=20?= =?UTF-8?q?=E2=80=94=20prevent=20script=20injection=20and=20scope=20permis?= =?UTF-8?q?sions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace inline expression contexts with env vars in update-azd-core.yml to prevent command injection via repository_dispatch payloads (CWE-77) - Strengthen version validation to require strict semver format - Move workflow-level permissions to job-level in website.yml to enforce least privilege — build job now gets read-only access (CWE-276) Closes #95 Closes #97 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/update-azd-core.yml | 41 +++++++++++++++++---------- .github/workflows/website.yml | 16 +++++++---- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/.github/workflows/update-azd-core.yml b/.github/workflows/update-azd-core.yml index 03ab5d5..0fbbbf6 100644 --- a/.github/workflows/update-azd-core.yml +++ b/.github/workflows/update-azd-core.yml @@ -37,27 +37,33 @@ jobs: steps: - name: Resolve inputs id: inputs + env: + EVENT_NAME: ${{ github.event_name }} + DISPATCH_VERSION: ${{ github.event.client_payload.version }} + DISPATCH_BRANCH: ${{ github.event.client_payload.branch }} + INPUT_VERSION: ${{ inputs.version }} + INPUT_BRANCH: ${{ inputs.branch }} run: | - if [ "${{ github.event_name }}" = "repository_dispatch" ]; then - echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT - echo "branch=${{ github.event.client_payload.branch }}" >> $GITHUB_OUTPUT + if [ "$EVENT_NAME" = "repository_dispatch" ]; then + echo "version=$DISPATCH_VERSION" >> $GITHUB_OUTPUT + echo "branch=$DISPATCH_BRANCH" >> $GITHUB_OUTPUT else - echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT - echo "branch=${{ inputs.branch }}" >> $GITHUB_OUTPUT + echo "version=$INPUT_VERSION" >> $GITHUB_OUTPUT + echo "branch=$INPUT_BRANCH" >> $GITHUB_OUTPUT fi working-directory: . - name: Validate version + env: + VERSION: ${{ steps.inputs.outputs.version }} run: | - VERSION="${{ steps.inputs.outputs.version }}" - if [ -z "$VERSION" ]; then echo "ERROR: version is required" exit 1 fi - if [[ ! "$VERSION" =~ ^v ]]; then - echo "ERROR: Version must start with 'v' (e.g., v0.5.0)" + if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then + echo "ERROR: Version must be valid semver starting with 'v' (e.g., v0.5.0)" exit 1 fi working-directory: . @@ -70,16 +76,20 @@ jobs: - name: Create branch if: ${{ !steps.inputs.outputs.branch }} + env: + VERSION: ${{ steps.inputs.outputs.version }} run: | - BRANCH="update-azd-core-${{ steps.inputs.outputs.version }}" + BRANCH="update-azd-core-${VERSION}" git checkout -b "$BRANCH" echo "BRANCH=$BRANCH" >> $GITHUB_ENV working-directory: . - name: Set branch env if: ${{ steps.inputs.outputs.branch }} + env: + INPUT_BRANCH: ${{ steps.inputs.outputs.branch }} run: | - echo "BRANCH=${{ steps.inputs.outputs.branch }}" >> $GITHUB_ENV + echo "BRANCH=$INPUT_BRANCH" >> $GITHUB_ENV working-directory: . - name: Set up Go @@ -90,15 +100,16 @@ jobs: cache-dependency-path: cli/go.sum - name: Update azd-core + env: + VERSION: ${{ steps.inputs.outputs.version }} run: | - VERSION="${{ steps.inputs.outputs.version }}" GOWORK=off go get "github.com/jongio/azd-core@${VERSION}" GOWORK=off go mod tidy - name: Commit and push + env: + VERSION: ${{ steps.inputs.outputs.version }} run: | - VERSION="${{ steps.inputs.outputs.version }}" - if git diff --quiet HEAD -- go.mod go.sum; then echo "Already up to date" exit 0 @@ -114,8 +125,8 @@ jobs: if: ${{ !steps.inputs.outputs.branch }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.inputs.outputs.version }} run: | - VERSION="${{ steps.inputs.outputs.version }}" gh pr create \ --title "chore: update azd-core to ${VERSION}" \ --body "Update azd-core to ${VERSION}" diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index d2ccaac..0c0b4bd 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -20,16 +20,14 @@ concurrency: group: pages-${{ github.ref }} cancel-in-progress: true -permissions: - contents: write - pull-requests: write - packages: read - jobs: # Build job - runs for all triggers except PR closed build: if: github.event.action != 'closed' runs-on: ubuntu-latest + permissions: + contents: read + packages: read steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -90,6 +88,8 @@ jobs: if: github.event_name != 'pull_request' needs: build runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout gh-pages uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -128,6 +128,9 @@ jobs: if: github.event_name == 'pull_request' && github.event.action != 'closed' needs: build runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -207,6 +210,9 @@ jobs: cleanup-preview: if: github.event_name == 'pull_request' && github.event.action == 'closed' runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write steps: - name: Checkout gh-pages uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4