From 746fce9328e328265235571e2f730d47182ee043 Mon Sep 17 00:00:00 2001 From: Steve Degosserie <723552+stiiifff@users.noreply.github.com> Date: Fri, 12 Dec 2025 10:52:50 +0100 Subject: [PATCH] =?UTF-8?q?security:=20=F0=9F=9B=A1=EF=B8=8F=20Harden=20Gi?= =?UTF-8?q?tHub=20Actions=20workflows=20(#349)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR addresses several security vulnerabilities and applies hardening measures to the GitHub Actions workflows: - **Replace `secrets: inherit` with explicit secret passing** - Prevents unnecessary exposure of all repository secrets to called workflows - **Add SHA256 checksum verification for downloaded binaries** - Protects against supply chain attacks via compromised upstream releases - **Add GitHub Environment protections for release workflows** - Requires approval before publishing to Docker Hub or creating releases - **Add explicit minimal permissions to all workflows** - Follows principle of least privilege, removes unnecessary `packages: write` from CI.yml ## Changes by Category ### 1. Explicit Secret Passing | Workflow | Before | After | |----------|--------|-------| | CI.yml → docker-build-ci | `secrets: inherit` | No secrets (GITHUB_TOKEN is automatic) | | CI.yml → docker-build-release | `secrets: inherit` | Explicit `DOCKERHUB_USERNAME`, `DOCKERHUB_TOKEN` | | CI.yml → e2e-tests | `secrets: inherit` | No secrets (GITHUB_TOKEN is automatic) | ### 2. Binary Checksum Verification | Workflow | Binary | SHA256 | |----------|--------|--------| | task-rust-lint.yml | taplo 0.8.1 | `c62baa73c9d7c1572...` | | task-e2e.yml | kurtosis 1.11.99 | `5e88e98c1b255362...` | ### 3. Environment Protections | Workflow | Job | Environment | |----------|-----|-------------| | task-docker-release.yml | build-test-push | `production` | | task-publish-binary.yml | publish-draft-release | `releases` | | task-publish-binary.yml | docker-release-candidate | `production` | | task-publish-runtime.yml | publish-draft-release | `releases` | ### 4. Explicit Permissions All 14 workflow files now have explicit `permissions:` blocks with minimal required access. Co-authored-by: Claude Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> --- .github/workflows/CI.yml | 11 +++++++---- .github/workflows/task-build-operator.yml | 5 +++++ .github/workflows/task-check-metadata.yml | 4 ++++ .github/workflows/task-docker-release.yml | 9 +++++++++ .github/workflows/task-e2e.yml | 9 +++++++++ .github/workflows/task-foundry-tests.yml | 4 ++++ .github/workflows/task-moonwall-tests.yml | 5 +++++ .github/workflows/task-publish-binary.yml | 6 ++++++ .github/workflows/task-publish-runtime.yml | 4 ++++ .github/workflows/task-rust-lint.yml | 12 ++++++++++++ .github/workflows/task-rust-tests.yml | 4 ++++ .github/workflows/task-ts-build.yml | 4 ++++ .github/workflows/task-ts-lint.yml | 4 ++++ .github/workflows/weekly-audit.yml | 4 ++++ 14 files changed, 81 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b3ba0025..c66e712b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -11,9 +11,10 @@ on: pull_request: branches: [main] +# Explicit minimal permissions +# Note: Reusable workflows define their own permissions permissions: contents: read - packages: write concurrency: group: pr-checks-${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -46,14 +47,16 @@ jobs: docker-build-ci: needs: [build-operator] uses: ./.github/workflows/task-docker-ci.yml - secrets: inherit + # Note: GITHUB_TOKEN is automatically available to reusable workflows with: binary-hash: ${{ needs.build-operator.outputs.binary-hash }} docker-build-release: if: github.ref == 'refs/heads/main' uses: ./.github/workflows/task-docker-release.yml - secrets: inherit + secrets: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} moonwall-tests: needs: [build-operator] @@ -65,6 +68,6 @@ jobs: e2e-tests: needs: [docker-build-ci] uses: ./.github/workflows/task-e2e.yml - secrets: inherit + # Note: GITHUB_TOKEN is automatically available to reusable workflows with: image-tag: ${{ needs.docker-build-ci.outputs.image-tag }} diff --git a/.github/workflows/task-build-operator.yml b/.github/workflows/task-build-operator.yml index e5c03a27..e5204b37 100644 --- a/.github/workflows/task-build-operator.yml +++ b/.github/workflows/task-build-operator.yml @@ -10,6 +10,11 @@ on: description: "The hash of the operator binary" value: ${{ jobs.build-node.outputs.binary-hash }} +# Explicit minimal permissions +permissions: + contents: read + actions: write # Required for uploading artifacts + jobs: build-node: outputs: diff --git a/.github/workflows/task-check-metadata.yml b/.github/workflows/task-check-metadata.yml index d2eae53a..045c9365 100644 --- a/.github/workflows/task-check-metadata.yml +++ b/.github/workflows/task-check-metadata.yml @@ -14,6 +14,10 @@ on: required: true type: string +# Explicit minimal permissions +permissions: + contents: read + defaults: run: shell: bash diff --git a/.github/workflows/task-docker-release.yml b/.github/workflows/task-docker-release.yml index 1fae2697..acae60df 100644 --- a/.github/workflows/task-docker-release.yml +++ b/.github/workflows/task-docker-release.yml @@ -17,6 +17,13 @@ on: type: boolean default: false workflow_call: + secrets: + DOCKERHUB_USERNAME: + description: "Docker Hub username" + required: true + DOCKERHUB_TOKEN: + description: "Docker Hub access token" + required: true outputs: image-tag: description: "The tag portion of the docker image (without registry)" @@ -33,6 +40,8 @@ concurrency: jobs: build-test-push: runs-on: ubuntu-latest + # Require approval before publishing to Docker Hub + environment: production outputs: image-tag: ${{ steps.extract_tag.outputs.image-tag }} diff --git a/.github/workflows/task-e2e.yml b/.github/workflows/task-e2e.yml index 92e5bcc6..8cf9080c 100644 --- a/.github/workflows/task-e2e.yml +++ b/.github/workflows/task-e2e.yml @@ -60,6 +60,9 @@ jobs: - name: Install Kurtosis run: | # Install Kurtosis locally without sudo + # SHA256 checksum for patched kurtosis binary (stiiifff fork v1.11.99) + KURTOSIS_SHA256="5e88e98c1b255362268b4c385cdb6bbba7e82b333c4b2c05bc0bff7de0560b2a" + if ! command -v kurtosis &> /dev/null; then echo "Installing Kurtosis $KURTOSIS_VERSION locally" mkdir -p ~/.local/bin @@ -68,6 +71,12 @@ jobs: # rm kurtosis-cli.tar.gz # For now, we use a patched version of Kurtosis CLI & Engine that supports Podman properly wget -q -O ~/.local/bin/kurtosis https://github.com/stiiifff/kurtosis/releases/download/1.11.99/kurtosis + + # Verify checksum before making executable + echo "Verifying kurtosis checksum..." + echo "${KURTOSIS_SHA256} $HOME/.local/bin/kurtosis" | sha256sum -c - + + chmod +x ~/.local/bin/kurtosis echo "$HOME/.local/bin" >> $GITHUB_PATH export PATH="$HOME/.local/bin:$PATH" else diff --git a/.github/workflows/task-foundry-tests.yml b/.github/workflows/task-foundry-tests.yml index f9b82e8c..c6bc6d81 100644 --- a/.github/workflows/task-foundry-tests.yml +++ b/.github/workflows/task-foundry-tests.yml @@ -9,6 +9,10 @@ on: workflow_dispatch: workflow_call: +# Explicit minimal permissions +permissions: + contents: read + env: FOUNDRY_PROFILE: ci diff --git a/.github/workflows/task-moonwall-tests.yml b/.github/workflows/task-moonwall-tests.yml index 3c08d527..b75e552c 100644 --- a/.github/workflows/task-moonwall-tests.yml +++ b/.github/workflows/task-moonwall-tests.yml @@ -9,6 +9,11 @@ on: required: true type: string +# Explicit minimal permissions +permissions: + contents: read + actions: write # Required for uploading artifacts + jobs: moonwall: runs-on: ubuntu-latest diff --git a/.github/workflows/task-publish-binary.yml b/.github/workflows/task-publish-binary.yml index b0ad824d..cfbc757a 100644 --- a/.github/workflows/task-publish-binary.yml +++ b/.github/workflows/task-publish-binary.yml @@ -16,6 +16,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + actions: write # Required for uploading artifacts steps: - name: Checkout uses: actions/checkout@v5 @@ -35,6 +36,7 @@ jobs: needs: ["prepare-sources"] permissions: contents: read + actions: write # Required for uploading artifacts strategy: matrix: cpu: ["x86-64", "skylake", "znver3"] @@ -52,6 +54,8 @@ jobs: publish-draft-release: runs-on: ubuntu-latest + # Require approval before creating release drafts + environment: releases permissions: contents: write needs: ["build-binary"] @@ -108,6 +112,8 @@ jobs: docker-release-candidate: runs-on: ubuntu-latest + # Require approval before publishing RC images to Docker Hub + environment: production needs: ["build-binary"] steps: - name: Checkout diff --git a/.github/workflows/task-publish-runtime.yml b/.github/workflows/task-publish-runtime.yml index 25743482..ef02895e 100644 --- a/.github/workflows/task-publish-runtime.yml +++ b/.github/workflows/task-publish-runtime.yml @@ -18,6 +18,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + actions: write # Required for uploading artifacts steps: - uses: actions/checkout@v5 - name: Upload scripts @@ -49,6 +50,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + actions: write # Required for uploading artifacts strategy: matrix: chain: ["stagenet", "testnet", "mainnet"] @@ -104,6 +106,8 @@ jobs: ####### Prepare the release draft ####### publish-draft-release: runs-on: ubuntu-latest + # Require approval before creating release drafts + environment: releases permissions: contents: write needs: ["setup-scripts", "build-srtool-runtimes"] diff --git a/.github/workflows/task-rust-lint.yml b/.github/workflows/task-rust-lint.yml index 7435f1d3..8cd39e05 100644 --- a/.github/workflows/task-rust-lint.yml +++ b/.github/workflows/task-rust-lint.yml @@ -14,6 +14,10 @@ on: description: set to pull_request number to execute on external pr required: false +# Explicit minimal permissions +permissions: + contents: read + env: CARGO_TERM_COLOR: always WORKING_DIR: operator @@ -77,11 +81,19 @@ jobs: run: | # Install taplo in user space without sudo TAPLO_VERSION="0.8.1" + # SHA256 checksum for taplo-full-linux-x86_64.gz (decompressed binary) + TAPLO_SHA256="c62baa73c9d7c1572047b1a502ca805e1372e5db7c9a935532f702f12d6115ce" + if ! command -v taplo &> /dev/null || [[ $(taplo --version | grep -oP '\d+\.\d+\.\d+' | head -1) != "$TAPLO_VERSION" ]]; then echo "Installing taplo $TAPLO_VERSION in ~/.local/bin" mkdir -p ~/.local/bin curl -Ls "https://github.com/tamasfe/taplo/releases/download/${TAPLO_VERSION}/taplo-full-linux-x86_64.gz" | \ gzip -d > ~/.local/bin/taplo + + # Verify checksum before making executable + echo "Verifying taplo checksum..." + echo "${TAPLO_SHA256} $HOME/.local/bin/taplo" | sha256sum -c - + chmod +x ~/.local/bin/taplo echo "$HOME/.local/bin" >> $GITHUB_PATH export PATH="$HOME/.local/bin:$PATH" diff --git a/.github/workflows/task-rust-tests.yml b/.github/workflows/task-rust-tests.yml index 68602485..0088bbc4 100644 --- a/.github/workflows/task-rust-tests.yml +++ b/.github/workflows/task-rust-tests.yml @@ -10,6 +10,10 @@ on: workflow_dispatch: workflow_call: +# Explicit minimal permissions +permissions: + contents: read + jobs: all-rust-tests: name: Run all Operator Rust tests (/w partitioning) diff --git a/.github/workflows/task-ts-build.yml b/.github/workflows/task-ts-build.yml index 33f8fcef..246a9192 100644 --- a/.github/workflows/task-ts-build.yml +++ b/.github/workflows/task-ts-build.yml @@ -4,6 +4,10 @@ on: workflow_dispatch: workflow_call: +# Explicit minimal permissions +permissions: + contents: read + jobs: generate-wagmi: runs-on: ubuntu-latest diff --git a/.github/workflows/task-ts-lint.yml b/.github/workflows/task-ts-lint.yml index f794f95b..b5d859c8 100644 --- a/.github/workflows/task-ts-lint.yml +++ b/.github/workflows/task-ts-lint.yml @@ -4,6 +4,10 @@ on: workflow_dispatch: workflow_call: +# Explicit minimal permissions +permissions: + contents: read + jobs: typecheck: runs-on: ubuntu-latest diff --git a/.github/workflows/weekly-audit.yml b/.github/workflows/weekly-audit.yml index 302ffaed..8928daa3 100644 --- a/.github/workflows/weekly-audit.yml +++ b/.github/workflows/weekly-audit.yml @@ -14,6 +14,10 @@ on: - 'Cargo.toml' - 'Cargo.lock' +# Explicit minimal permissions +permissions: + contents: read + jobs: audit: runs-on: ubuntu-latest