From 448a09b39e055e57875f007739ecaa75988f929a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fahreddin=20=C3=96zcan?= Date: Tue, 12 May 2026 12:21:37 +0300 Subject: [PATCH] fix(ci): pin actions to SHAs and harden injection-prone workflows Pin every third-party and first-party action across all 6 workflows to a full commit SHA (with a version comment), so a compromised action tag can no longer alter what runs in jobs that hold NPM_TOKEN, AWS keys, or OIDC. Same pattern recommended by GitHub's hardening docs and OpenSSF. Other fixes in scope: - ecr-deploy.yml: route inputs.version and steps.build-push.outputs.digest through env: vars in the GITHUB_STEP_SUMMARY step so neither is evaluated as shell. Quote $GITHUB_STEP_SUMMARY. - mcp-registry.yml: route inputs.version through an env var, validate against a semver regex, and read VERSION via shell ($VERSION from $GITHUB_ENV) rather than re-interpolating ${{ env.VERSION }}. Replace the lossy 'echo \$(jq ...) > server.json' pattern with a temp file + mv. Pin the mcp-publisher binary to v1.4.0 and verify its SHA-256 before extraction. Hardcode linux/amd64 since runs-on is ubuntu-latest. - canary-release.yml / release.yml: move 'Configure npm authentication' from before 'pnpm install' to right before the publish step. NPM_TOKEN is no longer present in ~/.npmrc while dependency lifecycle scripts run, so a compromised dep or attacker-influenced branch cannot exfil the publish token via preinstall/postinstall. Net: closes the 10 MEDIUM GitHub-Actions findings from the deepsec scan (2x rce, 6x unpinned-actions, 1x untrusted-branch-checkout, 1x unverified-binary-download). --- .github/workflows/canary-release.yml | 16 ++++++++------ .github/workflows/changeset-check.yml | 4 ++-- .github/workflows/ecr-deploy.yml | 29 +++++++++++++----------- .github/workflows/mcp-registry.yml | 32 +++++++++++++++++++-------- .github/workflows/release.yml | 18 ++++++++------- .github/workflows/test.yml | 10 ++++----- 6 files changed, 65 insertions(+), 44 deletions(-) diff --git a/.github/workflows/canary-release.yml b/.github/workflows/canary-release.yml index 48b9d596..056740a0 100644 --- a/.github/workflows/canary-release.yml +++ b/.github/workflows/canary-release.yml @@ -16,30 +16,32 @@ jobs: contents: read steps: - name: Checkout Repo - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ inputs.branch || github.ref }} - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "20" - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 with: version: 10 - - name: Configure npm authentication - run: | - echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc - - name: Install Dependencies run: pnpm install --frozen-lockfile - name: Build all packages run: pnpm build + - name: Configure npm authentication + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc + - name: Publish Snapshot run: pnpm release:snapshot env: diff --git a/.github/workflows/changeset-check.yml b/.github/workflows/changeset-check.yml index 3ce60c1e..5426b20b 100644 --- a/.github/workflows/changeset-check.yml +++ b/.github/workflows/changeset-check.yml @@ -10,12 +10,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repo - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Check for changeset - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | const { execSync } = require('child_process'); diff --git a/.github/workflows/ecr-deploy.yml b/.github/workflows/ecr-deploy.yml index cec3f866..ef3c59d0 100644 --- a/.github/workflows/ecr-deploy.yml +++ b/.github/workflows/ecr-deploy.yml @@ -14,10 +14,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v5 + uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -25,17 +25,17 @@ jobs: - name: Login to Amazon ECR id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 + uses: aws-actions/amazon-ecr-login@fa648b43de3d4d023bcb3f89ed6940096949c419 # v2.1.5 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Build and push Docker image id: build-push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 with: context: . file: packages/mcp/Dockerfile @@ -47,11 +47,14 @@ jobs: cache-to: type=gha,mode=max - name: Create GitHub Summary + env: + VERSION: ${{ inputs.version }} + DIGEST: ${{ steps.build-push.outputs.digest }} run: | - echo "## Docker Image Deployed Successfully" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Image Tag:** \`${{ inputs.version }}\`" >> $GITHUB_STEP_SUMMARY - echo "**Platforms:** \`linux/amd64\`, \`linux/arm64\`" >> $GITHUB_STEP_SUMMARY - echo "**Digest:** \`${{ steps.build-push.outputs.digest }}\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Status:** ✅ Image pushed to AWS ECR" >> $GITHUB_STEP_SUMMARY + echo "## Docker Image Deployed Successfully" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "**Image Tag:** \`$VERSION\`" >> "$GITHUB_STEP_SUMMARY" + echo "**Platforms:** \`linux/amd64\`, \`linux/arm64\`" >> "$GITHUB_STEP_SUMMARY" + echo "**Digest:** \`$DIGEST\`" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "**Status:** ✅ Image pushed to AWS ECR" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/mcp-registry.yml b/.github/workflows/mcp-registry.yml index 2f60b4b3..8f8a2787 100644 --- a/.github/workflows/mcp-registry.yml +++ b/.github/workflows/mcp-registry.yml @@ -15,37 +15,51 @@ jobs: permissions: id-token: write # Required for OIDC authentication with MCP Registry contents: read + env: + MCP_PUBLISHER_VERSION: v1.4.0 + # sha256 of mcp-publisher_linux_amd64.tar.gz from the v1.4.0 release. + # Verify against https://github.com/modelcontextprotocol/registry/releases/tag/v1.4.0 + MCP_PUBLISHER_SHA256: c4b402b43a85166c3f840641ca1c9e6de5bfa1cf533c22576d663ccbda0711bb steps: - name: Checkout Repo - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: lts/* - name: Set version + env: + USER_VERSION: ${{ inputs.version }} run: | - if [ -n "${{ inputs.version }}" ]; then - VERSION="${{ inputs.version }}" - # Remove 'v' prefix if it exists - VERSION="${VERSION#v}" + if [ -n "$USER_VERSION" ]; then + if ! [[ "$USER_VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+([.-][A-Za-z0-9.-]+)?$ ]]; then + echo "::error::Invalid version format: $USER_VERSION" + exit 1 + fi + VERSION="${USER_VERSION#v}" else VERSION=$(node -p "require('./packages/mcp/package.json').version") fi - echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "VERSION=$VERSION" >> "$GITHUB_ENV" echo "Publishing version: $VERSION" - name: Update package version in server.json run: | - echo $(jq --arg v "${{ env.VERSION }}" '.version = $v | .packages[0].version = $v' server.json) > server.json + jq --arg v "$VERSION" '.version = $v | .packages[0].version = $v' server.json > server.json.tmp + mv server.json.tmp server.json - name: Validate server.json run: npx mcp-registry-validator validate server.json - name: Install MCP Publisher run: | - curl -L "https://github.com/modelcontextprotocol/registry/releases/download/v1.4.0/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher + curl -fSL -o mcp-publisher.tar.gz \ + "https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/mcp-publisher_linux_amd64.tar.gz" + echo "${MCP_PUBLISHER_SHA256} mcp-publisher.tar.gz" | sha256sum -c - + tar xzf mcp-publisher.tar.gz mcp-publisher + rm mcp-publisher.tar.gz - name: Login to MCP Registry run: ./mcp-publisher login github-oidc diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6f944542..cf8ddcb0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,31 +16,33 @@ jobs: pull-requests: write steps: - name: Checkout Repo - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "20" - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 with: version: 10 - - name: Configure npm authentication - run: | - echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc - - name: Install Dependencies run: pnpm install --frozen-lockfile - name: Build all packages run: pnpm build + - name: Configure npm authentication + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc + - name: Create Release PR or Publish id: changesets - uses: changesets/action@v1 + uses: changesets/action@63a615b9cd06ba9a3e6d13796c7fbcb080a60a0b # v1.8.0 with: publish: pnpm release commit: "chore(release): version packages" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7f67f824..befc3b22 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,15 +14,15 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "20" - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 with: version: 10 @@ -30,10 +30,10 @@ jobs: id: pnpm-cache shell: bash run: | - echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + echo "STORE_PATH=$(pnpm store path)" >> "$GITHUB_OUTPUT" - name: Cache pnpm dependencies - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}