From 6c003dad132dadb1097d6ed9da24eb2263832c06 Mon Sep 17 00:00:00 2001 From: JJordan0K <69581081+JJordan0C@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:16:57 +0200 Subject: [PATCH 1/3] ci: improve cross-platform workflow --- .github/workflows/ci.yml | 86 +++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ca6d9c..2d54b6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,38 +23,21 @@ jobs: timeout-minutes: 25 steps: - uses: actions/checkout@v7 - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: 1.90.0 - components: rustfmt, clippy - - name: Check formatting - run: cargo fmt --all -- --check - uses: actions/setup-node@v7 with: node-version: 22 cache: npm cache-dependency-path: crates/needle-app/web/package-lock.json - - name: Cache Rust dependencies and build - uses: actions/cache@v6 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-rust-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }} - restore-keys: | - ${{ runner.os }}-rust-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }}- - ${{ runner.os }}-rust- - name: Install web dependencies working-directory: crates/needle-app/web run: npm ci - - name: Verify web unit tests, lint and production bundle + - name: Test, type-check, lint, and build the web control plane working-directory: crates/needle-app/web run: | npm test + npm run typecheck npm run lint npm run build - - run: cargo clippy --locked --workspace --all-targets -- -D warnings - name: Upload web production bundle uses: actions/upload-artifact@v7 with: @@ -63,12 +46,24 @@ jobs: retention-days: 1 if-no-files-found: error - checks: - needs: frontend-quality + rust-format: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: 1.90.0 + components: rustfmt + - name: Check Rust formatting + run: cargo fmt --all -- --check + + platform-checks: + needs: [frontend-quality, rust-format] strategy: fail-fast: false matrix: - os: ${{ fromJSON(github.event_name == 'pull_request' && '["ubuntu-latest"]' || '["windows-latest", "ubuntu-latest", "macos-latest"]') }} + os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} timeout-minutes: 30 steps: @@ -103,11 +98,13 @@ jobs: if: matrix.os == 'ubuntu-latest' working-directory: crates/needle-app/web run: npm ci - - name: Check Windows-specific lints - if: matrix.os == 'windows-latest' + - name: Check Rust lints + if: matrix.os == 'ubuntu-latest' || matrix.os == 'windows-latest' run: cargo clippy --locked --workspace --all-targets -- -D warnings - - run: cargo test --locked --workspace - - run: cargo build --locked --workspace + - name: Test the Rust workspace + run: cargo test --locked --workspace + - name: Build the Rust workspace + run: cargo build --locked --workspace - name: Cache Playwright browsers if: matrix.os == 'ubuntu-latest' uses: actions/cache@v6 @@ -124,5 +121,38 @@ jobs: if: matrix.os == 'ubuntu-latest' working-directory: crates/needle-app/web run: npm run test:e2e:local - - run: cargo run --locked -p needle-app -- plugin package --output ci-plugin - - run: cargo run --locked -p needle-app -- plugin package --benchmark --output ci-benchmark-plugin + - name: Validate product and benchmark plugins + run: | + cargo run --locked -p needle-app -- plugin validate + cargo run --locked -p needle-app -- plugin validate --benchmark + - name: Package product and benchmark plugins + run: | + cargo run --locked -p needle-app -- plugin package --output ci-plugin + cargo run --locked -p needle-app -- plugin package --benchmark --output ci-benchmark-plugin + + # Preserve the existing required-check context while making it cover every OS. + checks: + name: checks (ubuntu-latest) + if: always() + needs: [frontend-quality, rust-format, platform-checks] + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Require every CI stage + env: + FRONTEND_RESULT: ${{ needs['frontend-quality'].result }} + RUST_FORMAT_RESULT: ${{ needs['rust-format'].result }} + PLATFORM_RESULT: ${{ needs['platform-checks'].result }} + run: | + if [[ "$FRONTEND_RESULT" != "success" ]]; then + echo "::error::Frontend quality failed: $FRONTEND_RESULT" + exit 1 + fi + if [[ "$RUST_FORMAT_RESULT" != "success" ]]; then + echo "::error::Rust formatting failed: $RUST_FORMAT_RESULT" + exit 1 + fi + if [[ "$PLATFORM_RESULT" != "success" ]]; then + echo "::error::One or more platform checks failed: $PLATFORM_RESULT" + exit 1 + fi From d5a87ed87b92509730668e2661fb8c8e92b29e7b Mon Sep 17 00:00:00 2001 From: JJordan0K <69581081+JJordan0C@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:13:16 +0200 Subject: [PATCH 2/3] ci: add security and dependency checks Add CodeQL, dependency review, cargo-deny, Zizmor, Codecov, and OpenSSF Scorecard workflows. Configure Dependabot for Cargo, npm, and GitHub Actions dependencies, and pin CI actions to immutable commit SHAs. --- .github/dependabot.yml | 62 +++++++++++++++++++++++++ .github/workflows/ci.yml | 28 ++++++----- .github/workflows/codeql.yml | 54 +++++++++++++++++++++ .github/workflows/coverage.yml | 52 +++++++++++++++++++++ .github/workflows/dependency-review.yml | 24 ++++++++++ .github/workflows/scorecard.yml | 41 ++++++++++++++++ .github/workflows/supply-chain.yml | 48 +++++++++++++++++++ deny.toml | 31 +++++++++++++ 8 files changed, 329 insertions(+), 11 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/coverage.yml create mode 100644 .github/workflows/dependency-review.yml create mode 100644 .github/workflows/scorecard.yml create mode 100644 .github/workflows/supply-chain.yml create mode 100644 deny.toml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f0902ce --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,62 @@ +version: 2 + +updates: + - package-ecosystem: cargo + directory: "/" + schedule: + interval: weekly + day: monday + time: "06:00" + timezone: Europe/Rome + cooldown: + default-days: 7 + semver-major-days: 30 + semver-minor-days: 7 + semver-patch-days: 3 + open-pull-requests-limit: 5 + groups: + rust-dependencies: + patterns: + - "*" + commit-message: + prefix: "chore(deps)" + + - package-ecosystem: npm + directory: "/crates/needle-app/web" + schedule: + interval: weekly + day: monday + time: "06:15" + timezone: Europe/Rome + cooldown: + default-days: 7 + semver-major-days: 30 + semver-minor-days: 7 + semver-patch-days: 3 + open-pull-requests-limit: 5 + groups: + frontend-dependencies: + patterns: + - "*" + commit-message: + prefix: "chore(deps)" + + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + day: monday + time: "06:30" + timezone: Europe/Rome + cooldown: + default-days: 7 + semver-major-days: 30 + semver-minor-days: 7 + semver-patch-days: 3 + open-pull-requests-limit: 5 + groups: + github-actions: + patterns: + - "*" + commit-message: + prefix: "ci(deps)" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d54b6b..0f9b63e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,8 +22,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 25 steps: - - uses: actions/checkout@v7 - - uses: actions/setup-node@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 cache: npm @@ -39,7 +41,7 @@ jobs: npm run lint npm run build - name: Upload web production bundle - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: web-dist path: crates/needle-app/web/dist @@ -50,8 +52,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v7 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 with: toolchain: 1.90.0 components: rustfmt @@ -67,13 +71,15 @@ jobs: runs-on: ${{ matrix.os }} timeout-minutes: 30 steps: - - uses: actions/checkout@v7 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 with: toolchain: 1.90.0 components: clippy - name: Cache Rust dependencies and build - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.cargo/registry @@ -84,11 +90,11 @@ jobs: ${{ runner.os }}-rust-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }}- ${{ runner.os }}-rust- - name: Download web production bundle - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: web-dist path: crates/needle-app/web/dist - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 if: matrix.os == 'ubuntu-latest' with: node-version: 22 @@ -107,7 +113,7 @@ jobs: run: cargo build --locked --workspace - name: Cache Playwright browsers if: matrix.os == 'ubuntu-latest' - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: .cache/ms-playwright key: ${{ runner.os }}-playwright-${{ hashFiles('crates/needle-app/web/package-lock.json') }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..87221ae --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,54 @@ +name: codeql + +on: + pull_request: + push: + branches: [main] + schedule: + - cron: "19 4 * * 1" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: codeql-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + analyze: + name: analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + packages: read + security-events: write + strategy: + fail-fast: false + matrix: + include: + - language: rust + build-mode: none + - language: javascript-typescript + build-mode: none + - language: actions + build-mode: none + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 + if: matrix.language == 'rust' + with: + toolchain: 1.90.0 + - name: Initialize CodeQL + uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + queries: security-extended + - name: Analyze + uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..64dd5ff --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,52 @@ +name: coverage + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + id-token: write + +concurrency: + group: coverage-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + rust-coverage: + runs-on: ubuntu-latest + timeout-minutes: 35 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 22 + cache: npm + cache-dependency-path: crates/needle-app/web/package-lock.json + - name: Build embedded frontend assets + working-directory: crates/needle-app/web + run: | + npm ci + npm run build + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 + with: + toolchain: 1.90.0 + components: llvm-tools-preview + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@67729d5c413db75907f0ad1e39bb04b9c868ff60 # v2.85.7 + with: + tool: cargo-llvm-cov + - name: Generate Rust coverage + run: cargo llvm-cov --locked --workspace --lcov --output-path lcov.info + - name: Upload coverage to Codecov + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + files: lcov.info + flags: rust + name: rust + use_oidc: true + fail_ci_if_error: false diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..00149c1 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,24 @@ +name: dependency-review + +on: + pull_request: + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Review dependency changes + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 + with: + fail-on-severity: high + vulnerability-check: true + license-check: true + show-patched-versions: true + retry-on-snapshot-warnings: true diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..b628fca --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,41 @@ +name: scorecard + +on: + branch_protection_rule: + schedule: + - cron: "11 5 * * 1" + push: + branches: [main] + workflow_dispatch: + +permissions: read-all + +jobs: + analysis: + name: scorecard + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + security-events: write + id-token: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Run OpenSSF Scorecard + uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + - name: Upload Scorecard artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: scorecard-results + path: results.sarif + retention-days: 5 + - name: Upload Scorecard results to code scanning + uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + with: + sarif_file: results.sarif diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml new file mode 100644 index 0000000..390880b --- /dev/null +++ b/.github/workflows/supply-chain.yml @@ -0,0 +1,48 @@ +name: supply-chain + +on: + pull_request: + push: + branches: [main] + schedule: + - cron: "37 4 * * 1" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: supply-chain-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + cargo-deny: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Audit Rust dependencies + uses: EmbarkStudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25 # v2.1.1 + with: + arguments: --all-features --locked + command: check + + zizmor: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Audit GitHub Actions workflows + uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 + with: + advanced-security: false + annotations: true + min-severity: medium + min-confidence: medium + version: v1.21.0 diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..7551bbd --- /dev/null +++ b/deny.toml @@ -0,0 +1,31 @@ +[advisories] +ignore = [ + { id = "RUSTSEC-2024-0436", reason = "Transitive through statrs 0.18; no safe upgrade is currently available." }, +] + +[licenses] +allow = [ + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "MIT", + "MIT-0", + "Unicode-3.0", + "Unlicense", + "Zlib", +] +confidence-threshold = 0.8 + +[bans] +multiple-versions = "warn" +wildcards = "warn" +highlight = "all" +workspace-default-features = "allow" +external-default-features = "allow" + +[sources] +unknown-registry = "deny" +unknown-git = "deny" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +allow-git = [] From 43d6d8c7468c8390067f6fea33c0bab30b81601c Mon Sep 17 00:00:00 2001 From: JJordan0K <69581081+JJordan0C@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:35:29 +0200 Subject: [PATCH 3/3] Update ci.yml --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f9b63e..0951a47 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -136,9 +136,9 @@ jobs: cargo run --locked -p needle-app -- plugin package --output ci-plugin cargo run --locked -p needle-app -- plugin package --benchmark --output ci-benchmark-plugin - # Preserve the existing required-check context while making it cover every OS. - checks: - name: checks (ubuntu-latest) + # Single required check covering every CI stage and operating system. + ci-required: + name: ci-required if: always() needs: [frontend-quality, rust-format, platform-checks] runs-on: ubuntu-latest