From f61c3335329d9ca43ebdd44d899c29d35e7045fd Mon Sep 17 00:00:00 2001 From: Jordan Ritter Date: Fri, 15 May 2026 13:51:57 -0700 Subject: [PATCH] Harden CI workflows: fix npm auth, git scope, JSON injection, timeouts - publish-release.yml: add missing NODE_AUTH_TOKEN env var for npm publish - update-competitive-matrix.yml: change git config --global to --local - All Slack notification curls: replace shell-interpolated JSON strings with jq -nc --arg to prevent injection from PR titles and other untrusted inputs (notify-pr, deploy-health-check, publish-release, update-competitive-matrix, deploy-pages, publish-docker, unreleased-check) - Add timeout-minutes to every job across all 12 workflows (the only job that already had one was zizmor at 5m) --- .github/workflows/dependabot-auto-merge.yml | 1 + .github/workflows/dependabot-major-analysis.yml | 1 + .github/workflows/deploy-health-check.yml | 5 ++++- .github/workflows/deploy-pages.yml | 5 ++++- .github/workflows/index-health-monitor.yml | 1 + .github/workflows/notify-pr.yml | 6 +++++- .github/workflows/publish-docker.yml | 9 +++++++-- .github/workflows/publish-release.yml | 12 ++++++++++-- .github/workflows/static-quality.yml | 4 ++++ .github/workflows/unreleased-check.yml | 5 ++++- .github/workflows/update-competitive-matrix.yml | 11 ++++++++--- 11 files changed, 49 insertions(+), 11 deletions(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 2189161..43ac59e 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -11,6 +11,7 @@ permissions: jobs: auto-merge: runs-on: ubuntu-latest + timeout-minutes: 5 if: github.event.pull_request.user.login == 'dependabot[bot]' steps: - name: Fetch Dependabot metadata diff --git a/.github/workflows/dependabot-major-analysis.yml b/.github/workflows/dependabot-major-analysis.yml index 9c76651..3bbb236 100644 --- a/.github/workflows/dependabot-major-analysis.yml +++ b/.github/workflows/dependabot-major-analysis.yml @@ -11,6 +11,7 @@ permissions: jobs: analyze-major: runs-on: ubuntu-latest + timeout-minutes: 5 if: github.event.pull_request.user.login == 'dependabot[bot]' steps: - name: Fetch Dependabot metadata diff --git a/.github/workflows/deploy-health-check.yml b/.github/workflows/deploy-health-check.yml index 53a7626..f2a7113 100644 --- a/.github/workflows/deploy-health-check.yml +++ b/.github/workflows/deploy-health-check.yml @@ -6,6 +6,7 @@ permissions: {} jobs: health-check: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Wait for deploy run: sleep 120 @@ -23,9 +24,11 @@ jobs: if: failure() run: | if [ -n "$SLACK_WEBHOOK" ]; then + payload=$(jq -nc --arg repo "$GH_REPOSITORY" --arg run "$GH_RUN_ID" \ + '{text: "Pathfinder production health check FAILED after deploy\n"}') curl -s -X POST "$SLACK_WEBHOOK" \ -H 'Content-Type: application/json' \ - -d "{\"text\":\"Pathfinder production health check FAILED after deploy\n\"}" || true + -d "$payload" || true fi env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 2aa6925..ef32c75 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -14,6 +14,7 @@ concurrency: jobs: deploy: runs-on: ubuntu-latest + timeout-minutes: 10 environment: name: github-pages url: ${{ steps.deploy.outputs.page_url }} @@ -37,7 +38,9 @@ jobs: GH_RUN_ID: ${{ github.run_id }} run: | if [ -n "$SLACK_WEBHOOK" ]; then + payload=$(jq -nc --arg repo "$GH_REPOSITORY" --arg run "$GH_RUN_ID" \ + '{text: "❌ *Pathfinder GitHub Pages deploy failed*\n"}') curl -s -X POST "$SLACK_WEBHOOK" \ -H "Content-Type: application/json" \ - -d "{\"text\":\"❌ *Pathfinder GitHub Pages deploy failed*\n\"}" || true + -d "$payload" || true fi diff --git a/.github/workflows/index-health-monitor.yml b/.github/workflows/index-health-monitor.yml index 198e9f3..f06ba84 100644 --- a/.github/workflows/index-health-monitor.yml +++ b/.github/workflows/index-health-monitor.yml @@ -18,6 +18,7 @@ permissions: jobs: monitor: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Restore state from cache uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 diff --git a/.github/workflows/notify-pr.yml b/.github/workflows/notify-pr.yml index 27c10f0..8aa00d9 100644 --- a/.github/workflows/notify-pr.yml +++ b/.github/workflows/notify-pr.yml @@ -6,6 +6,7 @@ permissions: {} jobs: notify: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Notify Slack if: github.actor != 'github-actions[bot]' @@ -17,7 +18,10 @@ jobs: PR_AUTHOR: ${{ github.actor }} run: | if [ -n "$SLACK_WEBHOOK" ]; then + payload=$(jq -nc --arg title "$PR_TITLE" --arg author "$PR_AUTHOR" \ + --arg url "$PR_URL" --arg num "$PR_NUMBER" \ + '{text: "New PR on pathfinder: *\($title)* by \($author)\n<\($url)|View PR #\($num)>"}') curl -sf -X POST "$SLACK_WEBHOOK" \ -H 'Content-Type: application/json' \ - -d "{\"text\":\"New PR on pathfinder: *${PR_TITLE}* by ${PR_AUTHOR}\n<${PR_URL}|View PR #${PR_NUMBER}>\"}" || true + -d "$payload" || true fi diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 17be7a8..e8c1bcd 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -6,6 +6,7 @@ permissions: {} jobs: docker: runs-on: ubuntu-latest + timeout-minutes: 30 permissions: contents: read packages: write @@ -36,9 +37,11 @@ jobs: REF_NAME: ${{ github.ref_name }} run: | if [ -n "$SLACK_WEBHOOK" ]; then + payload=$(jq -nc --arg ref "$REF_NAME" \ + '{text: "Docker image published: ghcr.io/copilotkit/pathfinder:\($ref)"}') curl -sf -X POST "$SLACK_WEBHOOK" \ -H 'Content-Type: application/json' \ - -d "{\"text\":\"Docker image published: ghcr.io/copilotkit/pathfinder:${REF_NAME}\"}" || true + -d "$payload" || true fi - name: Notify Slack — failure if: failure() @@ -48,7 +51,9 @@ jobs: GH_RUN_ID: ${{ github.run_id }} run: | if [ -n "$SLACK_WEBHOOK" ]; then + payload=$(jq -nc --arg repo "$GH_REPOSITORY" --arg run "$GH_RUN_ID" \ + '{text: "Docker publish failed\n"}') curl -sf -X POST "$SLACK_WEBHOOK" \ -H 'Content-Type: application/json' \ - -d "{\"text\":\"Docker publish failed\n\"}" || true + -d "$payload" || true fi diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index b139d73..0fa557c 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -7,6 +7,7 @@ permissions: {} jobs: build: runs-on: ubuntu-latest + timeout-minutes: 10 permissions: contents: read steps: @@ -63,6 +64,7 @@ jobs: needs: build if: needs.build.outputs.published == 'false' runs-on: ubuntu-latest + timeout-minutes: 10 environment: npm permissions: contents: write @@ -79,6 +81,8 @@ jobs: - name: Publish to npm run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Configure git credentials env: @@ -112,9 +116,11 @@ jobs: run: | VERSION="v${RELEASE_VERSION}" if [ -n "$SLACK_WEBHOOK" ]; then + payload=$(jq -nc --arg ver "$VERSION" --arg rel "$RELEASE_VERSION" --arg repo "$GH_REPOSITORY" \ + '{text: "📦 *@copilotkit/pathfinder \($ver) published*\nnpm: https://www.npmjs.com/package/@copilotkit/pathfinder/v/\($rel)\nRelease: https://github.com/\($repo)/releases/tag/\($ver)"}') curl -s -X POST "$SLACK_WEBHOOK" \ -H "Content-Type: application/json" \ - -d "{\"text\":\"📦 *@copilotkit/pathfinder ${VERSION} published*\nnpm: https://www.npmjs.com/package/@copilotkit/pathfinder/v/${RELEASE_VERSION}\nRelease: https://github.com/${GH_REPOSITORY}/releases/tag/${VERSION}\"}" || true + -d "$payload" || true fi - name: Notify Slack on failure @@ -125,7 +131,9 @@ jobs: GH_RUN_ID: ${{ github.run_id }} run: | if [ -n "$SLACK_WEBHOOK" ]; then + payload=$(jq -nc --arg repo "$GH_REPOSITORY" --arg run "$GH_RUN_ID" \ + '{text: "❌ *Pathfinder release failed*\n"}') curl -s -X POST "$SLACK_WEBHOOK" \ -H "Content-Type: application/json" \ - -d "{\"text\":\"❌ *Pathfinder release failed*\n\"}" || true + -d "$payload" || true fi diff --git a/.github/workflows/static-quality.yml b/.github/workflows/static-quality.yml index cc331ac..f8c01d0 100644 --- a/.github/workflows/static-quality.yml +++ b/.github/workflows/static-quality.yml @@ -11,6 +11,7 @@ permissions: jobs: prettier: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -22,6 +23,7 @@ jobs: build: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -33,6 +35,7 @@ jobs: version-sync: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -43,6 +46,7 @@ jobs: test: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: diff --git a/.github/workflows/unreleased-check.yml b/.github/workflows/unreleased-check.yml index dddc3aa..ee2f12e 100644 --- a/.github/workflows/unreleased-check.yml +++ b/.github/workflows/unreleased-check.yml @@ -10,6 +10,7 @@ permissions: jobs: check: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -58,9 +59,11 @@ jobs: if: steps.unreleased.outputs.count run: | if [ -n "$SLACK_WEBHOOK" ]; then + payload=$(jq -nc --arg count "$UNRELEASED_COUNT" --arg tag "$UNRELEASED_TAG" --arg repo "$GH_REPOSITORY" \ + '{text: "Unreleased changes on main — \($count) commits since \($tag)\n"}') curl -sf -X POST "$SLACK_WEBHOOK" \ -H 'Content-Type: application/json' \ - -d "{\"text\":\"Unreleased changes on main — ${UNRELEASED_COUNT} commits since ${UNRELEASED_TAG}\n\"}" || true + -d "$payload" || true fi env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/update-competitive-matrix.yml b/.github/workflows/update-competitive-matrix.yml index 9bcb772..6ef07f1 100644 --- a/.github/workflows/update-competitive-matrix.yml +++ b/.github/workflows/update-competitive-matrix.yml @@ -9,6 +9,7 @@ permissions: {} jobs: update: runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: write pull-requests: write @@ -30,7 +31,7 @@ jobs: - name: Configure git credentials for push env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: git config --global url."https://x-access-token:${GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/" + run: git config --local url."https://x-access-token:${GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/" - name: Create PR if: steps.changes.outputs.changed == 'true' id: pr @@ -47,9 +48,11 @@ jobs: if: steps.changes.outputs.changed == 'true' run: | if [ -n "$SLACK_WEBHOOK" ]; then + payload=$(jq -nc --arg url "$PR_URL_OUTPUT" \ + '{text: "Competitive matrix updated — PR created\n<\($url)|View PR>"}') curl -sf -X POST "$SLACK_WEBHOOK" \ -H 'Content-Type: application/json' \ - -d "{\"text\":\"Competitive matrix updated — PR created\n<${PR_URL_OUTPUT}|View PR>\"}" || true + -d "$payload" || true fi env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} @@ -58,9 +61,11 @@ jobs: if: failure() run: | if [ -n "$SLACK_WEBHOOK" ]; then + payload=$(jq -nc --arg repo "$GH_REPOSITORY" --arg run "$GH_RUN_ID" \ + '{text: "Competitive matrix scan failed\n"}') curl -sf -X POST "$SLACK_WEBHOOK" \ -H 'Content-Type: application/json' \ - -d "{\"text\":\"Competitive matrix scan failed\n\"}" || true + -d "$payload" || true fi env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}