diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml new file mode 100644 index 00000000..a8f3eec7 --- /dev/null +++ b/.github/workflows/actionlint.yml @@ -0,0 +1,30 @@ +name: actionlint + +# Lint the GitHub Actions workflows: YAML/expression correctness, action input +# validity, and shellcheck over every run: block. Complements zizmor (security) +# with correctness. actionlint is version-pinned and installed via the runner's +# preinstalled Go toolchain (no extra third-party action to trust). +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + actionlint: + name: actionlint (workflow lint) + runs-on: ubuntu-latest + steps: + - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Install + run actionlint (pinned) + run: | + go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 + "$(go env GOPATH)/bin/actionlint" -color diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 5ef8c8bc..81aac9f3 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -34,6 +34,8 @@ jobs: with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: EmbarkStudios/cargo-deny-action@6c8f9facfa5047ec02d8485b6bf52b587b7777d1 # v2.0.18 with: command: check advisories diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index c12f325e..ec1b2b05 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -64,6 +64,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # full history + tags for git-cliff + persist-credentials: false - name: Generate release notes if: steps.semver.outputs.ok == 'true' # orhun/git-cliff-action v4.8.0, SHA-pinned (supply-chain hygiene for a diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e566fca7..f44d2929 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,11 +48,14 @@ jobs: - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (2026-03-27) with: + toolchain: stable components: clippy, rustfmt - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: cargo fmt --check run: cargo fmt --all --check - name: cargo clippy @@ -74,9 +77,13 @@ jobs: - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (2026-03-27) + with: + toolchain: stable + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: cargo test (default features) run: cargo test - name: cargo test (experimental) @@ -100,9 +107,13 @@ jobs: - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@1.89.0 - - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # MSRV pin; channel via toolchain: below + with: + toolchain: 1.89.0 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: cargo build run: cargo build - name: cargo test @@ -120,9 +131,13 @@ jobs: - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (2026-03-27) + with: + toolchain: stable + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: assert no BLAS/faer/ndarray/statrs in the tree run: | set -euo pipefail @@ -167,6 +182,8 @@ jobs: with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: release-publish SBOM invariants run: bash tests/release_publish_invariants.sh @@ -185,8 +202,10 @@ jobs: - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@v6 - - uses: EmbarkStudios/cargo-deny-action@v2.0.18 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: EmbarkStudios/cargo-deny-action@6c8f9facfa5047ec02d8485b6bf52b587b7777d1 # v2.0.18 with: command: check @@ -213,9 +232,13 @@ jobs: - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (2026-03-27) + with: + toolchain: stable + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: install Intel SDE env: # SHA256 of ${SDE_VERSION}.tar.xz — verified before the archive is @@ -284,11 +307,14 @@ jobs: - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (2026-03-27) with: + toolchain: stable targets: wasm32-unknown-unknown, wasm32-wasip1 - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: install wasmtime (executes the wasip1 test binary) run: | set -euo pipefail @@ -328,8 +354,12 @@ jobs: - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (2026-03-27) + with: + toolchain: stable + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: run bench_rank (scaled, seeded synthetic corpus) run: cargo run --release --example bench_rank -- --n 10000 --queries 100 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 23747032..2428ed3f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -64,6 +64,8 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Initialize CodeQL uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4 diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index e279fad4..d5e1c9a1 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -39,11 +39,14 @@ jobs: - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (2026-03-27) + with: + toolchain: stable components: clippy, rustfmt - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 # The core ci.yml clippy is scoped to the core crate via default-members, # so the binding gets its quality gate here instead. - run: cargo fmt -p ordvec-python --check @@ -79,12 +82,16 @@ jobs: - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@v6 - - uses: actions/setup-python@v6.2.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python }} - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (2026-03-27) + with: + toolchain: stable + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: install build + test deps run: python -m pip install --upgrade pip maturin numpy pytest - name: build the wheel (abi3, release) diff --git a/.github/workflows/release-crate.yml b/.github/workflows/release-crate.yml index ef5269f0..72fe54d2 100644 --- a/.github/workflows/release-crate.yml +++ b/.github/workflows/release-crate.yml @@ -39,6 +39,8 @@ jobs: with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (2026-03-27) with: toolchain: stable @@ -101,6 +103,8 @@ jobs: with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (2026-03-27) with: toolchain: stable diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index e3fa762e..a1ffb76b 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -45,6 +45,8 @@ jobs: with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Build abi3 wheel (covers CPython 3.9+) uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: @@ -85,6 +87,8 @@ jobs: with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Build the sdist uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 00000000..598cfff7 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,49 @@ +name: scorecard + +# OpenSSF Scorecard — supply-chain posture score (branch protection, pinned +# deps, token permissions, CI tests, vuln reporting, etc.). Publishes results to +# the OpenSSF API (enables the score badge) and uploads SARIF to code scanning. +on: + branch_protection_rule: + schedule: + - cron: "27 6 * * 1" # weekly, Monday 06:27 UTC + push: + branches: [main] + +permissions: + contents: read + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Job-level permissions REPLACE the workflow-level set (omitted scopes + # become none), so contents: read must be explicit here — checkout and + # Scorecard need it to read the repo. + contents: read + security-events: write # upload the SARIF result to code scanning + id-token: write # OIDC to publish results to the OpenSSF API + steps: + - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Run Scorecard + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + - name: Upload the result as a build artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: scorecard-results + path: results.sarif + retention-days: 5 + - name: Upload SARIF to code scanning + uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.31.6 + with: + sarif_file: results.sarif diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 00000000..013d812d --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,34 @@ +name: zizmor + +# Static security audit of the GitHub Actions workflows themselves — action +# pinning, token permissions, template injection, credential persistence. This +# locks in the supply-chain hardening: a future PR that adds an unpinned action, +# a broad GITHUB_TOKEN, or a credential-persisting checkout fails here. Run +# offline + version-pinned so the gate is deterministic. +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + zizmor: + name: zizmor (workflow security audit) + runs-on: ubuntu-latest + steps: + - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.13" + - name: Install zizmor (pinned) + run: pip install zizmor==1.25.2 + - name: Audit workflows + run: zizmor --offline --persona=regular .github/workflows/