From 2286b473229f9652a01ed24210a5a24d3e3377be Mon Sep 17 00:00:00 2001 From: Jordan Ritter Date: Thu, 14 May 2026 17:39:58 -0700 Subject: [PATCH 1/6] =?UTF-8?q?ci:=20harden=20existing=20workflows=20?= =?UTF-8?q?=E2=80=94=20SHA=20pins,=20permissions,=20shell=20injection,=20p?= =?UTF-8?q?ersist-credentials?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/agent-restricted.yml | 12 +++-- .github/workflows/build-and-test.yml | 20 +++++--- .github/workflows/cleanup-pr-tarballs.yml | 4 +- .github/workflows/codeql.yml | 11 ++-- .github/workflows/e2e-tests-full.yml | 11 ++-- .github/workflows/e2e-tests.yml | 24 +++++---- .github/workflows/lint.yml | 51 ++++++++++++------- .github/workflows/pr-size.yml | 5 +- .github/workflows/pr-tarball.yml | 25 +++++---- .github/workflows/pr-title.yml | 2 +- .github/workflows/release.yml | 25 +++++---- .../workflows/slack-issue-notification.yml | 5 +- .../workflows/slack-open-prs-notification.yml | 4 +- .github/workflows/strands-command.yml | 10 ++-- 14 files changed, 127 insertions(+), 82 deletions(-) diff --git a/.github/workflows/agent-restricted.yml b/.github/workflows/agent-restricted.yml index 7a0948861..9ba260dac 100644 --- a/.github/workflows/agent-restricted.yml +++ b/.github/workflows/agent-restricted.yml @@ -56,15 +56,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Check authorization + env: + AUTHORIZED_USERS: ${{ secrets.AUTHORIZED_USERS }} + ACTOR: ${{ github.actor }} run: | - AUTHORIZED_USERS="${{ secrets.AUTHORIZED_USERS }}" - if [[ ",$AUTHORIZED_USERS," != *",${{ github.actor }},"* ]]; then - echo "❌ User ${{ github.actor }} is NOT authorized" + if [[ ",$AUTHORIZED_USERS," != *",$ACTOR,"* ]]; then + echo "❌ User $ACTOR is NOT authorized" exit 1 fi - echo "✅ User ${{ github.actor }} is authorized" + echo "✅ User $ACTOR is authorized" - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Run Strands Agent uses: ./.github/actions/strands-action diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 88b1a857a..ad65f3740 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -25,9 +25,11 @@ jobs: node-version: [20.x, 22.x, 24.x] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: ${{ matrix.node-version }} cache: 'npm' @@ -36,14 +38,14 @@ jobs: git config --global user.email "bedrock-agentcore-npm+ci@amazon.com" git config --global user.name "CI" - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 - run: npm ci - run: npm run build --if-present - run: npm run test:unit - run: npm run test:integ - name: Upload coverage artifact if: matrix.node-version == '20.x' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: coverage-report path: coverage/ @@ -61,7 +63,7 @@ jobs: test -f sanitytest/agentcore/agentcore.json - name: Upload tarball artifact if: matrix.node-version == '20.x' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: tarball path: '*.tgz' @@ -75,14 +77,16 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Download coverage artifact - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: coverage-report path: coverage/ - name: Coverage Report - uses: davelosert/vitest-coverage-report-action@v2 + uses: davelosert/vitest-coverage-report-action@02f3c2e641286b7fa308cd3e430783103ce6103b # v2 with: json-summary-path: coverage/coverage-summary.json json-final-path: coverage/coverage-final.json diff --git a/.github/workflows/cleanup-pr-tarballs.yml b/.github/workflows/cleanup-pr-tarballs.yml index 1892f3a17..082907ea7 100644 --- a/.github/workflows/cleanup-pr-tarballs.yml +++ b/.github/workflows/cleanup-pr-tarballs.yml @@ -14,7 +14,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Delete PR tarball releases older than 7 days env: GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1e9b0a4bd..d7f2af67b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -8,6 +8,9 @@ on: pull_request_target: branches: ['main'] +permissions: + contents: read + # Cancel in-progress runs for PRs; never cancel runs on main (merges should not abort each other) concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -23,12 +26,14 @@ jobs: contents: read steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4 with: languages: javascript-typescript - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4 diff --git a/.github/workflows/e2e-tests-full.yml b/.github/workflows/e2e-tests-full.yml index 50528a8b3..ce6ae60e4 100644 --- a/.github/workflows/e2e-tests-full.yml +++ b/.github/workflows/e2e-tests-full.yml @@ -28,10 +28,11 @@ jobs: matrix: cdk-source: [npm, main] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || 'main' }} - - uses: actions/setup-node@v6 + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: '20.x' cache: 'npm' @@ -39,9 +40,9 @@ jobs: run: | git config --global user.email "ci@amazon.com" git config --global user.name "CI" - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v6 + uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6 with: role-to-assume: ${{ secrets.E2E_AWS_ROLE_ARN }} aws-region: ${{ inputs.aws_region || 'us-east-1' }} @@ -49,7 +50,7 @@ jobs: id: aws run: echo "account_id=$(aws sts get-caller-identity --query Account --output text)" >> "$GITHUB_OUTPUT" - name: Get API keys from Secrets Manager - uses: aws-actions/aws-secretsmanager-get-secrets@v3 + uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 # v3 with: secret-ids: | E2E,${{ secrets.E2E_SECRET_ARN }} diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 025b7b600..7b9f8ea59 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -25,18 +25,21 @@ jobs: steps: - name: Check authorization id: check + env: + EVENT_NAME: ${{ github.event_name }} + AUTHORIZED_USERS: ${{ secrets.AUTHORIZED_USERS }} + ACTOR: ${{ github.actor }} run: | - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then echo "✅ Manual workflow dispatch — authorized" echo "is_authorized=true" >> "$GITHUB_OUTPUT" exit 0 fi - AUTHORIZED_USERS="${{ secrets.AUTHORIZED_USERS }}" - if [[ ",$AUTHORIZED_USERS," == *",${{ github.actor }},"* ]]; then - echo "✅ User ${{ github.actor }} is authorized" + if [[ ",$AUTHORIZED_USERS," == *",$ACTOR,"* ]]; then + echo "✅ User $ACTOR is authorized" echo "is_authorized=true" >> "$GITHUB_OUTPUT" else - echo "⏭️ User ${{ github.actor }} is not in AUTHORIZED_USERS — skipping E2E tests." + echo "⏭️ User $ACTOR is not in AUTHORIZED_USERS — skipping E2E tests." echo "ℹ️ External contributors: ask a maintainer to run the E2E tests manually via workflow_dispatch." echo "is_authorized=false" >> "$GITHUB_OUTPUT" fi @@ -52,10 +55,11 @@ jobs: matrix: cdk-source: [npm, main] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/setup-node@v6 + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: '20.x' cache: 'npm' @@ -63,9 +67,9 @@ jobs: run: | git config --global user.email "ci@amazon.com" git config --global user.name "CI" - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v6 + uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6 with: role-to-assume: ${{ secrets.E2E_AWS_ROLE_ARN }} aws-region: ${{ inputs.aws_region || 'us-east-1' }} @@ -73,7 +77,7 @@ jobs: id: aws run: echo "account_id=$(aws sts get-caller-identity --query Account --output text)" >> "$GITHUB_OUTPUT" - name: Get API keys from Secrets Manager - uses: aws-actions/aws-secretsmanager-get-secrets@v3 + uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 # v3 with: secret-ids: | E2E,${{ secrets.E2E_SECRET_ARN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 24a9317f6..647da4b3d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,15 +18,17 @@ jobs: setup: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 20.x cache: 'npm' - run: npm ci - name: Cache node_modules - uses: actions/cache/save@v5 + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: node_modules key: node-modules-${{ hashFiles('package-lock.json') }} @@ -35,11 +37,13 @@ jobs: needs: setup runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 20.x - - uses: actions/cache/restore@v5 + - uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: node_modules key: node-modules-${{ hashFiles('package-lock.json') }} @@ -49,11 +53,13 @@ jobs: needs: setup runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 20.x - - uses: actions/cache/restore@v5 + - uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: node_modules key: node-modules-${{ hashFiles('package-lock.json') }} @@ -63,11 +69,13 @@ jobs: needs: setup runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 20.x - - uses: actions/cache/restore@v5 + - uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: node_modules key: node-modules-${{ hashFiles('package-lock.json') }} @@ -77,11 +85,13 @@ jobs: needs: setup runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 20.x - - uses: actions/cache/restore@v5 + - uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: node_modules key: node-modules-${{ hashFiles('package-lock.json') }} @@ -91,11 +101,13 @@ jobs: needs: setup runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 20.x - - uses: actions/cache/restore@v5 + - uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: node_modules key: node-modules-${{ hashFiles('package-lock.json') }} @@ -104,9 +116,10 @@ jobs: schema-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 + persist-credentials: false - name: Reject schema changes outside release PRs run: | if git diff --name-only origin/main...HEAD | grep -q '^schemas/agentcore\.schema\.v[0-9]*\.json$'; then diff --git a/.github/workflows/pr-size.yml b/.github/workflows/pr-size.yml index 1982c05fd..2e1f54a9a 100644 --- a/.github/workflows/pr-size.yml +++ b/.github/workflows/pr-size.yml @@ -6,6 +6,9 @@ on: pull_request_target: branches: [main] +permissions: + contents: read + jobs: label-size: runs-on: ubuntu-latest @@ -13,7 +16,7 @@ jobs: pull-requests: write steps: - name: Calculate PR size and apply label - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | const prNumber = context.payload.pull_request.number; diff --git a/.github/workflows/pr-tarball.yml b/.github/workflows/pr-tarball.yml index 3c5c5c522..8f9a97757 100644 --- a/.github/workflows/pr-tarball.yml +++ b/.github/workflows/pr-tarball.yml @@ -15,13 +15,15 @@ jobs: steps: - name: Check authorization id: check + env: + AUTHORIZED_USERS: ${{ secrets.AUTHORIZED_USERS }} + ACTOR: ${{ github.actor }} run: | - AUTHORIZED_USERS="${{ secrets.AUTHORIZED_USERS }}" - if [[ ",$AUTHORIZED_USERS," == *",${{ github.actor }},"* ]]; then - echo "✅ User ${{ github.actor }} is authorized" + if [[ ",$AUTHORIZED_USERS," == *",$ACTOR,"* ]]; then + echo "✅ User $ACTOR is authorized" echo "is_authorized=true" >> "$GITHUB_OUTPUT" else - echo "⏭️ User ${{ github.actor }} is not in AUTHORIZED_USERS — skipping." + echo "⏭️ User $ACTOR is not in AUTHORIZED_USERS — skipping." echo "is_authorized=false" >> "$GITHUB_OUTPUT" fi @@ -30,10 +32,11 @@ jobs: if: needs.authorize.outputs.is_authorized == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/setup-node@v6 + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: '20.x' cache: 'npm' @@ -41,7 +44,7 @@ jobs: run: | git config --global user.email "bedrock-agentcore-npm+ci@amazon.com" git config --global user.name "CI" - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 - run: npm ci - run: npm run build --if-present - run: npm pack @@ -56,6 +59,8 @@ jobs: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} TARBALL_NAME: ${{ steps.tarball.outputs.name }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + REPO: ${{ github.repository }} run: | TAG="pr-${PR_NUMBER}-tarball" @@ -68,12 +73,12 @@ jobs: --title "PR #${PR_NUMBER} Tarball" \ --notes "Auto-generated tarball for PR #${PR_NUMBER}." \ --draft \ - --target "${{ github.event.pull_request.head.sha }}" + --target "$PR_HEAD_SHA" - DOWNLOAD_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/${TARBALL_NAME}" + DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${TAG}/${TARBALL_NAME}" echo "url=$DOWNLOAD_URL" >> $GITHUB_OUTPUT - name: Comment on PR - uses: marocchino/sticky-pull-request-comment@v3 + uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 with: header: tarball message: | diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 3ab976395..217f6e8c4 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -14,7 +14,7 @@ jobs: permissions: pull-requests: read steps: - - uses: amannn/action-semantic-pull-request@v6.1.1 + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 21afc4e1f..89caef3bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,19 +38,21 @@ jobs: steps: - name: Validate running from main + env: + GIT_REF: ${{ github.ref }} run: | - if [[ "${{ github.ref }}" != "refs/heads/main" ]]; then - echo "⚠️ WARNING: Running from ${{ github.ref }}" + if [[ "$GIT_REF" != "refs/heads/main" ]]; then + echo "⚠️ WARNING: Running from $GIT_REF" echo "⚠️ Production releases should only run from main branch" fi - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 20.x @@ -200,9 +202,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: release/v${{ needs.prepare-release.outputs.version }} + persist-credentials: false - name: Verify version before build env: @@ -221,7 +224,7 @@ jobs: echo "✓ Version verified: $ACTUAL_VERSION" - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 20.x @@ -252,7 +255,7 @@ jobs: run: npm pack - name: Upload artifacts - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: dist path: | @@ -295,7 +298,7 @@ jobs: steps: - name: Checkout latest main (AFTER PR merge) - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: main fetch-depth: 0 @@ -309,7 +312,7 @@ jobs: git log -1 --oneline - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 22.x registry-url: 'https://registry.npmjs.org' @@ -321,7 +324,7 @@ jobs: echo "Updated npm version: $(npm --version)" - name: Download artifacts - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: dist path: . @@ -377,7 +380,7 @@ jobs: git push origin "v$VERSION" - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 env: VERSION: ${{ steps.version.outputs.version }} GITHUB_REPOSITORY: ${{ github.repository }} diff --git a/.github/workflows/slack-issue-notification.yml b/.github/workflows/slack-issue-notification.yml index 4f2b76597..d046bb00d 100644 --- a/.github/workflows/slack-issue-notification.yml +++ b/.github/workflows/slack-issue-notification.yml @@ -4,12 +4,15 @@ on: issues: types: [opened] +permissions: + contents: read + jobs: notify-slack: runs-on: ubuntu-latest steps: - name: Send issue details to Slack - uses: slackapi/slack-github-action@v3.0.1 + uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1 with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} webhook-type: webhook-trigger diff --git a/.github/workflows/slack-open-prs-notification.yml b/.github/workflows/slack-open-prs-notification.yml index 51ec8078b..bd8d5936c 100644 --- a/.github/workflows/slack-open-prs-notification.yml +++ b/.github/workflows/slack-open-prs-notification.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Get open PRs id: open-prs - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | const { data: prs } = await github.rest.pulls.list({ @@ -40,7 +40,7 @@ jobs: ); - name: Send open PRs summary to Slack - uses: slackapi/slack-github-action@v3.0.1 + uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1 with: webhook: ${{ secrets.SLACK_OPEN_PRS_WEBHOOK_URL }} webhook-type: webhook-trigger diff --git a/.github/workflows/strands-command.yml b/.github/workflows/strands-command.yml index bfba8c80d..a964f215e 100644 --- a/.github/workflows/strands-command.yml +++ b/.github/workflows/strands-command.yml @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check authorization - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | // Skip auth check for workflow_dispatch (manual runs) @@ -65,12 +65,12 @@ jobs: } - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 - name: Add strands-running label - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | const issueNumber = ${{ inputs.issue_id || github.event.issue.number || github.event.pull_request.number }}; @@ -83,7 +83,7 @@ jobs: - name: Process inputs and build prompts id: process-inputs - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | const processInputs = require('./.github/scripts/javascript/process-inputs.cjs'); @@ -112,7 +112,7 @@ jobs: - name: Remove strands-running label if: always() - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | try { From fbf37b5c90929f256740993139e4f0abf3764713 Mon Sep 17 00:00:00 2001 From: Jordan Ritter Date: Thu, 14 May 2026 17:39:58 -0700 Subject: [PATCH 2/6] ci: add zizmor static analysis for workflow security --- .github/workflows/security_zizmor.yml | 47 ++++++++++++++++++++++ .github/zizmor.yml | 57 +++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 .github/workflows/security_zizmor.yml create mode 100644 .github/zizmor.yml diff --git a/.github/workflows/security_zizmor.yml b/.github/workflows/security_zizmor.yml new file mode 100644 index 000000000..1474636c0 --- /dev/null +++ b/.github/workflows/security_zizmor.yml @@ -0,0 +1,47 @@ +name: security / zizmor + +on: + push: + branches: [main, master] + paths: + - ".github/workflows/**" + - ".github/actions/**" + - ".github/zizmor.yml" + - ".github/dependabot.yml" + pull_request: + paths: + - ".github/workflows/**" + - ".github/actions/**" + - ".github/zizmor.yml" + - ".github/dependabot.yml" + schedule: + - cron: "0 9 * * 1" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'schedule' && github.run_id || github.ref }} + cancel-in-progress: ${{ github.event_name != 'schedule' }} + +permissions: + contents: read + +jobs: + zizmor: + name: Static analysis (zizmor) + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Run zizmor + uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 + with: + min-severity: medium + advanced-security: false + annotations: true + config: .github/zizmor.yml diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 000000000..05606a6d5 --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,57 @@ +rules: + dangerous-triggers: + ignore: + # Dependabot auto-merge: uses pull_request_target for write token. + # Does NOT checkout PR code. Actor-gated to dependabot[bot]. + - dependabot-auto-merge.yml + # Dependabot major analysis: uses pull_request_target for PR comments. + # Does NOT checkout PR code. Actor-gated to dependabot[bot]. + - dependabot-major-analysis.yml + # Pre-existing: codeql.yml uses pull_request_target for code scanning. + - codeql.yml + # Pre-existing: e2e-tests.yml uses pull_request_target to access secrets + # for end-to-end testing infrastructure. + - e2e-tests.yml + # Pre-existing: pr-size.yml uses pull_request_target for PR labels. + - pr-size.yml + # Pre-existing: pr-tarball.yml uses pull_request_target to post build + # artifacts as PR comments. + - pr-tarball.yml + # Pre-existing: pr-title.yml uses pull_request_target for title validation. + - pr-title.yml + + # Pre-existing: template expansions in composite action and workflow run steps. + # strands-action uses inputs.* in run steps (composite action, inputs are typed). + # strands-command.yml interpolates inputs.* and github.event.issue.number into + # actions/github-script blocks; write-access-only triggers make exploitation unlikely. + template-injection: + ignore: + - action.yml + - strands-command.yml + + # Pre-existing: composite action uses unpinned action references. + # These are first-party or well-known actions within the composite action. + unpinned-uses: + ignore: + - action.yml + + # Pre-existing: e2e-tests.yml and release.yml set broad permissions + # at workflow level. e2e needs id-token for cloud auth. release needs + # contents/pull-requests write for creating releases. + # pr-tarball.yml needs contents/pull-requests write for posting artifacts. + excessive-permissions: + ignore: + - e2e-tests.yml + - pr-tarball.yml + - release.yml + + # Pre-existing: e2e-tests.yml writes to GITHUB_ENV from a step output. + # The value comes from a trusted GitHub Actions context, not user input. + github-env: + ignore: + - e2e-tests.yml + + # Pre-existing: release.yml uses setup-node with caching enabled. + cache-poisoning: + ignore: + - release.yml From 9a59d26cffe95287c07bf376c14f3650070310bf Mon Sep 17 00:00:00 2001 From: Jordan Ritter Date: Thu, 14 May 2026 17:39:58 -0700 Subject: [PATCH 3/6] ci: add Dependabot daily with auto-merge and major-version analysis --- .github/dependabot.yml | 25 ++- .github/workflows/dependabot-auto-merge.yml | 32 ++++ .../workflows/dependabot-major-analysis.yml | 144 ++++++++++++++++++ 3 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/dependabot-auto-merge.yml create mode 100644 .github/workflows/dependabot-major-analysis.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 09ed2343c..64872ada1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -27,14 +27,35 @@ updates: prefix: 'chore' include: 'scope' open-pull-requests-limit: 10 + cooldown: + default-days: 1 - package-ecosystem: 'github-actions' directory: '/' schedule: - interval: 'weekly' + interval: 'daily' groups: - github-actions: + minor-and-patch: + patterns: + - '*' + update-types: + - 'minor' + - 'patch' + # Workaround for dependabot/dependabot-core#14202: without an explicit + # major group, major updates matching the minor-and-patch pattern are + # silently suppressed. Remove this group when #14202 is fixed to get + # individual (ungrouped) PRs per major bump instead. + major: patterns: - '*' + update-types: + - 'major' + labels: + - 'dependencies' + - 'github-actions' commit-message: prefix: 'ci' + include: 'scope' + open-pull-requests-limit: 10 + cooldown: + default-days: 1 diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 000000000..218916149 --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,32 @@ +name: Dependabot Auto-Merge (Minor/Patch) + +on: + pull_request_target: + types: [opened, synchronize] + +permissions: + contents: write + pull-requests: write + +jobs: + auto-merge: + runs-on: ubuntu-latest + if: github.event.pull_request.user.login == 'dependabot[bot]' + steps: + - name: Fetch Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Auto-approve and merge minor/patch github-actions updates + if: >- + steps.metadata.outputs.package-ecosystem == 'github_actions' && + (steps.metadata.outputs.update-type == 'version-update:semver-minor' || + steps.metadata.outputs.update-type == 'version-update:semver-patch') + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + gh pr review "$PR_URL" --approve + gh pr merge "$PR_URL" --auto --merge diff --git a/.github/workflows/dependabot-major-analysis.yml b/.github/workflows/dependabot-major-analysis.yml new file mode 100644 index 000000000..3f0850937 --- /dev/null +++ b/.github/workflows/dependabot-major-analysis.yml @@ -0,0 +1,144 @@ +name: Dependabot Major Version Analysis + +on: + pull_request_target: + types: [opened] + +permissions: + contents: read + pull-requests: write + +jobs: + analyze-major: + runs-on: ubuntu-latest + if: github.event.pull_request.user.login == 'dependabot[bot]' + steps: + - name: Fetch Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Analyze major version bump + if: >- + steps.metadata.outputs.package-ecosystem == 'github_actions' && + steps.metadata.outputs.update-type == 'version-update:semver-major' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + DEP_NAME: ${{ steps.metadata.outputs.dependency-names }} + PREV_VERSION: ${{ steps.metadata.outputs.previous-version }} + NEW_VERSION: ${{ steps.metadata.outputs.new-version }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const depName = process.env.DEP_NAME; + const prevVersion = process.env.PREV_VERSION; + const newVersion = process.env.NEW_VERSION; + const parts = depName.split('/'); + const owner = parts[0]; + const repo = parts[1]; + const repoSlug = `${owner}/${repo}`; + + let releases = []; + try { + const { data } = await github.rest.repos.listReleases({ owner, repo, per_page: 50 }); + releases = data; + } catch (err) { + core.warning(`Could not fetch releases for ${repoSlug}: ${err.message}`); + } + + const prevMajor = parseInt(prevVersion.replace(/^v/, ''), 10); + const newMajor = parseInt(newVersion.replace(/^v/, ''), 10); + + const relevantReleases = releases.filter(r => { + const major = parseInt(r.tag_name.replace(/^v/, ''), 10); + return major > prevMajor && major <= newMajor; + }); + + let releaseNotesSummary = ''; + let breakingChanges = ''; + + if (relevantReleases.length === 0) { + releaseNotesSummary = '_No releases found between these versions._'; + breakingChanges = `_Unable to determine breaking changes automatically. Please review the [full changelog](https://github.com/${repoSlug}/releases)._`; + } else { + for (const release of relevantReleases.slice(0, 10)) { + const body = release.body || '_No release notes._'; + releaseNotesSummary += `### ${release.tag_name}${release.name && release.name !== release.tag_name ? ' — ' + release.name : ''}\n\n`; + releaseNotesSummary += body.substring(0, 2000); + if (body.length > 2000) releaseNotesSummary += '\n\n_...truncated_'; + releaseNotesSummary += '\n\n---\n\n'; + const lines = body.split('\n'); + for (const line of lines) { + if (/breaking|BREAKING|removed|deprecated|incompatible|migration/i.test(line)) { + breakingChanges += `- ${line.trim()}\n`; + } + } + } + } + + if (!breakingChanges) { + breakingChanges = '_No explicit breaking changes detected in release notes. Manual review recommended._'; + } + + let commentBody = `## :warning: Major Version Update — Manual Review Required + + | Field | Value | + |-------|-------| + | **Action** | [\`${depName}\`](https://github.com/${repoSlug}) | + | **Previous** | \`v${prevVersion}\` | + | **New** | \`v${newVersion}\` | + | **Type** | Major (\`v${prevMajor}\` → \`v${newMajor}\`) | + + ### Breaking Changes + + ${breakingChanges} + + ### Release Notes (v${prevMajor + 1} → v${newMajor}) + + ${releaseNotesSummary} + + ### Next Steps + + 1. Review breaking changes above + 2. Check if workflow inputs/outputs changed + 3. Verify compatibility with your CI/CD configuration + + > Full changelog: https://github.com/${repoSlug}/releases + + --- + _Generated automatically for Dependabot major version PRs._`.replace(/^ /gm, ''); + + if (commentBody.length > 64000) { + commentBody = commentBody.substring(0, 63900) + '\n\n_...comment truncated due to size limit._'; + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: commentBody, + }); + + try { + const labelsToAdd = ['major-update', 'needs-review']; + for (const label of labelsToAdd) { + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch { + const colors = { 'major-update': 'B60205', 'needs-review': 'FBCA04' }; + await github.rest.issues.createLabel({ + owner: context.repo.owner, repo: context.repo.repo, + name: label, color: colors[label] || 'EDEDED', + }); + } + } + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + labels: labelsToAdd, + }); + } catch (err) { + core.warning(`Could not add labels: ${err.message}`); + } From 5441e19855e8f033e8f71ff66b106c358c4a2c00 Mon Sep 17 00:00:00 2001 From: Jordan Ritter Date: Fri, 15 May 2026 09:10:28 -0700 Subject: [PATCH 4/6] ci: format new workflow files with prettier --- .github/workflows/dependabot-auto-merge.yml | 4 ++-- .../workflows/dependabot-major-analysis.yml | 4 ++-- .github/workflows/security_zizmor.yml | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 218916149..15a44138a 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -21,8 +21,8 @@ jobs: - name: Auto-approve and merge minor/patch github-actions updates if: >- - steps.metadata.outputs.package-ecosystem == 'github_actions' && - (steps.metadata.outputs.update-type == 'version-update:semver-minor' || + steps.metadata.outputs.package-ecosystem == 'github_actions' && (steps.metadata.outputs.update-type == + 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch') env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/dependabot-major-analysis.yml b/.github/workflows/dependabot-major-analysis.yml index 3f0850937..8a86404e6 100644 --- a/.github/workflows/dependabot-major-analysis.yml +++ b/.github/workflows/dependabot-major-analysis.yml @@ -21,8 +21,8 @@ jobs: - name: Analyze major version bump if: >- - steps.metadata.outputs.package-ecosystem == 'github_actions' && - steps.metadata.outputs.update-type == 'version-update:semver-major' + steps.metadata.outputs.package-ecosystem == 'github_actions' && steps.metadata.outputs.update-type == + 'version-update:semver-major' uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: DEP_NAME: ${{ steps.metadata.outputs.dependency-names }} diff --git a/.github/workflows/security_zizmor.yml b/.github/workflows/security_zizmor.yml index 1474636c0..217bb0689 100644 --- a/.github/workflows/security_zizmor.yml +++ b/.github/workflows/security_zizmor.yml @@ -4,18 +4,18 @@ on: push: branches: [main, master] paths: - - ".github/workflows/**" - - ".github/actions/**" - - ".github/zizmor.yml" - - ".github/dependabot.yml" + - '.github/workflows/**' + - '.github/actions/**' + - '.github/zizmor.yml' + - '.github/dependabot.yml' pull_request: paths: - - ".github/workflows/**" - - ".github/actions/**" - - ".github/zizmor.yml" - - ".github/dependabot.yml" + - '.github/workflows/**' + - '.github/actions/**' + - '.github/zizmor.yml' + - '.github/dependabot.yml' schedule: - - cron: "0 9 * * 1" + - cron: '0 9 * * 1' workflow_dispatch: concurrency: From dc84451e7a9532c657d1ed3f8efca66604e02342 Mon Sep 17 00:00:00 2001 From: Jordan Ritter Date: Fri, 15 May 2026 09:12:40 -0700 Subject: [PATCH 5/6] fix(ci): redesign pull_request_target workflows for fork safety, format new files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit e2e-tests.yml: Remove pull_request_target trigger entirely. The workflow is now workflow_dispatch-only with an optional pr_number input that checks out the PR merge ref. Maintainers trigger E2E runs manually, eliminating the risk of fork PRs executing arbitrary code with AWS credentials and API keys. The AUTHORIZED_USERS gate is removed since workflow_dispatch is already restricted to repo collaborators. pr-tarball.yml: Split into two workflows. The build step (pr-tarball.yml) now uses a plain pull_request trigger with read-only permissions — it builds the tarball and uploads it as an artifact. A new pr-tarball-publish.yml workflow triggers via workflow_run after the build succeeds, downloading the artifact and creating the draft release + PR comment with write permissions. The publish workflow runs base-branch code only, never PR-controlled code. zizmor.yml: Remove suppressions for e2e-tests.yml and pr-tarball.yml dangerous-triggers and excessive-permissions since neither workflow uses pull_request_target anymore. Also runs prettier on the three new workflow files (dependabot-auto-merge, dependabot-major-analysis, security_zizmor) and the redesigned files. --- .github/workflows/e2e-tests.yml | 51 +++++--------- .github/workflows/pr-tarball-publish.yml | 88 ++++++++++++++++++++++++ .github/workflows/pr-tarball.yml | 75 +++----------------- .github/zizmor.yml | 19 ++--- 4 files changed, 121 insertions(+), 112 deletions(-) create mode 100644 .github/workflows/pr-tarball-publish.yml diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 7b9f8ea59..2dad82d67 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -2,14 +2,16 @@ name: E2E Tests on: workflow_dispatch: inputs: + pr_number: + description: 'PR number to test (checks out the PR merge ref)' + required: false + type: string aws_region: description: 'AWS region for deployment' default: 'us-east-1' - pull_request_target: - branches: [main] concurrency: - group: e2e-${{ github.event.pull_request.number || github.ref }} + group: e2e-${{ inputs.pr_number || github.ref }} cancel-in-progress: false permissions: @@ -17,36 +19,7 @@ permissions: contents: read jobs: - authorize: - runs-on: ubuntu-latest - if: github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request_target' - outputs: - is_authorized: ${{ steps.check.outputs.is_authorized }} - steps: - - name: Check authorization - id: check - env: - EVENT_NAME: ${{ github.event_name }} - AUTHORIZED_USERS: ${{ secrets.AUTHORIZED_USERS }} - ACTOR: ${{ github.actor }} - run: | - if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then - echo "✅ Manual workflow dispatch — authorized" - echo "is_authorized=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - if [[ ",$AUTHORIZED_USERS," == *",$ACTOR,"* ]]; then - echo "✅ User $ACTOR is authorized" - echo "is_authorized=true" >> "$GITHUB_OUTPUT" - else - echo "⏭️ User $ACTOR is not in AUTHORIZED_USERS — skipping E2E tests." - echo "ℹ️ External contributors: ask a maintainer to run the E2E tests manually via workflow_dispatch." - echo "is_authorized=false" >> "$GITHUB_OUTPUT" - fi - e2e: - needs: authorize - if: needs.authorize.outputs.is_authorized == 'true' runs-on: ubuntu-latest environment: e2e-testing timeout-minutes: 30 @@ -55,9 +28,21 @@ jobs: matrix: cdk-source: [npm, main] steps: + - name: Resolve checkout ref + id: ref + env: + PR_NUMBER: ${{ inputs.pr_number }} + run: | + if [[ -n "$PR_NUMBER" ]]; then + echo "ref=refs/pull/${PR_NUMBER}/merge" >> "$GITHUB_OUTPUT" + echo "Checking out PR #${PR_NUMBER} merge ref" + else + echo "ref=${{ github.ref }}" >> "$GITHUB_OUTPUT" + echo "Checking out ${{ github.ref }}" + fi - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ steps.ref.outputs.ref }} persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: diff --git a/.github/workflows/pr-tarball-publish.yml b/.github/workflows/pr-tarball-publish.yml new file mode 100644 index 000000000..5f732467e --- /dev/null +++ b/.github/workflows/pr-tarball-publish.yml @@ -0,0 +1,88 @@ +name: PR Tarball (Publish) + +# Runs in the context of the BASE branch after PR Tarball (Build) completes. +# This workflow has write permissions but never checks out or executes PR code. +on: + workflow_run: + workflows: ['PR Tarball (Build)'] + types: [completed] + +permissions: + contents: write + pull-requests: write + +jobs: + publish-tarball: + runs-on: ubuntu-latest + if: >- + github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' + steps: + - name: Get PR number + id: pr + env: + GH_TOKEN: ${{ github.token }} + RUN_ID: ${{ github.event.workflow_run.id }} + REPO: ${{ github.repository }} + run: | + # The triggering workflow_run carries the PR info in its pull_requests array + PR_NUMBER=$(gh api "repos/${REPO}/actions/runs/${RUN_ID}" \ + --jq '.pull_requests[0].number') + if [[ -z "$PR_NUMBER" || "$PR_NUMBER" == "null" ]]; then + echo "::error::Could not determine PR number from workflow run" + exit 1 + fi + echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + + - name: Download tarball artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: pr-tarball + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ github.token }} + + - name: Get tarball info + id: tarball + run: | + TARBALL_NAME=$(ls *.tgz | head -1 | xargs basename) + echo "name=$TARBALL_NAME" >> "$GITHUB_OUTPUT" + + - name: Create or update PR release + id: release + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ steps.pr.outputs.number }} + TARBALL_NAME: ${{ steps.tarball.outputs.name }} + PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + REPO: ${{ github.repository }} + run: | + TAG="pr-${PR_NUMBER}-tarball" + + # Delete existing release if it exists (to update the tarball) + gh release delete "$TAG" --yes --cleanup-tag 2>/dev/null || true + + # Create a new pre-release with the tarball + gh release create "$TAG" \ + "${TARBALL_NAME}" \ + --title "PR #${PR_NUMBER} Tarball" \ + --notes "Auto-generated tarball for PR #${PR_NUMBER}." \ + --draft \ + --target "$PR_HEAD_SHA" + + DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${TAG}/${TARBALL_NAME}" + echo "url=$DOWNLOAD_URL" >> "$GITHUB_OUTPUT" + + - name: Comment on PR + uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 + with: + number: ${{ steps.pr.outputs.number }} + header: tarball + message: | + ## Package Tarball + + **[${{ steps.tarball.outputs.name }}](${{ steps.release.outputs.url }})** + + ### How to install + + ```bash + npm install ${{ steps.release.outputs.url }} + ``` diff --git a/.github/workflows/pr-tarball.yml b/.github/workflows/pr-tarball.yml index 8f9a97757..6e295319d 100644 --- a/.github/workflows/pr-tarball.yml +++ b/.github/workflows/pr-tarball.yml @@ -1,40 +1,17 @@ -name: PR Tarball +name: PR Tarball (Build) on: - pull_request_target: + pull_request: branches: [main] permissions: - contents: write - pull-requests: write + contents: read jobs: - authorize: - runs-on: ubuntu-latest - outputs: - is_authorized: ${{ steps.check.outputs.is_authorized }} - steps: - - name: Check authorization - id: check - env: - AUTHORIZED_USERS: ${{ secrets.AUTHORIZED_USERS }} - ACTOR: ${{ github.actor }} - run: | - if [[ ",$AUTHORIZED_USERS," == *",$ACTOR,"* ]]; then - echo "✅ User $ACTOR is authorized" - echo "is_authorized=true" >> "$GITHUB_OUTPUT" - else - echo "⏭️ User $ACTOR is not in AUTHORIZED_USERS — skipping." - echo "is_authorized=false" >> "$GITHUB_OUTPUT" - fi - - pr-tarball: - needs: authorize - if: needs.authorize.outputs.is_authorized == 'true' + build-tarball: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: @@ -52,42 +29,10 @@ jobs: id: tarball run: | TARBALL_NAME=$(ls *.tgz | head -1 | xargs basename) - echo "name=$TARBALL_NAME" >> $GITHUB_OUTPUT - - name: Create or update PR release - id: release - env: - GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ github.event.pull_request.number }} - TARBALL_NAME: ${{ steps.tarball.outputs.name }} - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} - REPO: ${{ github.repository }} - run: | - TAG="pr-${PR_NUMBER}-tarball" - - # Delete existing release if it exists (to update the tarball) - gh release delete "$TAG" --yes --cleanup-tag 2>/dev/null || true - - # Create a new pre-release with the tarball - gh release create "$TAG" \ - "${TARBALL_NAME}" \ - --title "PR #${PR_NUMBER} Tarball" \ - --notes "Auto-generated tarball for PR #${PR_NUMBER}." \ - --draft \ - --target "$PR_HEAD_SHA" - - DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${TAG}/${TARBALL_NAME}" - echo "url=$DOWNLOAD_URL" >> $GITHUB_OUTPUT - - name: Comment on PR - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 + echo "name=$TARBALL_NAME" >> "$GITHUB_OUTPUT" + - name: Upload tarball as artifact + uses: actions/upload-artifact@ea165f8d65b6db9a6b7c67862cd61e8cede3b60e # v4 with: - header: tarball - message: | - ## Package Tarball - - **[${{ steps.tarball.outputs.name }}](${{ steps.release.outputs.url }})** - - ### How to install - - ```bash - npm install ${{ steps.release.outputs.url }} - ``` + name: pr-tarball + path: '*.tgz' + retention-days: 30 diff --git a/.github/zizmor.yml b/.github/zizmor.yml index 05606a6d5..3ae57ca13 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -9,14 +9,8 @@ rules: - dependabot-major-analysis.yml # Pre-existing: codeql.yml uses pull_request_target for code scanning. - codeql.yml - # Pre-existing: e2e-tests.yml uses pull_request_target to access secrets - # for end-to-end testing infrastructure. - - e2e-tests.yml # Pre-existing: pr-size.yml uses pull_request_target for PR labels. - pr-size.yml - # Pre-existing: pr-tarball.yml uses pull_request_target to post build - # artifacts as PR comments. - - pr-tarball.yml # Pre-existing: pr-title.yml uses pull_request_target for title validation. - pr-title.yml @@ -35,18 +29,15 @@ rules: ignore: - action.yml - # Pre-existing: e2e-tests.yml and release.yml set broad permissions - # at workflow level. e2e needs id-token for cloud auth. release needs - # contents/pull-requests write for creating releases. - # pr-tarball.yml needs contents/pull-requests write for posting artifacts. + # Pre-existing: release.yml sets broad permissions at workflow level. + # release needs contents/pull-requests write for creating releases. excessive-permissions: ignore: - - e2e-tests.yml - - pr-tarball.yml - release.yml - # Pre-existing: e2e-tests.yml writes to GITHUB_ENV from a step output. - # The value comes from a trusted GitHub Actions context, not user input. + # e2e-tests.yml writes to GITHUB_ENV (CDK_TARBALL path from npm pack). + # The value comes from a trusted local command, not user input. + # Workflow is now workflow_dispatch-only (maintainer-triggered). github-env: ignore: - e2e-tests.yml From 7fcd916c44fdd9e34a28587f05bc3d37041c7c11 Mon Sep 17 00:00:00 2001 From: Jordan Ritter Date: Fri, 15 May 2026 09:20:50 -0700 Subject: [PATCH 6/6] fix(ci): correct upload-artifact SHA, resolve zizmor findings - Fix impostor commit: upload-artifact SHA was ea165f8d65b6db9a... (nonexistent), corrected to ea165f8d65b6e75b... (v4.6.2) - Fix template injection in e2e-tests.yml: move github.ref into env var (GH_REF) instead of inline ${{ }} expansion in run block - Fix version comment mismatch in codeql.yml: v4 -> v4.35.4 - Suppress workflow_run trigger warning for pr-tarball-publish.yml (by-design split build/publish pattern, never executes PR code) --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/e2e-tests.yml | 5 +++-- .github/workflows/pr-tarball.yml | 2 +- .github/zizmor.yml | 4 ++++ 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d7f2af67b..1531bd41d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -31,9 +31,9 @@ jobs: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4 + uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 with: languages: javascript-typescript - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4 + uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 2dad82d67..c5254ca2c 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -32,13 +32,14 @@ jobs: id: ref env: PR_NUMBER: ${{ inputs.pr_number }} + GH_REF: ${{ github.ref }} run: | if [[ -n "$PR_NUMBER" ]]; then echo "ref=refs/pull/${PR_NUMBER}/merge" >> "$GITHUB_OUTPUT" echo "Checking out PR #${PR_NUMBER} merge ref" else - echo "ref=${{ github.ref }}" >> "$GITHUB_OUTPUT" - echo "Checking out ${{ github.ref }}" + echo "ref=${GH_REF}" >> "$GITHUB_OUTPUT" + echo "Checking out ${GH_REF}" fi - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: diff --git a/.github/workflows/pr-tarball.yml b/.github/workflows/pr-tarball.yml index 6e295319d..41cd6b9e6 100644 --- a/.github/workflows/pr-tarball.yml +++ b/.github/workflows/pr-tarball.yml @@ -31,7 +31,7 @@ jobs: TARBALL_NAME=$(ls *.tgz | head -1 | xargs basename) echo "name=$TARBALL_NAME" >> "$GITHUB_OUTPUT" - name: Upload tarball as artifact - uses: actions/upload-artifact@ea165f8d65b6db9a6b7c67862cd61e8cede3b60e # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: pr-tarball path: '*.tgz' diff --git a/.github/zizmor.yml b/.github/zizmor.yml index 3ae57ca13..289154cf2 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -13,6 +13,10 @@ rules: - pr-size.yml # Pre-existing: pr-title.yml uses pull_request_target for title validation. - pr-title.yml + # pr-tarball-publish.yml uses workflow_run to run in base-branch context + # with write permissions. It never checks out or executes PR code — it only + # downloads the build artifact and publishes it as a GitHub release. + - pr-tarball-publish.yml # Pre-existing: template expansions in composite action and workflow run steps. # strands-action uses inputs.* in run steps (composite action, inputs are typed).