From 5f2f17208b641cb7f81336c42c8e1b7b5beddc64 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Mar 2026 09:27:30 +0000 Subject: [PATCH] security: apply zizmor GitHub Actions security improvements Ran zizmor v1.23.1 against all workflow files and resolved all high-priority findings (reduced from 30 high to 0 high): - Pin all action references to commit SHAs to prevent supply-chain attacks: - actions/checkout@de0fac2e (v6.0.2) - actions/cache@cdf6c1fa (v5) - actions/upload-artifact@bbbca2dd (v7) - erlef/setup-beam@ee09b1e5 (v1) - philss/rustler-precompiled-action@853ac56 (v1.1.4) - Add persist-credentials: false to all checkout steps (artipacked) - Remove overly broad pull-requests: write from workflow-level permissions - Fix template injection in all-checks-pass job by passing needs results via env vars rather than inline ${{ }} expressions - Move Turso secrets from job-level env to step-level env to reduce exposure surface (secrets-outside-env) - Replace dtolnay/rust-toolchain action with direct rustup script calls as recommended (superfluous-actions) - Replace softprops/action-gh-release action with gh release CLI call Remaining findings: 4 medium secrets-outside-env warnings for Turso secrets, which require configuring a GitHub Deployment Environment in repo settings. https://claude.ai/code/session_01EUdjWCLtSWQYY5j4yc8Qb5 --- .github/workflows/ci.yml | 108 ++++++++++++++++++++-------------- .github/workflows/release.yml | 23 ++++---- 2 files changed, 76 insertions(+), 55 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddf99a6..85a89c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,6 @@ name: CI permissions: contents: read - pull-requests: write on: pull_request: @@ -26,16 +25,18 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ matrix.rust }} - components: rustfmt, clippy + run: | + rustup update ${{ matrix.rust }} + rustup default ${{ matrix.rust }} + rustup component add rustfmt clippy - name: Cache Rust dependencies - uses: actions/cache@v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: | ~/.cargo/bin/ @@ -76,15 +77,18 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Rust nightly - uses: dtolnay/rust-toolchain@nightly - with: - components: llvm-tools-preview + run: | + rustup update nightly + rustup default nightly + rustup component add llvm-tools-preview - name: Cache Rust dependencies - uses: actions/cache@v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: | ~/.cargo/bin/ @@ -119,21 +123,21 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable + run: rustup update stable && rustup default stable - name: Set up Elixir - uses: erlef/setup-beam@v1 + uses: erlef/setup-beam@ee09b1e59bb240681c382eb1f0abc6a04af72764 # v1 with: elixir-version: ${{ matrix.elixir }} otp-version: ${{ matrix.otp }} - name: Cache Mix dependencies - uses: actions/cache@v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: | deps @@ -176,21 +180,21 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable + run: rustup update stable && rustup default stable - name: Set up Elixir - uses: erlef/setup-beam@v1 + uses: erlef/setup-beam@ee09b1e59bb240681c382eb1f0abc6a04af72764 # v1 with: elixir-version: ${{ matrix.elixir }} otp-version: ${{ matrix.otp }} - name: Cache Mix dependencies - uses: actions/cache@v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: | deps @@ -215,19 +219,21 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Rust - uses: dtolnay/rust-toolchain@stable + run: rustup update stable && rustup default stable - name: Set up Elixir - uses: erlef/setup-beam@v1 + uses: erlef/setup-beam@ee09b1e59bb240681c382eb1f0abc6a04af72764 # v1 with: elixir-version: "1.18.0" otp-version: "27.0" - name: Cache Mix dependencies - uses: actions/cache@v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: | deps @@ -237,7 +243,7 @@ jobs: ${{ runner.os }}-integration-mix- - name: Cache Rust dependencies - uses: actions/cache@v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: | ~/.cargo/bin/ @@ -289,13 +295,13 @@ jobs: needs: [rust-checks, elixir-tests-latest, elixir-tests-compatibility] # Only run on PRs to main if: github.event_name == 'pull_request' && github.base_ref == 'main' - env: - TURSO_DB_URI: ${{ secrets.TURSO_DB_URI }} - TURSO_AUTH_TOKEN: ${{ secrets.TURSO_AUTH_TOKEN }} steps: - name: Check if secrets are available id: check-secrets + env: + TURSO_DB_URI: ${{ secrets.TURSO_DB_URI }} + TURSO_AUTH_TOKEN: ${{ secrets.TURSO_AUTH_TOKEN }} run: | if [ -z "$TURSO_DB_URI" ] || [ -z "$TURSO_AUTH_TOKEN" ]; then echo "Secrets not available, skipping tests" @@ -307,22 +313,24 @@ jobs: - name: Checkout code if: steps.check-secrets.outputs.skip != 'true' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Rust if: steps.check-secrets.outputs.skip != 'true' - uses: dtolnay/rust-toolchain@stable + run: rustup update stable && rustup default stable - name: Set up Elixir if: steps.check-secrets.outputs.skip != 'true' - uses: erlef/setup-beam@v1 + uses: erlef/setup-beam@ee09b1e59bb240681c382eb1f0abc6a04af72764 # v1 with: elixir-version: "1.18.0" otp-version: "27.0" - name: Cache Mix dependencies if: steps.check-secrets.outputs.skip != 'true' - uses: actions/cache@v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: | deps @@ -333,7 +341,7 @@ jobs: - name: Cache Rust dependencies if: steps.check-secrets.outputs.skip != 'true' - uses: actions/cache@v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: | ~/.cargo/bin/ @@ -355,6 +363,9 @@ jobs: - name: Run Turso remote tests if: steps.check-secrets.outputs.skip != 'true' + env: + TURSO_DB_URI: ${{ secrets.TURSO_DB_URI }} + TURSO_AUTH_TOKEN: ${{ secrets.TURSO_AUTH_TOKEN }} run: mix test test/turso_remote_test.exs --trace all-checks-pass: @@ -374,21 +385,28 @@ jobs: steps: - name: Check if all jobs passed run: | - if [ "${{ needs.rust-checks.result }}" != "success" ] || \ - [ "${{ needs.rust-fuzz.result }}" != "success" ] || \ - [ "${{ needs.elixir-tests-latest.result }}" != "success" ] || \ - [ "${{ needs.elixir-tests-compatibility.result }}" != "success" ] || \ - [ "${{ needs.integration-test.result }}" != "success" ]; then + if [ "${NEEDS_RUST_CHECKS_RESULT}" != "success" ] || \ + [ "${NEEDS_RUST_FUZZ_RESULT}" != "success" ] || \ + [ "${NEEDS_ELIXIR_TESTS_LATEST_RESULT}" != "success" ] || \ + [ "${NEEDS_ELIXIR_TESTS_COMPATIBILITY_RESULT}" != "success" ] || \ + [ "${NEEDS_INTEGRATION_TEST_RESULT}" != "success" ]; then echo "One or more checks failed" exit 1 fi echo "All checks passed successfully!" # Note: Turso remote tests are optional and don't block the build - if [ "${{ needs.turso-remote-tests.result }}" == "success" ]; then + if [ "${NEEDS_TURSO_REMOTE_TESTS_RESULT}" == "success" ]; then echo "Turso remote tests also passed!" - elif [ "${{ needs.turso-remote-tests.result }}" == "skipped" ]; then + elif [ "${NEEDS_TURSO_REMOTE_TESTS_RESULT}" == "skipped" ]; then echo "Turso remote tests were skipped (not on PR to main or credentials not available)" - elif [ "${{ needs.turso-remote-tests.result }}" == "failure" ]; then + elif [ "${NEEDS_TURSO_REMOTE_TESTS_RESULT}" == "failure" ]; then echo "WARNING: Turso remote tests failed (but not blocking the build)" fi + env: + NEEDS_RUST_CHECKS_RESULT: ${{ needs.rust-checks.result }} + NEEDS_RUST_FUZZ_RESULT: ${{ needs.rust-fuzz.result }} + NEEDS_ELIXIR_TESTS_LATEST_RESULT: ${{ needs.elixir-tests-latest.result }} + NEEDS_ELIXIR_TESTS_COMPATIBILITY_RESULT: ${{ needs.elixir-tests-compatibility.result }} + NEEDS_INTEGRATION_TEST_RESULT: ${{ needs.integration-test.result }} + NEEDS_TURSO_REMOTE_TESTS_RESULT: ${{ needs.turso-remote-tests.result }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e888cb6..634231f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,7 +48,9 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Extract and validate project version shell: bash @@ -83,13 +85,14 @@ jobs: echo "PROJECT_VERSION=$VERSION" >> $GITHUB_ENV - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.job.target }} + run: | + rustup update stable + rustup default stable + rustup target add ${{ matrix.job.target }} - name: Build the project id: build-crate - uses: philss/rustler-precompiled-action@v1.1.4 + uses: philss/rustler-precompiled-action@853ac56183f29a080304df3ff8a194b5bbdc24cc # v1.1.4 with: project-name: ecto_libsql project-version: ${{ env.PROJECT_VERSION }} @@ -99,14 +102,14 @@ jobs: project-dir: "." - name: Artifact upload - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: ${{ steps.build-crate.outputs.file-name }} path: ${{ steps.build-crate.outputs.file-path }} - name: Publish archives and packages - uses: softprops/action-gh-release@v2 - with: - files: | - ${{ steps.build-crate.outputs.file-path }} if: startsWith(github.ref, 'refs/tags/') && inputs.test_only != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_FILE_PATH: ${{ steps.build-crate.outputs.file-path }} + run: gh release upload "${GITHUB_REF_NAME}" "${RELEASE_FILE_PATH}" --clobber