From 4179d3e11afa7d048c30df209956e24c16b61667 Mon Sep 17 00:00:00 2001 From: Steven Serrata Date: Thu, 9 Apr 2026 17:57:39 -0400 Subject: [PATCH] ci: harden GitHub Actions workflows Port the pan.dev post-incident hardening checklist: - persist-credentials: false on all checkouts - pass user-controlled context via env vars, not shell interpolation - constrain artifact unzip to demo/build/* - post-build tamper diff on deploy-affecting config - environment: preview/production on deploy jobs - restore-only, SHA-pinned screenshot cache on PR path; trusted writer on main - concurrency group on live deploy - add CODEOWNERS for build-critical paths - remove obsolete canary-beta-release workflow Co-Authored-By: Claude Opus 4.6 --- .github/CODEOWNERS | 10 ++++ .github/workflows/build-perf.yml | 4 ++ .github/workflows/canary-beta-release.yml | 43 ----------------- .github/workflows/codeql-analysis.yml | 2 + .github/workflows/deploy-live.yml | 56 ++++++++++++++++++++++- .github/workflows/deploy-preview.yml | 25 +++++++--- 6 files changed, 89 insertions(+), 51 deletions(-) create mode 100644 .github/CODEOWNERS delete mode 100644 .github/workflows/canary-beta-release.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..db8cc7d84 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,10 @@ +# Build-critical paths require maintainer review +/.github/ @PaloAltoNetworks/docusaurus-openapi-maintainers +/scripts/ @PaloAltoNetworks/docusaurus-openapi-maintainers +/packages/ @PaloAltoNetworks/docusaurus-openapi-maintainers +/package.json @PaloAltoNetworks/docusaurus-openapi-maintainers +/yarn.lock @PaloAltoNetworks/docusaurus-openapi-maintainers +/demo/docusaurus.config.ts @PaloAltoNetworks/docusaurus-openapi-maintainers +/firebase.json @PaloAltoNetworks/docusaurus-openapi-maintainers +/.firebaserc @PaloAltoNetworks/docusaurus-openapi-maintainers +/.github/CODEOWNERS @PaloAltoNetworks/docusaurus-openapi-maintainers diff --git a/.github/workflows/build-perf.yml b/.github/workflows/build-perf.yml index 7d5a067fd..48baa0b44 100644 --- a/.github/workflows/build-perf.yml +++ b/.github/workflows/build-perf.yml @@ -18,6 +18,8 @@ jobs: pull-requests: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + with: + persist-credentials: false - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4 with: node-version: "22" @@ -41,6 +43,8 @@ jobs: contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + with: + persist-credentials: false - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4 with: node-version: "22" diff --git a/.github/workflows/canary-beta-release.yml b/.github/workflows/canary-beta-release.yml deleted file mode 100644 index e3dea4270..000000000 --- a/.github/workflows/canary-beta-release.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Canary Beta Release - -on: - push: - branches: - - v3.0.0 - paths: - - packages/** - -permissions: - contents: read - -jobs: - publish-canary: - name: Publish Canary Beta - runs-on: ubuntu-latest - if: ${{ github.repository == 'PaloAltoNetworks/docusaurus-openapi-docs' && github.ref == 'refs/heads/v3.0.0' && github.event_name == 'push' }} - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - with: - fetch-depth: 0 # Needed to get the commit number with "git rev-list --count HEAD" - - name: Set up Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4 - with: - node-version: "22" - cache: yarn - - name: Prepare git - run: | - git config --global user.name "Steven Serrata" - git config --global user.email "sserrata@paloaltonetworks.com" - git fetch - git checkout v3.0.0 - echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" >> .npmrc - env: - NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} - - name: Installation - run: yarn --frozen-lockfile --prefer-offline --ignore-scripts && yarn build-packages - - name: Publish Canary release - run: | - yarn canaryBeta - env: - NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 49361ebfc..1eb7ed00d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -22,6 +22,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + with: + persist-credentials: false - name: Initialize CodeQL uses: github/codeql-action/init@ebcb5b36ded6beda4ceefea6a8bc4cc885255bb3 # v3 diff --git a/.github/workflows/deploy-live.yml b/.github/workflows/deploy-live.yml index 991bbf588..2734fbea7 100644 --- a/.github/workflows/deploy-live.yml +++ b/.github/workflows/deploy-live.yml @@ -4,6 +4,10 @@ on: push: branches: [main] +concurrency: + group: deploy-live + cancel-in-progress: false + jobs: build: if: github.repository == 'PaloAltoNetworks/docusaurus-openapi-docs' @@ -15,6 +19,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + with: + persist-credentials: false - name: Setup node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4 @@ -31,6 +37,9 @@ jobs: - name: Build site run: yarn build-demo && zip -r build.zip demo/build + - name: Check for tampered config + run: git diff --exit-code -- firebase.json .firebaserc package.json yarn.lock 'demo/docusaurus.config.*' 'scripts/**' '.github/**' + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: build @@ -41,12 +50,16 @@ jobs: name: Deploy needs: build runs-on: ubuntu-latest + environment: production permissions: + contents: read id-token: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + with: + persist-credentials: false - name: Setup node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4 @@ -73,7 +86,7 @@ jobs: name: build - name: Unzip build artifact - run: unzip build.zip + run: unzip -n build.zip 'demo/build/*' - name: Deploy to Firebase id: deploy_live @@ -86,3 +99,44 @@ jobs: target: docusaurus-openapi.tryingpan.dev env: FIREBASE_CLI_PREVIEWS: hostingchannels + + cache_prod_screenshots: + name: Cache Prod Screenshots + needs: deploy + if: ${{ github.repository == 'PaloAltoNetworks/docusaurus-openapi-docs' }} + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + with: + persist-credentials: false + + - name: Setup node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4 + with: + node-version: "22" + cache: "yarn" + + - name: Install dependencies + run: yarn --frozen-lockfile --prefer-offline --ignore-scripts + + - name: Install Playwright + run: npx playwright install --with-deps chromium + + - name: Get production sitemap hash + id: sitemap-hash + run: | + hash=$(curl -fsSL https://docusaurus-openapi.tryingpan.dev/sitemap.xml | sha256sum | cut -d' ' -f1) + echo "hash=$hash" >> "$GITHUB_OUTPUT" + + - name: Capture production screenshots + run: yarn ts-node scripts/sitemap-visual-diff.ts --preview-url https://docusaurus-openapi.tryingpan.dev/ --concurrency 4 --paths "/tests/" + + - name: Save production screenshots + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + with: + path: visual_diffs/prod + key: prod-screenshots-${{ steps.sitemap-hash.outputs.hash }} diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml index 09e8786cf..f73620865 100644 --- a/.github/workflows/deploy-preview.yml +++ b/.github/workflows/deploy-preview.yml @@ -17,12 +17,12 @@ jobs: - name: Check if actor is org member id: is-org-member run: | - if [ "${{ github.actor }}" = "dependabot[bot]" ]; then + if [ "$ACTOR" = "dependabot[bot]" ]; then echo "is-org-member-result=false" >> "$GITHUB_OUTPUT" exit 0 fi status=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $GH_TOKEN" \ - https://api.github.com/orgs/PaloAltoNetworks/members/${{ github.actor }}) + "https://api.github.com/orgs/PaloAltoNetworks/members/${ACTOR}") if [ "$status" = "204" ]; then echo "is-org-member-result=true" >> "$GITHUB_OUTPUT" else @@ -30,6 +30,7 @@ jobs: fi env: GH_TOKEN: ${{ secrets.PAT }} + ACTOR: ${{ github.actor }} analyze: if: github.repository == 'PaloAltoNetworks/docusaurus-openapi-docs' && needs.precheck.outputs.is-org-member-result == 'true' @@ -50,6 +51,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Initialize CodeQL uses: github/codeql-action/init@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3 @@ -79,6 +81,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Initialize CodeQL uses: github/codeql-action/init@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3 @@ -104,6 +107,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Setup node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4 @@ -120,6 +124,9 @@ jobs: - name: Build site run: yarn build-demo && zip -r build.zip demo/build + - name: Check for tampered config + run: git diff --exit-code -- firebase.json .firebaserc package.json yarn.lock 'demo/docusaurus.config.*' 'scripts/**' '.github/**' + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: build @@ -130,6 +137,7 @@ jobs: needs: build if: ${{ github.repository == 'PaloAltoNetworks/docusaurus-openapi-docs' && !failure() && !cancelled() }} runs-on: ubuntu-latest + environment: preview permissions: contents: read pull-requests: write @@ -141,6 +149,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + with: + persist-credentials: false - name: Setup node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4 @@ -167,7 +177,7 @@ jobs: name: build - name: Unzip build artifact - run: unzip build.zip + run: unzip -n build.zip 'demo/build/*' - name: Deploy to Firebase id: deploy_preview @@ -195,6 +205,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Setup node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4 @@ -215,15 +226,15 @@ jobs: echo "hash=$hash" >> "$GITHUB_OUTPUT" - name: Restore cached production screenshots - uses: actions/cache@v5.0.4 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: visual_diffs/prod key: prod-screenshots-${{ steps.sitemap-hash.outputs.hash }} - restore-keys: | - prod-screenshots- - name: Run visual diff - run: yarn ts-node scripts/sitemap-visual-diff.ts --preview-url ${{ needs.deploy.outputs.preview_url }} --summary-file visual_diffs/results.json --concurrency 4 --paths "/tests/" + run: yarn ts-node scripts/sitemap-visual-diff.ts --preview-url "$PREVIEW_URL" --summary-file visual_diffs/results.json --concurrency 4 --paths "/tests/" + env: + PREVIEW_URL: ${{ needs.deploy.outputs.preview_url }} - name: Generate report and summary run: yarn ts-node scripts/generate-visual-diff-report.ts visual_diffs/results.json visual_diffs/index.html