From a76cb89952e1fdc21d8bdb08ea9766fa1c61e7e3 Mon Sep 17 00:00:00 2001 From: Nelson Spence Date: Mon, 25 May 2026 10:01:54 -0500 Subject: [PATCH 1/5] ci: bump actions to Node 24 + add harden-runner across workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Node-20 deprecation fix (GitHub forces Node 24 by 2026-06): actions/checkout v4->v6 (ci/python as tags; audit/changelog re-pinned to the v6.0.2 SHA), actions/setup-python v5->v6 (python). Adds step-security/harden-runner@v2.19.4 (SHA-pinned, egress-policy: audit) as the first step of every job — an EDR-style egress/process/file baseline. Audit (not block) avoids breaking egress and is supported on Linux/macOS/Windows runners. The changelog SemVer gate is unchanged. --- .github/workflows/audit.yml | 5 +++- .github/workflows/changelog.yml | 7 ++++-- .github/workflows/ci.yml | 42 ++++++++++++++++++++++++++------- .github/workflows/python.yml | 12 +++++++--- 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 3e5d2c30..7c51a03c 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -30,7 +30,10 @@ jobs: name: cargo-deny advisories runs-on: ubuntu-latest steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: EmbarkStudios/cargo-deny-action@a531616d8ce3b9177443e48a1159bc945a099823 # v2.0.19 with: command: check advisories diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index a23c505d..c12f325e 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -40,6 +40,9 @@ jobs: name: release notes (git-cliff) runs-on: ubuntu-latest steps: + - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit - name: Gate on a stable SemVer tag id: semver shell: bash @@ -55,10 +58,10 @@ jobs: echo "::notice::$TAG_NAME is not a stable vMAJOR.MINOR.PATCH tag; skipping release notes." echo "ok=false" >> "$GITHUB_OUTPUT" fi - # actions/checkout v4.3.1, SHA-pinned — a contents:write job pins ALL + # actions/checkout v6.0.2, SHA-pinned — a contents:write job pins ALL # tooling to immutable refs (no mutable tags), git-cliff-action included. - if: steps.semver.outputs.ok == 'true' - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # full history + tags for git-cliff - name: Generate release notes diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05f8b7f1..97410f20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,12 @@ jobs: name: lint (fmt + clippy) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + # Establish an egress baseline (audit-only: logs outbound traffic + # without blocking it). A later PR can switch to block + allowlist. + - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: components: clippy, rustfmt @@ -66,7 +71,10 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v4 + - 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 - name: cargo test (default features) @@ -89,7 +97,10 @@ jobs: name: msrv (1.89.0) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - 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 - name: cargo build @@ -106,7 +117,10 @@ jobs: name: deps (no-system-deps + publish dry-run) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - 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 - name: assert no BLAS/faer/ndarray/statrs in the tree @@ -145,7 +159,10 @@ jobs: name: cargo-deny (advisories + licenses + bans + sources) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + - uses: actions/checkout@v6 - uses: EmbarkStudios/cargo-deny-action@v2 with: command: check @@ -170,7 +187,10 @@ jobs: SDE_VERSION: sde-external-10.7.0-2026-02-18-lin SDE_URL_BASE: https://downloadmirror.intel.com/913594 steps: - - uses: actions/checkout@v4 + - 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 - name: install Intel SDE @@ -238,7 +258,10 @@ jobs: name: wasm32 (simd128) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown, wasm32-wasip1 @@ -279,7 +302,10 @@ jobs: matrix: os: [ubuntu-latest, ubuntu-24.04-arm] steps: - - uses: actions/checkout@v4 + - 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 - name: run bench_rank (scaled, seeded synthetic corpus) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index f5d68988..b1ad1574 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -36,7 +36,10 @@ jobs: name: clippy + fmt (binding) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: components: clippy, rustfmt @@ -73,8 +76,11 @@ jobs: - { os: macos-latest, python: "3.13" } # macOS arm64 — NEON - { os: windows-latest, python: "3.13" } # windows x64 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python }} - uses: dtolnay/rust-toolchain@stable From b6c73caeec05397c3a11a53e03d8ba5fa0e8b465 Mon Sep 17 00:00:00 2001 From: Nelson Spence Date: Mon, 25 May 2026 10:01:54 -0500 Subject: [PATCH 2/5] ci: add Dependabot version updates with a 7-day cooldown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit github-actions (primary), cargo (workspace + the excluded fuzz crate), and pip (ordvec-python). Every ecosystem carries cooldown.default-days: 7 so nothing is auto-bumped same-day — a freshly published malicious release (the 'mini shai-hulud' npm-worm class of supply-chain attack) gets a week to be flagged/yanked before we'd open a PR. Grouped to cut noise. --- .github/dependabot.yml | 64 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..bc0d0f94 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,64 @@ +# Dependabot version updates. Docs: https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference +# Every ecosystem carries a 7-day cooldown: no dependency is bumped same-day, so a +# freshly-published malicious release (the "mini shai-hulud" npm-worm class of +# supply-chain attack) has a week to be flagged/yanked before we'd auto-PR it. +version: 2 +updates: + # PRIMARY TARGET: actions are SHA-pinned in .github/workflows; Dependabot keeps + # those pins current (and re-pins to the new SHA), just delayed 7 days. One + # grouped PR keeps the action bumps from flooding the queue. + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + cooldown: + default-days: 7 + open-pull-requests-limit: 5 + groups: + github-actions: + patterns: + - "*" + + # Cargo workspace root: core `ordvec` + the `ordvec-python` binding member + # (single workspace Cargo.lock). Group minor+patch into one PR; majors stay + # separate for manual review. + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + cooldown: + default-days: 7 + open-pull-requests-limit: 5 + groups: + cargo-minor-patch: + update-types: + - "minor" + - "patch" + + # ordvec-python declared Python deps (numpy). Grouped; no-op if nothing parseable. + - package-ecosystem: "pip" + directory: "/ordvec-python" + schedule: + interval: "weekly" + cooldown: + default-days: 7 + open-pull-requests-limit: 5 + groups: + pip-minor-patch: + update-types: + - "minor" + - "patch" + + # fuzz/ is a workspace-EXCLUDED standalone crate (own Cargo.lock), nightly-only + # cargo-fuzz tooling. Dev-only surface, low priority — group all, tight PR limit. + - package-ecosystem: "cargo" + directory: "/fuzz" + schedule: + interval: "weekly" + cooldown: + default-days: 7 + open-pull-requests-limit: 2 + groups: + fuzz-deps: + patterns: + - "*" From 6654601316b051a19c0eafe41edeb1ef589c82b9 Mon Sep 17 00:00:00 2001 From: Nelson Spence Date: Mon, 25 May 2026 10:01:54 -0500 Subject: [PATCH 3/5] ci: add CodeQL code scanning for Rust, Python, and Actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scans rust + python + actions (all GA; build-mode: none — no compiler/MSRV run). push/PR to main + weekly schedule (off the audit.yml slot). Least-privilege: security-events: write only on the analyze job; all actions SHA-pinned (scheduled + write-scoped); harden-runner first. --- .github/workflows/codeql.yml | 72 ++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..90b01be7 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,72 @@ +name: codeql + +# CodeQL code scanning (GitHub's static-analysis / variant-analysis engine). +# Scans the Rust core crate, the PyO3 Python bindings, and the GitHub Actions +# workflows themselves, surfacing security findings as code-scanning alerts. +# +# build-mode: none — CodeQL's Rust support and build-less ("no-build") database +# creation are GA (CodeQL >= 2.23.3). Rust, Python, and `actions` are all +# analysed WITHOUT a compiler/interpreter run, so no `cargo build` (and no +# MSRV toolchain) is needed here; the database is built directly from source. +# This keeps the scan fast and independent of the build matrix. +# +# Least-privilege permissions: `security-events: write` is the only elevated +# scope (required to upload SARIF / publish alerts); `contents: read` + +# `actions: read` are read-only. Nothing else is granted. +# +# Because this workflow runs UNATTENDED on a cron schedule AND holds +# `security-events: write`, every third-party action is SHA-pinned (immutable) +# — a compromised mutable tag (@v3/@v6) cannot auto-execute on a scheduled, +# write-scoped runner. harden-runner audits egress as the first step. + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + # Weekly. Wednesday 07:42 UTC — deliberately off the audit.yml slot + # (Monday 06:17 UTC) so the two scheduled scans never collide. + - cron: "42 7 * * 3" + +# Least-privilege: read-only at the workflow level; the analyze job elevates to +# security-events: write (the minimum CodeQL needs to publish alerts). +permissions: + contents: read + +concurrency: + group: codeql-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + permissions: + security-events: write # upload SARIF / publish code-scanning alerts + contents: read # checkout source + actions: read # read workflow run metadata (required by CodeQL) + strategy: + fail-fast: false + matrix: + language: [rust, python, actions] + steps: + - name: Harden runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Initialize CodeQL + uses: github/codeql-action/init@03e4368ac7daa2bd82b3e85262f3bf87ee112f57 # v3 + with: + languages: ${{ matrix.language }} + # No-build database creation (GA for rust/python/actions). + build-mode: none + + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@03e4368ac7daa2bd82b3e85262f3bf87ee112f57 # v3 + with: + category: "/language:${{ matrix.language }}" From 350b319e79972148e9e751cc06262e3c49722c84 Mon Sep 17 00:00:00 2001 From: Nelson Spence Date: Mon, 25 May 2026 10:18:49 -0500 Subject: [PATCH 4/5] ci: bump codeql-action v3 (node20) -> v4 (node24) Codex P1 (#42): github/codeql-action@v3 still declares runs.using: node20 for init/analyze, so it would break at the Node-20 retirement. v4 runs on node24 (verified: init/action.yml -> 'using: node24'). Pin init + analyze to the v4 SHA 7211b7c. Same inputs (languages / build-mode: none / category); the analyze legs already pass on v3, and v4 is the same engine on a newer runtime. --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 90b01be7..f33f7357 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,7 +16,7 @@ name: codeql # # Because this workflow runs UNATTENDED on a cron schedule AND holds # `security-events: write`, every third-party action is SHA-pinned (immutable) -# — a compromised mutable tag (@v3/@v6) cannot auto-execute on a scheduled, +# — a compromised mutable tag (@v4/@v6) cannot auto-execute on a scheduled, # write-scoped runner. harden-runner audits egress as the first step. on: @@ -60,13 +60,13 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Initialize CodeQL - uses: github/codeql-action/init@03e4368ac7daa2bd82b3e85262f3bf87ee112f57 # v3 + uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4 with: languages: ${{ matrix.language }} # No-build database creation (GA for rust/python/actions). build-mode: none - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@03e4368ac7daa2bd82b3e85262f3bf87ee112f57 # v3 + uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4 with: category: "/language:${{ matrix.language }}" From 4a73aa3990bc0b8854901673b43e8383538a097e Mon Sep 17 00:00:00 2001 From: Nelson Spence Date: Mon, 25 May 2026 10:38:46 -0500 Subject: [PATCH 5/5] ci: skip CodeQL on fork PRs (read-only token can't upload SARIF) (#42 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit qodo (Reliability): on a pull_request from a fork, GitHub provides a read-only GITHUB_TOKEN, so github/codeql-action/analyze's SARIF upload (security-events: write) fails — a spurious failure on external PRs. Gate the analyze job with 'github.event_name != pull_request || head.repo.full_name == github.repository' so it runs on push-to-main + the weekly schedule (full coverage) and same-repo PRs (PR-time scanning), and skips fork PRs. CodeQL is also not in the required-status-check set, so fork PRs are never blocked regardless. --- .github/workflows/codeql.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f33f7357..23747032 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -42,6 +42,12 @@ jobs: analyze: name: analyze (${{ matrix.language }}) runs-on: ubuntu-latest + # Skip CodeQL on PRs from forks: a forked PR gets a read-only GITHUB_TOKEN, + # so the SARIF upload (needs security-events: write) can't succeed and would + # fail noisily. push-to-main + the weekly schedule still scan everything that + # lands, and same-repo PRs still get PR-time scanning. (CodeQL is also not a + # required status check, so a fork PR is never blocked either way.) + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} permissions: security-events: write # upload SARIF / publish code-scanning alerts contents: read # checkout source