From 07695b22210692f56cf3a432d3731a5075ac4206 Mon Sep 17 00:00:00 2001 From: pasta Date: Fri, 28 Nov 2025 09:32:19 -0600 Subject: [PATCH 1/7] ci: harden workflows against script injection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move all ${{ }} expressions from run: script bodies to env: blocks. This prevents potential script injection attacks by ensuring that expression values are passed as environment variables rather than being interpolated directly into shell scripts. While most of these expressions come from trusted sources (workflow_call inputs with hardcoded values, step outputs, safe github context fields), this change provides structural safety as a defense-in-depth measure. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/build-container.yml | 4 +++- .github/workflows/build-depends.yml | 10 ++++++---- .github/workflows/build-src.yml | 18 ++++++++++++------ .github/workflows/build.yml | 8 ++++++-- .github/workflows/guix-build.yml | 22 ++++++++++++++++------ .github/workflows/lint.yml | 11 +++++++---- .github/workflows/merge-check.yml | 16 +++++++++++----- .github/workflows/release_docker_hub.yml | 12 +++++++++--- .github/workflows/test-src.yml | 8 +++++--- 9 files changed, 75 insertions(+), 34 deletions(-) diff --git a/.github/workflows/build-container.yml b/.github/workflows/build-container.yml index 67df008c220d..7cc87d659dde 100644 --- a/.github/workflows/build-container.yml +++ b/.github/workflows/build-container.yml @@ -35,9 +35,11 @@ jobs: - name: Prepare variables id: prepare + env: + GITHUB_REPOSITORY_LC: ${{ github.repository }} run: | BRANCH_NAME=$(echo "${GITHUB_REF##*/}" | tr '[:upper:]' '[:lower:]') - REPO_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') + REPO_NAME=$(echo "${GITHUB_REPOSITORY_LC}" | tr '[:upper:]' '[:lower:]') echo "tag=${BRANCH_NAME}" >> $GITHUB_OUTPUT echo "repo=${REPO_NAME}" >> $GITHUB_OUTPUT diff --git a/.github/workflows/build-depends.yml b/.github/workflows/build-depends.yml index b2fefcba010c..64b05a88fd9a 100644 --- a/.github/workflows/build-depends.yml +++ b/.github/workflows/build-depends.yml @@ -33,15 +33,15 @@ jobs: - name: Initial setup id: setup + env: + BUILD_TARGET: ${{ inputs.build-target }} run: | - BUILD_TARGET="${{ inputs.build-target }}" source ./ci/dash/matrix.sh echo "DEP_OPTS=${DEP_OPTS}" >> "${GITHUB_OUTPUT}" echo "HOST=${HOST}" >> "${GITHUB_OUTPUT}" DEP_HASH="$(echo -n "${BUILD_TARGET}" "${DEP_OPTS}" "${HOST}" | sha256sum | head -c 64)" echo "\"${BUILD_TARGET}\" has HOST=\"${HOST}\" and DEP_OPTS=\"${DEP_OPTS}\" with hash \"${DEP_HASH}\"" echo "DEP_HASH=${DEP_HASH}" >> "${GITHUB_OUTPUT}" - shell: bash - name: Cache depends sources @@ -76,12 +76,14 @@ jobs: depends-${{ hashFiles('contrib/containers/ci/ci.Dockerfile') }}-${{ inputs.build-target }}- - name: Build depends + env: + HOST: ${{ steps.setup.outputs.HOST }} + DEP_OPTS: ${{ steps.setup.outputs.DEP_OPTS }} run: | - export HOST="${{ steps.setup.outputs.HOST }}" if [ "${HOST}" = "x86_64-apple-darwin" ]; then ./contrib/containers/guix/scripts/setup-sdk fi - env ${{ steps.setup.outputs.DEP_OPTS }} make -j$(nproc) -C depends + env ${DEP_OPTS} make -j$(nproc) -C depends - name: Save depends cache uses: actions/cache/save@v4 diff --git a/.github/workflows/build-src.yml b/.github/workflows/build-src.yml index 6f9d3279b845..6e84930b4773 100644 --- a/.github/workflows/build-src.yml +++ b/.github/workflows/build-src.yml @@ -38,13 +38,15 @@ jobs: - name: Initial setup id: setup + env: + BUILD_TARGET: ${{ inputs.build-target }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha || '' }} run: | git config --global --add safe.directory "$PWD" git fetch -fu origin develop:develop - BUILD_TARGET="${{ inputs.build-target }}" source ./ci/dash/matrix.sh echo "HOST=${HOST}" >> $GITHUB_OUTPUT - echo "PR_BASE_SHA=${{ github.event.pull_request.base.sha || '' }}" >> $GITHUB_OUTPUT + echo "PR_BASE_SHA=${PR_BASE_SHA}" >> $GITHUB_OUTPUT shell: bash - name: Restore SDKs cache @@ -75,12 +77,13 @@ jobs: ccache-${{ hashFiles('contrib/containers/ci/ci.Dockerfile', 'depends/packages/*') }}-${{ inputs.build-target }}- - name: Build source + env: + BUILD_TARGET: ${{ inputs.build-target }} run: | CCACHE_MAXSIZE="400M" CACHE_DIR="/cache" mkdir /output BASE_OUTDIR="/output" - BUILD_TARGET="${{ inputs.build-target }}" source ./ci/dash/matrix.sh ./ci/dash/build_src.sh ccache -X 9 @@ -89,24 +92,27 @@ jobs: - name: Run linters if: inputs.build-target == 'linux64_multiprocess' + env: + BUILD_TARGET: ${{ inputs.build-target }} run: | - export BUILD_TARGET="${{ inputs.build-target }}" source ./ci/dash/matrix.sh ./ci/dash/lint-tidy.sh shell: bash - name: Run unit tests + env: + BUILD_TARGET: ${{ inputs.build-target }} run: | BASE_OUTDIR="/output" - BUILD_TARGET="${{ inputs.build-target }}" source ./ci/dash/matrix.sh ./ci/dash/test_unittests.sh shell: bash - name: Bundle artifacts id: bundle + env: + BUILD_TARGET: ${{ inputs.build-target }} run: | - export BUILD_TARGET="${{ inputs.build-target }}" export BUNDLE_KEY="build-${BUILD_TARGET}-$(git rev-parse --short=8 HEAD)" ./ci/dash/bundle-artifacts.sh create echo "key=${BUNDLE_KEY}" >> "${GITHUB_OUTPUT}" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4e9b6c9efa8b..cbd22fad01b2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,11 +26,15 @@ jobs: steps: - name: Check skip environment variables id: skip-check + env: + EVENT_NAME: ${{ github.event_name }} + SKIP_ON_PUSH: ${{ vars.SKIP_ON_PUSH }} + SKIP_ON_PR: ${{ vars.SKIP_ON_PR }} run: | - if [[ "${{ github.event_name }}" == "push" && "${{ vars.SKIP_ON_PUSH }}" != "" ]]; then + if [[ "${EVENT_NAME}" == "push" && "${SKIP_ON_PUSH}" != "" ]]; then echo "Skipping build on push due to SKIP_ON_PUSH environment variable" echo "skip=true" >> $GITHUB_OUTPUT - elif [[ "${{ github.event_name }}" == "pull_request_target" && "${{ vars.SKIP_ON_PR }}" != "" ]]; then + elif [[ "${EVENT_NAME}" == "pull_request_target" && "${SKIP_ON_PR}" != "" ]]; then echo "Skipping build on pull request due to SKIP_ON_PR environment variable" echo "skip=true" >> $GITHUB_OUTPUT else diff --git a/.github/workflows/guix-build.yml b/.github/workflows/guix-build.yml index 48ab032f40bf..99707a13f7f0 100644 --- a/.github/workflows/guix-build.yml +++ b/.github/workflows/guix-build.yml @@ -33,12 +33,14 @@ jobs: - name: Commit variables id: prepare + env: + GITHUB_REPOSITORY_LC: ${{ github.repository }} run: | echo "hash=$(sha256sum ./dash/contrib/containers/guix/Dockerfile | cut -d ' ' -f1)" >> $GITHUB_OUTPUT echo "host_user_id=$(id -u)" >> $GITHUB_OUTPUT echo "host_group_id=$(id -g)" >> $GITHUB_OUTPUT BRANCH_NAME=$(echo "${GITHUB_REF##*/}" | tr '[:upper:]' '[:lower:]') - REPO_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') + REPO_NAME=$(echo "${GITHUB_REPOSITORY_LC}" | tr '[:upper:]' '[:lower:]') echo "image-tag=${BRANCH_NAME}" >> $GITHUB_OUTPUT echo "repo-name=${REPO_NAME}" >> $GITHUB_OUTPUT @@ -115,14 +117,19 @@ jobs: - name: Run Guix build timeout-minutes: 480 + env: + WORKSPACE: ${{ github.workspace }} + REPO_NAME: ${{ needs.build-image.outputs.repo-name }} + IMAGE_TAG: ${{ needs.build-image.outputs.image-tag }} + BUILD_TARGET: ${{ matrix.build_target }} run: | docker run --privileged -d --rm -t \ --name guix-daemon \ - -v ${{ github.workspace }}/dash:/src/dash \ - -v ${{ github.workspace }}/.cache:/home/ubuntu/.cache \ + -v "${WORKSPACE}/dash:/src/dash" \ + -v "${WORKSPACE}/.cache:/home/ubuntu/.cache" \ -w /src/dash \ - ghcr.io/${{ needs.build-image.outputs.repo-name }}/dashcore-guix-builder:${{ needs.build-image.outputs.image-tag }} && \ - docker exec guix-daemon bash -c 'HOSTS=${{ matrix.build_target }} /usr/local/bin/guix-start /src/dash' + "ghcr.io/${REPO_NAME}/dashcore-guix-builder:${IMAGE_TAG}" && \ + docker exec guix-daemon bash -c "HOSTS=${BUILD_TARGET} /usr/local/bin/guix-start /src/dash" - name: Ensure build passes run: | @@ -133,8 +140,11 @@ jobs: - name: Compute SHA256 checksums continue-on-error: true # It will complain on depending on only some hosts + env: + BUILD_TARGET: ${{ matrix.build_target }} + WORKSPACE: ${{ github.workspace }} run: | - HOSTS=${{ matrix.build_target }} ./dash/contrib/containers/guix/scripts/guix-check ${{ github.workspace }}/dash + HOSTS="${BUILD_TARGET}" ./dash/contrib/containers/guix/scripts/guix-check "${WORKSPACE}/dash" - name: Upload build artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 78b825aa8254..91d45feb606a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,15 +29,18 @@ jobs: shell: bash - name: Run linters + env: + EVENT_NAME: ${{ github.event_name }} + REF: ${{ github.ref }} run: | export BUILD_TARGET="linux64" export CHECK_DOC=1 # Determine if this is a PR and set commit range accordingly - if [ "${{ github.event_name }}" = "pull_request_target" ]; then + if [ "${EVENT_NAME}" = "pull_request_target" ]; then export COMMIT_RANGE="$(git merge-base origin/develop HEAD)..HEAD" export PULL_REQUEST="true" - elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" != "refs/heads/develop" ] && [ "${{ github.ref }}" != "refs/heads/master" ]; then + elif [ "${EVENT_NAME}" = "push" ] && [ "${REF}" != "refs/heads/develop" ] && [ "${REF}" != "refs/heads/master" ]; then # For push events on feature branches, check against develop export COMMIT_RANGE="$(git merge-base origin/develop HEAD)..HEAD" export PULL_REQUEST="true" @@ -47,8 +50,8 @@ jobs: export PULL_REQUEST="false" fi - echo "Event name: ${{ github.event_name }}" - echo "Ref: ${{ github.ref }}" + echo "Event name: ${EVENT_NAME}" + echo "Ref: ${REF}" echo "COMMIT_RANGE=${COMMIT_RANGE}" echo "PULL_REQUEST=${PULL_REQUEST}" echo "Running git log for commit range:" diff --git a/.github/workflows/merge-check.yml b/.github/workflows/merge-check.yml index 7fc2e22a8705..495f5ca103c8 100644 --- a/.github/workflows/merge-check.yml +++ b/.github/workflows/merge-check.yml @@ -24,20 +24,26 @@ jobs: git config user.email "noreply@example.com" - name: Check merge --ff-only + env: + REF_NAME: ${{ github.ref_name }} + EVENT_NAME: ${{ github.event_name }} + PR_BASE_REF: ${{ github.event.pull_request.base.ref }} + PR_NUMBER: ${{ github.event.pull_request.number }} + COMMIT_SHA: ${{ github.sha }} run: | - if [[ "${{ github.ref_name }}" == "master" ]]; then + if [[ "${REF_NAME}" == "master" ]]; then echo "Already on master, no need to check --ff-only" else git fetch --no-tags origin master:master - if [[ "${{ github.event_name }}" == "pull_request"* ]]; then - git fetch --no-tags origin ${{ github.event.pull_request.base.ref }}:base_branch + if [[ "${EVENT_NAME}" == "pull_request"* ]]; then + git fetch --no-tags origin "${PR_BASE_REF}":base_branch git checkout base_branch - git pull --rebase=false origin pull/${{ github.event.pull_request.number }}/head + git pull --rebase=false origin "pull/${PR_NUMBER}/head" git checkout master git merge --ff-only base_branch else git checkout master - git merge --ff-only ${{ github.sha }} + git merge --ff-only "${COMMIT_SHA}" fi fi diff --git a/.github/workflows/release_docker_hub.yml b/.github/workflows/release_docker_hub.yml index f4e1775a7859..0bb2b6d29cf6 100644 --- a/.github/workflows/release_docker_hub.yml +++ b/.github/workflows/release_docker_hub.yml @@ -28,17 +28,21 @@ jobs: - name: Set raw tag id: get_tag + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} run: | - TAG=${{ github.event.release.tag_name }} + TAG="${RELEASE_TAG}" echo "build_tag=${TAG#v}" >> $GITHUB_OUTPUT - name: Set suffix uses: actions/github-script@v6 id: suffix + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} with: result-encoding: string script: | - const fullTag = '${{ github.event.release.tag_name }}'; + const fullTag = process.env.RELEASE_TAG; if (fullTag.includes('-')) { const [, fullSuffix] = fullTag.split('-'); const [suffix] = fullSuffix.split('.'); @@ -79,4 +83,6 @@ jobs: platforms: linux/amd64,linux/arm64 - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} + env: + DIGEST: ${{ steps.docker_build.outputs.digest }} + run: echo "${DIGEST}" diff --git a/.github/workflows/test-src.yml b/.github/workflows/test-src.yml index c0a9e5e58289..5f5b4f23fd32 100644 --- a/.github/workflows/test-src.yml +++ b/.github/workflows/test-src.yml @@ -49,10 +49,11 @@ jobs: - name: Run functional tests id: test + env: + BUILD_TARGET: ${{ inputs.build-target }} + BUNDLE_KEY: ${{ inputs.bundle-key }} run: | git config --global --add safe.directory "$PWD" - export BUILD_TARGET="${{ inputs.build-target }}" - export BUNDLE_KEY="${{ inputs.bundle-key }}" ./ci/dash/bundle-artifacts.sh extract ./ci/dash/slim-workspace.sh source ./ci/dash/matrix.sh @@ -62,8 +63,9 @@ jobs: - name: Bundle test logs id: bundle if: success() || (failure() && steps.test.outcome == 'failure') + env: + BUILD_TARGET: ${{ inputs.build-target }} run: | - export BUILD_TARGET="${{ inputs.build-target }}" echo "short-sha=$(git rev-parse --short=8 HEAD)" >> "${GITHUB_OUTPUT}" ( [ -d "testlogs" ] && echo "upload-logs=true" >> "${GITHUB_OUTPUT}" && ./ci/dash/bundle-logs.sh ) \ || echo "upload-logs=false" >> "${GITHUB_OUTPUT}" From 5e6ec8a95075d05557d763c8b56a7dec5ea7d326 Mon Sep 17 00:00:00 2001 From: pasta Date: Fri, 28 Nov 2025 14:23:12 -0600 Subject: [PATCH 2/7] ci: add persist-credentials and explicit permissions to workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add persist-credentials: false to all checkout actions to prevent credential persistence in .git/config (artipacked vulnerability) - Add explicit minimal permissions blocks to workflows that were using defaults (clang-diff-format, prevent-master-pr, release_docker_hub, semantic-pull-request) - Scope permissions to job level in guix-build.yml where possible (build-image needs packages:write, build needs id-token/attestations) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/build-container.yml | 1 + .github/workflows/build-depends.yml | 1 + .github/workflows/build-src.yml | 1 + .github/workflows/clang-diff-format.yml | 6 ++++++ .github/workflows/guix-build.yml | 17 ++++++++++++----- .github/workflows/lint.yml | 1 + .github/workflows/merge-check.yml | 1 + .github/workflows/predict-conflicts.yml | 2 ++ .github/workflows/prevent-master-pr.yml | 2 ++ .github/workflows/release_docker_hub.yml | 5 +++++ .github/workflows/semantic-pull-request.yml | 3 +++ .github/workflows/test-src.yml | 1 + 12 files changed, 36 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-container.yml b/.github/workflows/build-container.yml index 7cc87d659dde..a9f5efbde475 100644 --- a/.github/workflows/build-container.yml +++ b/.github/workflows/build-container.yml @@ -32,6 +32,7 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Prepare variables id: prepare diff --git a/.github/workflows/build-depends.yml b/.github/workflows/build-depends.yml index 64b05a88fd9a..222ee4345dcd 100644 --- a/.github/workflows/build-depends.yml +++ b/.github/workflows/build-depends.yml @@ -30,6 +30,7 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Initial setup id: setup diff --git a/.github/workflows/build-src.yml b/.github/workflows/build-src.yml index 6e84930b4773..20550778d00d 100644 --- a/.github/workflows/build-src.yml +++ b/.github/workflows/build-src.yml @@ -35,6 +35,7 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 50 + persist-credentials: false - name: Initial setup id: setup diff --git a/.github/workflows/clang-diff-format.yml b/.github/workflows/clang-diff-format.yml index 5f43bb4f792c..89276ca1ca06 100644 --- a/.github/workflows/clang-diff-format.yml +++ b/.github/workflows/clang-diff-format.yml @@ -4,12 +4,18 @@ on: pull_request: branches: - develop + +permissions: + contents: read + jobs: ClangFormat: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 + with: + persist-credentials: false - name: Fetch git run: git fetch --no-tags -fu origin develop:develop - name: Run Clang-Format-Diff.py diff --git a/.github/workflows/guix-build.yml b/.github/workflows/guix-build.yml index 99707a13f7f0..6f8c97a5f5af 100644 --- a/.github/workflows/guix-build.yml +++ b/.github/workflows/guix-build.yml @@ -1,19 +1,20 @@ name: Guix Build -permissions: - packages: write - id-token: write - attestations: write - on: pull_request_target: pull_request: types: [labeled] push: +permissions: + contents: read + jobs: build-image: runs-on: ubuntu-24.04-arm + permissions: + contents: read + packages: write if: | (github.event_name == 'push' && (startsWith(github.ref, 'refs/tags/') || vars.RUN_GUIX_ON_ALL_PUSH == 'true')) || contains(github.event.pull_request.labels.*.name, 'guix-build') @@ -27,6 +28,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha || github.sha }} path: dash fetch-depth: 0 + persist-credentials: false - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -72,6 +74,10 @@ jobs: needs: build-image # runs-on: [ "self-hosted", "linux", "x64", "ubuntu-core" ] runs-on: ubuntu-24.04-arm + permissions: + contents: read + id-token: write + attestations: write strategy: matrix: build_target: [x86_64-linux-gnu, arm-linux-gnueabihf, aarch64-linux-gnu, riscv64-linux-gnu, powerpc64-linux-gnu, x86_64-w64-mingw32, x86_64-apple-darwin, arm64-apple-darwin] @@ -88,6 +94,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha || github.sha }} path: dash fetch-depth: 0 + persist-credentials: false - name: Cache depends sources uses: actions/cache@v4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 91d45feb606a..7fbb42a65e09 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -21,6 +21,7 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 50 + persist-credentials: false - name: Initial setup run: | diff --git a/.github/workflows/merge-check.yml b/.github/workflows/merge-check.yml index 495f5ca103c8..e797d65f9c4c 100644 --- a/.github/workflows/merge-check.yml +++ b/.github/workflows/merge-check.yml @@ -17,6 +17,7 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + persist-credentials: false - name: Set up Git run: | diff --git a/.github/workflows/predict-conflicts.yml b/.github/workflows/predict-conflicts.yml index 356ec7fcce68..b0e53201f7cc 100644 --- a/.github/workflows/predict-conflicts.yml +++ b/.github/workflows/predict-conflicts.yml @@ -29,6 +29,8 @@ jobs: ghToken: "${{ secrets.GITHUB_TOKEN }}" - name: Checkout uses: actions/checkout@v4 + with: + persist-credentials: false - name: validate potential conflicts id: validate_conflicts run: pip3 install hjson && .github/workflows/handle_potential_conflicts.py "$conflicts" diff --git a/.github/workflows/prevent-master-pr.yml b/.github/workflows/prevent-master-pr.yml index 58f506860498..ae76ec87c1c8 100644 --- a/.github/workflows/prevent-master-pr.yml +++ b/.github/workflows/prevent-master-pr.yml @@ -5,6 +5,8 @@ on: branches: - master +permissions: {} + jobs: fail: runs-on: ubuntu-latest diff --git a/.github/workflows/release_docker_hub.yml b/.github/workflows/release_docker_hub.yml index 0bb2b6d29cf6..dde34f36a422 100644 --- a/.github/workflows/release_docker_hub.yml +++ b/.github/workflows/release_docker_hub.yml @@ -4,6 +4,9 @@ on: release: types: [published] +permissions: + contents: read + jobs: release: name: Release to Docker Hub @@ -11,6 +14,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + with: + persist-credentials: false - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml index 4e5c9e59de96..898e2ada9b51 100644 --- a/.github/workflows/semantic-pull-request.yml +++ b/.github/workflows/semantic-pull-request.yml @@ -7,6 +7,9 @@ on: - edited - synchronize +permissions: + pull-requests: read + jobs: main: name: Validate PR title diff --git a/.github/workflows/test-src.yml b/.github/workflows/test-src.yml index 5f5b4f23fd32..7b5e161749f1 100644 --- a/.github/workflows/test-src.yml +++ b/.github/workflows/test-src.yml @@ -33,6 +33,7 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 1 + persist-credentials: false - name: Download build artifacts uses: actions/download-artifact@v4 From f24b1757b6b0c59417993f969203c2ef42f50146 Mon Sep 17 00:00:00 2001 From: pasta Date: Fri, 28 Nov 2025 14:24:09 -0600 Subject: [PATCH 3/7] ci: add zizmor linter for GitHub Actions security MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add lint-github-actions.py to check workflows for security issues using zizmor. The linter: - Checks for template injection, dangerous triggers, excessive permissions, credential persistence, and other security issues - Skips gracefully if zizmor is not installed - Disables unpinned-uses and unpinned-images audits (not a priority) - Ignores specific findings that are intentional or false positives: - dangerous-triggers: pull_request_target used with proper safeguards - template-injection: workflow_call inputs from hardcoded callers - excessive-permissions: required for reusable workflow architecture Install zizmor with: pip install zizmor 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- test/lint/lint-github-actions.py | 114 +++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100755 test/lint/lint-github-actions.py diff --git a/test/lint/lint-github-actions.py b/test/lint/lint-github-actions.py new file mode 100755 index 000000000000..b3f690bf928d --- /dev/null +++ b/test/lint/lint-github-actions.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2024 The Dash Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +""" +Check for security issues in GitHub Actions workflow files using zizmor. +""" + +import subprocess +import sys +import tempfile +import os + +# Disabled audits: +# These are intentionally disabled and don't indicate security issues in our context. +DISABLED = [ + 'unpinned-uses', # We use version tags rather than SHA pinning + 'unpinned-images', # Container images use dynamic tags +] + +# Ignored findings for specific files/locations: +# Format: 'audit-name': ['filename.yml:line', ...] +# Note: Use base filename only, not full path +IGNORED = { + # pull_request_target is used intentionally in these workflows with proper + # safeguards (explicit checkout of PR head SHA, limited permissions) + 'dangerous-triggers': [ + 'build.yml:3', + 'guix-build.yml:3', + 'label-merge-conflicts.yml:2', + 'merge-check.yml:6', + 'predict-conflicts.yml:3', + 'semantic-pull-request.yml:3', + ], + # inputs.context is passed to docker/build-push-action but only from internal + # workflow_call callers with hardcoded paths - not user-controllable + 'template-injection': [ + 'build-container.yml:60', + ], + # packages:write at workflow level is required because reusable workflows + # (build-container.yml) inherit caller permissions and need it to push to ghcr.io + 'excessive-permissions': [ + 'build.yml:9', + ], +} + + +def check_zizmor_install(): + try: + subprocess.run( + ['zizmor', '--version'], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True + ) + except FileNotFoundError: + print('Skipping GitHub Actions linting since zizmor is not installed.') + print('Install with: pip install zizmor') + sys.exit(0) + + +def generate_config(): + """Generate zizmor configuration with disabled and ignored rules.""" + lines = ['rules:'] + + # Add disabled audits + for audit in DISABLED: + lines.append(f' {audit}:') + lines.append(f' disable: true') + + # Add ignored findings + for audit, locations in IGNORED.items(): + lines.append(f' {audit}:') + lines.append(f' ignore:') + for loc in locations: + lines.append(f' - {loc}') + + return '\n'.join(lines) + '\n' + + +def main(): + check_zizmor_install() + + # Create a temporary config file + config_content = generate_config() + + with tempfile.NamedTemporaryFile(mode='w', suffix='.yml', delete=False) as f: + f.write(config_content) + config_path = f.name + + try: + # Build the zizmor command + zizmor_cmd = [ + 'zizmor', + '--config', config_path, + '.github/workflows/', + ] + + # Run zizmor + result = subprocess.run(zizmor_cmd) + + # zizmor returns non-zero if it finds issues + if result.returncode != 0: + print('GitHub Actions security issues found. Please fix the above issues.') + sys.exit(1) + finally: + # Clean up temp file + os.unlink(config_path) + + +if __name__ == '__main__': + main() From f8c0b6fcf9b292de06cdcef34d8e43588f35138a Mon Sep 17 00:00:00 2001 From: pasta Date: Fri, 28 Nov 2025 14:36:56 -0600 Subject: [PATCH 4/7] ci: add zizmor to CI container image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Install zizmor==1.17.0 in the CI container so lint-github-actions.py can run as part of the lint suite. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- contrib/containers/ci/ci-slim.Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/containers/ci/ci-slim.Dockerfile b/contrib/containers/ci/ci-slim.Dockerfile index 4abab253573f..476c893b29a6 100644 --- a/contrib/containers/ci/ci-slim.Dockerfile +++ b/contrib/containers/ci/ci-slim.Dockerfile @@ -80,7 +80,8 @@ RUN uv pip install --system --break-system-packages \ multiprocess \ mypy==0.981 \ pyzmq==24.0.1 \ - vulture==2.6 + vulture==2.6 \ + zizmor==1.17.0 # Install packages relied on by tests ARG DASH_HASH_VERSION=1.4.0 From 504580b4c4c58d00dc13767dd4f7565b51324bde Mon Sep 17 00:00:00 2001 From: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:12:28 -0600 Subject: [PATCH 5/7] chore: bump copyright to 2025 Co-authored-by: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> --- test/lint/lint-github-actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lint/lint-github-actions.py b/test/lint/lint-github-actions.py index b3f690bf928d..c6b155340513 100755 --- a/test/lint/lint-github-actions.py +++ b/test/lint/lint-github-actions.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (c) 2024 The Dash Core developers +# Copyright (c) 2025 The Dash Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. From d8a126557b063927c67cbfb6ad3f2cb9b14ba50c Mon Sep 17 00:00:00 2001 From: pasta Date: Mon, 1 Dec 2025 12:05:20 -0600 Subject: [PATCH 6/7] fix: coderabbit suggestions --- test/lint/lint-github-actions.py | 4 ++-- test/util/data/non-backported.txt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/lint/lint-github-actions.py b/test/lint/lint-github-actions.py index c6b155340513..09bda871ac03 100755 --- a/test/lint/lint-github-actions.py +++ b/test/lint/lint-github-actions.py @@ -68,12 +68,12 @@ def generate_config(): # Add disabled audits for audit in DISABLED: lines.append(f' {audit}:') - lines.append(f' disable: true') + lines.append(' disable: true') # Add ignored findings for audit, locations in IGNORED.items(): lines.append(f' {audit}:') - lines.append(f' ignore:') + lines.append(' ignore:') for loc in locations: lines.append(f' - {loc}') diff --git a/test/util/data/non-backported.txt b/test/util/data/non-backported.txt index eaa118ca8fca..04b4c48be3ae 100644 --- a/test/util/data/non-backported.txt +++ b/test/util/data/non-backported.txt @@ -61,3 +61,4 @@ src/wallet/bip39* src/wallet/coinjoin.* src/wallet/hdchain.* src/hash_x11.h +test/lint/lint-github-actions.py From fcae891daadaf92b86a49d0e732c02a6182b55a1 Mon Sep 17 00:00:00 2001 From: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:11:55 -0600 Subject: [PATCH 7/7] Update test/util/data/non-backported.txt --- test/util/data/non-backported.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/test/util/data/non-backported.txt b/test/util/data/non-backported.txt index 04b4c48be3ae..eaa118ca8fca 100644 --- a/test/util/data/non-backported.txt +++ b/test/util/data/non-backported.txt @@ -61,4 +61,3 @@ src/wallet/bip39* src/wallet/coinjoin.* src/wallet/hdchain.* src/hash_x11.h -test/lint/lint-github-actions.py