From 5f2f17208b641cb7f81336c42c8e1b7b5beddc64 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Mar 2026 09:27:30 +0000 Subject: [PATCH 1/4] 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 From 744e03b5c5815182ba518729b85435f15d6653f6 Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Fri, 20 Mar 2026 10:41:47 +1100 Subject: [PATCH 2/4] chore(security): Add default days for package update cooldown per Zizmor's suggestion --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7e1182d..3b5de7d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,6 +14,7 @@ updates: semver-major-days: 10 semver-minor-days: 5 semver-patch-days: 2 + default-days: 7 # Enable version checks and updates for Rust dependencies. - package-ecosystem: "cargo" directory: "/native/ecto_libsql/" @@ -23,6 +24,7 @@ updates: semver-major-days: 10 semver-minor-days: 5 semver-patch-days: 2 + default-days: 7 # Enable version updates for GitHub Actions. - package-ecosystem: "github-actions" # Workflow files stored in the default location of `.github/workflows` @@ -30,3 +32,5 @@ updates: directory: "/" schedule: interval: "monthly" + cooldown: + default-days: 7 From 25611c06382548c11b3f34e8ea796f90d09ac991 Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Fri, 20 Mar 2026 10:42:22 +1100 Subject: [PATCH 3/4] chore(security): Add environment for running Turos remote tests to scope secrets --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85a89c6..20879dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -292,6 +292,7 @@ jobs: turso-remote-tests: name: Turso Remote Database Tests runs-on: ubuntu-latest + environment: test 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' From 933ca89df0473f5c9dc02128a9de86ceb8ef5ae5 Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Fri, 20 Mar 2026 10:45:35 +1100 Subject: [PATCH 4/4] chore(security): Add correct tags for actions/cache version --- .github/workflows/ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20879dc..c582493 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: rustup component add rustfmt clippy - name: Cache Rust dependencies - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/.cargo/bin/ @@ -88,7 +88,7 @@ jobs: rustup component add llvm-tools-preview - name: Cache Rust dependencies - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/.cargo/bin/ @@ -137,7 +137,7 @@ jobs: otp-version: ${{ matrix.otp }} - name: Cache Mix dependencies - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | deps @@ -194,7 +194,7 @@ jobs: otp-version: ${{ matrix.otp }} - name: Cache Mix dependencies - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | deps @@ -233,7 +233,7 @@ jobs: otp-version: "27.0" - name: Cache Mix dependencies - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | deps @@ -243,7 +243,7 @@ jobs: ${{ runner.os }}-integration-mix- - name: Cache Rust dependencies - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/.cargo/bin/ @@ -331,7 +331,7 @@ jobs: - name: Cache Mix dependencies if: steps.check-secrets.outputs.skip != 'true' - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | deps @@ -342,7 +342,7 @@ jobs: - name: Cache Rust dependencies if: steps.check-secrets.outputs.skip != 'true' - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/.cargo/bin/