diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..3a579e2 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,46 @@ +# Dependabot configuration (#554) +# +# Keeps Cargo dependencies and GitHub Actions up to date, which is the +# front line of dependency-vulnerability management: most advisories are +# resolved by upgrading to a patched release. +version: 2 +updates: + # Rust workspace (contracts + path deps resolve via the root lockfile). + - package-ecosystem: cargo + directory: "/" + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 10 + labels: + - dependencies + - rust + commit-message: + prefix: "deps" + include: scope + + # Standalone api-server crate (its own lockfile). + - package-ecosystem: cargo + directory: "/api-server" + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 10 + labels: + - dependencies + - rust + commit-message: + prefix: "deps(api-server)" + include: scope + + # GitHub Actions used across the workflows. + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + day: monday + labels: + - dependencies + - ci + commit-message: + prefix: "ci" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6607dd1..7c0a228 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,24 +31,13 @@ jobs: - name: Clippy run: cargo clippy --workspace --all-targets -- -D warnings - - name: Security audit - run: | - cargo install cargo-audit - cargo audit + # Security audit and dependency vulnerability scanning now live in + # security.yml (#553) and dependency-scan.yml (#554). + # Code coverage enforcement lives in coverage.yml (#555). + # Mutation testing lives in mutation.yml (#556). - name: Test run: cargo test --workspace - name: Regression tests run: cargo test --workspace regression_tests - - - name: Code coverage - run: | - cargo install cargo-tarpaulin - cargo tarpaulin --workspace --out Xml --output-dir coverage - - - name: Upload coverage - uses: codecov/codecov-action@v4 - with: - files: ./coverage/cobertura.xml - fail_ci_if_error: true diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..d7c84e4 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,65 @@ +name: Coverage + +# Code coverage enforcement (#555). +# +# Runs cargo-tarpaulin over the workspace and FAILS the build if line +# coverage drops below the threshold below. Results are also uploaded to +# Codecov, whose own gates are configured in codecov.yml. + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +env: + # Minimum acceptable line coverage (percent). Raise as coverage improves. + COVERAGE_THRESHOLD: 70 + +concurrency: + group: coverage-${{ github.ref }} + cancel-in-progress: true + +jobs: + coverage: + name: Enforce coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- + + - name: Install cargo-tarpaulin + run: cargo install cargo-tarpaulin --locked + + - name: Run coverage with enforcement + run: | + cargo tarpaulin --workspace \ + --out Xml --out Lcov --output-dir coverage \ + --fail-under ${{ env.COVERAGE_THRESHOLD }} + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + files: ./coverage/cobertura.xml + fail_ci_if_error: true + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + - name: Coverage summary + if: always() + run: | + echo "## Coverage" >> "$GITHUB_STEP_SUMMARY" + echo "Enforced minimum: **${{ env.COVERAGE_THRESHOLD }}%** line coverage." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/dependency-scan.yml b/.github/workflows/dependency-scan.yml new file mode 100644 index 0000000..4bb2e31 --- /dev/null +++ b/.github/workflows/dependency-scan.yml @@ -0,0 +1,61 @@ +name: Dependency Scan + +# Dependency vulnerability scanning (#554). +# +# Scans the Cargo dependency tree for known vulnerabilities (RustSec) and +# yanked crates on every change to the lockfile/manifests, on a weekly +# schedule, and on demand. Complements the broader policy checks in +# security.yml. + +on: + push: + branches: [main] + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + - 'deny.toml' + - '.github/workflows/dependency-scan.yml' + pull_request: + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + - 'deny.toml' + - '.github/workflows/dependency-scan.yml' + schedule: + # Daily so freshly disclosed advisories are caught quickly. + - cron: '0 5 * * *' + workflow_dispatch: + +permissions: + contents: read + issues: write + +concurrency: + group: dependency-scan-${{ github.ref }} + cancel-in-progress: true + +jobs: + # ---------------------------------------------------------------------- + # cargo-audit against the RustSec advisory database. + # ---------------------------------------------------------------------- + cargo-audit: + name: cargo-audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Audit dependencies + uses: rustsec/audit-check@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + # ---------------------------------------------------------------------- + # cargo-deny advisories — second source of truth, fails the PR directly. + # ---------------------------------------------------------------------- + cargo-deny-advisories: + name: cargo-deny advisories + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v2 + with: + command: check advisories diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml new file mode 100644 index 0000000..94feec4 --- /dev/null +++ b/.github/workflows/mutation.yml @@ -0,0 +1,69 @@ +name: Mutation Testing + +# Mutation testing in CI/CD (#556, builds on #378). +# +# cargo-mutants injects small changes ("mutants") into the contracts and +# checks the test suite catches them. Configuration is in .cargo-mutants.toml. +# +# Two modes: +# * Pull requests — fast, only mutates lines changed in the PR diff. +# * Schedule/manual — full run over both contracts, uploaded as an artifact. + +on: + pull_request: + schedule: + # Weekly full sweep. + - cron: '0 4 * * 1' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: mutation-${{ github.ref }} + cancel-in-progress: true + +jobs: + mutants: + name: cargo-mutants + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # Need the base ref to compute the PR diff for incremental runs. + fetch-depth: 0 + + - uses: dtolnay/rust-toolchain@stable + + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-mutants-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- + + - name: Install cargo-mutants + run: cargo install cargo-mutants --locked + + # Incremental run on PRs: only mutate lines touched by this PR. + - name: Mutation test (PR diff) + if: github.event_name == 'pull_request' + run: | + git diff origin/${{ github.base_ref }}..HEAD > pr.diff + cargo mutants --no-shuffle -p ip_registry -p atomic_swap \ + --in-diff pr.diff + + # Full run on schedule / manual dispatch. + - name: Mutation test (full) + if: github.event_name != 'pull_request' + run: cargo mutants --no-shuffle -p ip_registry -p atomic_swap + + - name: Upload mutation report + if: always() + uses: actions/upload-artifact@v4 + with: + name: mutants-report + path: mutants.out/ + if-no-files-found: ignore diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..0f32af0 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,84 @@ +name: Security Scan + +# Security scanning for the AtomicIP contracts (#553). +# +# Combines several complementary scanners: +# * cargo-deny — supply-chain policy (bans, licenses, sources, advisories) +# * gitleaks — secret detection across the repo history +# * clippy — static analysis gate with warnings treated as errors +# +# Dedicated dependency vulnerability scanning lives in dependency-scan.yml (#554). + +on: + push: + branches: [main] + pull_request: + schedule: + # Re-run weekly so newly disclosed issues surface even without a push. + - cron: '0 6 * * 1' + workflow_dispatch: + +permissions: + contents: read + security-events: write + +concurrency: + group: security-${{ github.ref }} + cancel-in-progress: true + +jobs: + # ---------------------------------------------------------------------- + # Supply-chain policy: licenses, banned crates, sources, advisories. + # ---------------------------------------------------------------------- + cargo-deny: + name: cargo-deny (${{ matrix.checks }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + checks: + - advisories + - bans licenses sources + steps: + - uses: actions/checkout@v4 + - name: Run cargo-deny + uses: EmbarkStudios/cargo-deny-action@v2 + with: + command: check ${{ matrix.checks }} + + # ---------------------------------------------------------------------- + # Secret scanning: catch committed credentials / keys. + # ---------------------------------------------------------------------- + secret-scan: + name: Secret scan (gitleaks) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Run gitleaks + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # ---------------------------------------------------------------------- + # Static analysis: clippy as a security gate (denies all warnings). + # ---------------------------------------------------------------------- + static-analysis: + name: Static analysis (clippy) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-security-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- + - name: Clippy (deny warnings) + run: cargo clippy --workspace --all-targets -- -D warnings diff --git a/README.md b/README.md index 4c8fa60..6da0aef 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,10 @@ Latest testnet deployment addresses are published in GitHub Actions deployment s - [Threat Model & Security](docs/threat-model.md) - [Integration Guide for Wallet Providers](docs/integration-guide.md) - [Security Policy](SECURITY.md) +- [Security Scanning (CI/CD)](docs/security-scanning.md) +- [Dependency Vulnerability Scanning](docs/dependency-scanning.md) +- [Code Coverage Enforcement](docs/code-coverage.md) +- [Mutation Testing](docs/mutation-testing.md) - [Roadmap](docs/roadmap.md) ## 📦 Release Notes and Changelog diff --git a/SECURITY.md b/SECURITY.md index ffe5eca..97859fa 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -96,6 +96,21 @@ When reporting a vulnerability, please include: - 🔄 Time-locked commitments - 🔄 Partial disclosure proofs +## Automated Security Scanning + +Every push and pull request is scanned automatically in CI/CD: + +- **Security scanning** — supply-chain policy (cargo-deny), secret detection + (gitleaks), and static analysis. See [Security Scanning](docs/security-scanning.md). +- **Dependency vulnerability scanning** — cargo-audit + cargo-deny against the + RustSec advisory database, plus Dependabot. See [Dependency Scanning](docs/dependency-scanning.md). +- **Code coverage enforcement** — a minimum coverage threshold is enforced in + CI. See [Code Coverage](docs/code-coverage.md). +- **Mutation testing** — verifies the test suite catches logic errors. See + [Mutation Testing](docs/mutation-testing.md). + +Run all gates locally with `./scripts/security-checks.sh`. + ## Security Audits ### Audit Status diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..41391bd --- /dev/null +++ b/codecov.yml @@ -0,0 +1,29 @@ +# Codecov configuration (#555) +# +# Server-side coverage gates layered on top of the hard --fail-under check +# enforced locally in .github/workflows/coverage.yml. +coverage: + precision: 2 + round: down + range: "70...100" + status: + project: + default: + # Overall project coverage must stay at or above 70%. + target: 70% + threshold: 1% + patch: + default: + # New / changed lines in a PR must be at least 80% covered. + target: 80% + threshold: 1% + +comment: + layout: "reach, diff, flags, files" + behavior: default + require_changes: false + +ignore: + - "**/tests/**" + - "**/*_tests.rs" + - "fuzz/**" diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..38eeac3 --- /dev/null +++ b/deny.toml @@ -0,0 +1,82 @@ +# cargo-deny configuration (#553, #554) +# +# Drives the security and dependency scanning in +# .github/workflows/security.yml and .github/workflows/dependency-scan.yml. +# +# Run locally: +# cargo install cargo-deny --locked +# cargo deny check + +[graph] +# Only check the targets we actually ship / test on. +targets = [ + { triple = "wasm32-unknown-unknown" }, + { triple = "x86_64-unknown-linux-gnu" }, +] +all-features = true + +[output] +feature-depth = 1 + +# --------------------------------------------------------------------------- +# Security advisories (RustSec) — see #554 +# --------------------------------------------------------------------------- +[advisories] +db-urls = ["https://github.com/rustsec/advisory-db"] +# Fail the build on any vulnerability or unmaintained crate that has not been +# explicitly triaged below. +yanked = "deny" +ignore = [ + # Add advisory IDs here once triaged, e.g.: + # "RUSTSEC-2023-0001", +] + +# --------------------------------------------------------------------------- +# License policy +# --------------------------------------------------------------------------- +[licenses] +# Permissive licenses we accept across the dependency tree. +allow = [ + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "Unicode-DFS-2016", + "Unicode-3.0", + "Zlib", + "CC0-1.0", + "MPL-2.0", +] +confidence-threshold = 0.8 +# Workspace crates have no published license; skip them so the policy only +# governs third-party dependencies. +[[licenses.exceptions]] +name = "ip_registry" +allow = ["MIT", "Apache-2.0"] +[[licenses.exceptions]] +name = "atomic_swap" +allow = ["MIT", "Apache-2.0"] + +# --------------------------------------------------------------------------- +# Banned / duplicate dependencies +# --------------------------------------------------------------------------- +[bans] +multiple-versions = "warn" +wildcard-dependencies = "warn" +# Crates that must never enter the dependency tree. +deny = [ + # { name = "openssl", reason = "use rustls" }, +] +skip = [] +skip-tree = [] + +# --------------------------------------------------------------------------- +# Source / registry policy — only allow crates.io and trusted git sources +# --------------------------------------------------------------------------- +[sources] +unknown-registry = "deny" +unknown-git = "deny" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +allow-git = [] diff --git a/docs/code-coverage.md b/docs/code-coverage.md new file mode 100644 index 0000000..d2ee341 --- /dev/null +++ b/docs/code-coverage.md @@ -0,0 +1,54 @@ +# Code Coverage Enforcement (#555) + +Test coverage is measured with [cargo-tarpaulin](https://github.com/xd009642/tarpaulin) +and **enforced** in CI: a build fails if coverage drops below the threshold. + +## Workflow + +`.github/workflows/coverage.yml` runs on every push to `main`, every pull +request, and on manual dispatch. + +It runs tarpaulin over the workspace and fails the build via `--fail-under` +when line coverage falls below the threshold: + +```bash +cargo tarpaulin --workspace \ + --out Xml --out Lcov --output-dir coverage \ + --fail-under 70 +``` + +The threshold is set by the `COVERAGE_THRESHOLD` env var in the workflow +(currently **70%**). Raise it as coverage improves — never lower it to make a +build pass. + +## Codecov gates + +Results are uploaded to Codecov, configured in [`codecov.yml`](../codecov.yml): + +| Gate | Target | Meaning | +|-----------------|--------|------------------------------------------------| +| `project` | 70% | Overall coverage must stay at/above 70% | +| `patch` | 80% | New/changed lines in a PR must be ≥80% covered | + +The `patch` gate is the important one for day-to-day work: it ensures new code +arrives with tests, even if overall coverage is still climbing. + +Test files and fuzz targets are excluded from the coverage denominator (see the +`ignore` list in `codecov.yml`). + +## Running locally + +```bash +cargo install cargo-tarpaulin --locked +cargo tarpaulin --workspace --out Html --output-dir coverage +# open coverage/tarpaulin-report.html + +# Reproduce the CI gate: +cargo tarpaulin --workspace --fail-under 70 +``` + +## Raising coverage + +If the gate fails, the report lists uncovered lines. Add tests targeting those +paths — prioritise state-mutating contract entry points and error branches, +which are also tracked by the [Security Audit Checklist](security-audit-checklist.md). diff --git a/docs/dependency-scanning.md b/docs/dependency-scanning.md new file mode 100644 index 0000000..1bfd9b6 --- /dev/null +++ b/docs/dependency-scanning.md @@ -0,0 +1,55 @@ +# Dependency Vulnerability Scanning (#554) + +The Cargo dependency tree is scanned continuously for known vulnerabilities +(RustSec advisory database) and yanked crates, and kept current with +Dependabot. + +See also: [Security Scanning](security-scanning.md) · +[Security Policy](../SECURITY.md) + +## Workflow + +`.github/workflows/dependency-scan.yml` runs: + +- on push to `main` and on PRs that touch `Cargo.toml`, `Cargo.lock`, or + `deny.toml` +- **daily** (05:00 UTC) so freshly disclosed advisories are caught quickly +- on manual dispatch + +| Job | Tool | Notes | +|-------------------------|------------|------------------------------------------------| +| `cargo-audit` | cargo-audit| RustSec advisories; opens an issue on schedule | +| `cargo-deny-advisories` | cargo-deny | Second source of truth; fails the PR directly | + +## Running locally + +```bash +# cargo-audit +cargo install cargo-audit --locked +cargo audit + +# cargo-deny (uses deny.toml) +cargo install cargo-deny --locked +cargo deny check advisories +``` + +## Handling a reported vulnerability + +1. **Upgrade** the affected crate — `cargo update -p ` — to a patched + version. This resolves the majority of advisories. +2. If no fix exists, evaluate whether the vulnerable code path is reachable + from the contracts. Document the assessment. +3. As a last resort, add the advisory ID to the `[advisories].ignore` list in + [`deny.toml`](../deny.toml) with a comment explaining the risk acceptance + and a tracking link. + +## Dependabot + +[`.github/dependabot.yml`](../.github/dependabot.yml) opens weekly pull +requests (Mondays) for: + +- the Rust workspace (`/`) and the standalone `api-server` crate +- GitHub Actions versions used in the workflows + +Dependabot upgrades are the front line of vulnerability management: staying +current means most advisories are already fixed by the time they are disclosed. diff --git a/docs/mutation-testing.md b/docs/mutation-testing.md index 92da0ca..8296a58 100644 --- a/docs/mutation-testing.md +++ b/docs/mutation-testing.md @@ -62,3 +62,20 @@ Mutation testing was run against the current codebase. All mutants in the core validation paths (`require_non_zero_commitment`, `require_unique_commitment`, `require_positive_price`, status transition assignments) are killed by the `mutation_tests` module. + +## CI/CD Integration (#556) + +Mutation testing runs automatically via `.github/workflows/mutation.yml`: + +| Trigger | Scope | +|----------------------|------------------------------------| +| Pull request | Only lines changed in the PR diff (`--in-diff`) — fast | +| Weekly schedule (Mon)| Full sweep over both contracts | +| Manual dispatch | Full sweep | + +The PR-scoped run keeps feedback fast while ensuring new/changed logic is +covered by mutation-killing tests. The full report is uploaded as the +`mutants-report` build artifact (`mutants.out/`). + +If a PR introduces a **surviving** mutant, add a test (typically in +`src/mutation_tests.rs`) that fails for that mutation, then re-run. diff --git a/docs/security-scanning.md b/docs/security-scanning.md new file mode 100644 index 0000000..50eee14 --- /dev/null +++ b/docs/security-scanning.md @@ -0,0 +1,53 @@ +# Security Scanning (#553) + +Automated security scanning runs in CI/CD to catch vulnerable dependencies, +banned licenses, leaked secrets, and risky code before it reaches `main`. + +See also: [Dependency Scanning](dependency-scanning.md) · +[Security Audit Checklist](security-audit-checklist.md) · +[Security Policy](../SECURITY.md) + +## Workflow + +`.github/workflows/security.yml` runs on every push to `main`, every pull +request, weekly (Monday 06:00 UTC), and on manual dispatch. + +| Job | Tool | What it checks | +|-------------------|---------------------|------------------------------------------------------| +| `cargo-deny` | cargo-deny | Advisories, banned crates, license policy, sources | +| `secret-scan` | gitleaks | Committed secrets/keys across full history | +| `static-analysis` | clippy `-D warnings`| Lint/static-analysis gate, warnings fail the build | + +## cargo-deny + +Policy lives in [`deny.toml`](../deny.toml) at the repo root. It enforces: + +- **advisories** — RustSec vulnerability and unmaintained-crate database +- **bans** — disallowed crates and duplicate-version warnings +- **licenses** — only the permissive licenses in the `allow` list +- **sources** — crates may only come from crates.io (no unknown registries/git) + +Run locally before pushing: + +```bash +cargo install cargo-deny --locked +cargo deny check # all checks +cargo deny check advisories # just vulnerabilities +cargo deny check bans licenses sources +``` + +To accept a specific advisory or license, add it to the relevant `ignore` / +`allow` / `exceptions` section in `deny.toml` with a comment explaining why. + +## Secret scanning + +`gitleaks` scans the full git history for credentials. If it flags a real +secret: rotate the credential immediately, then scrub history. Never commit +real keys — use the patterns in [`.env.example`](../.env.example) and GitHub +Actions secrets instead. + +## Triage + +A failing security job blocks merge. Investigate the report, then either fix +the issue (upgrade the dependency, remove the secret) or, for false positives, +record an explicit exception in `deny.toml` with justification. diff --git a/scripts/security-checks.sh b/scripts/security-checks.sh new file mode 100755 index 0000000..a76b02a --- /dev/null +++ b/scripts/security-checks.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# security-checks.sh +# Run the CI security / quality gates locally, mirroring the GitHub workflows: +# security.yml (#553), dependency-scan.yml (#554), +# coverage.yml (#555), mutation.yml (#556). +# +# Usage: +# ./scripts/security-checks.sh # run all checks +# ./scripts/security-checks.sh audit # run a single check +# COVERAGE_THRESHOLD=80 ./scripts/security-checks.sh coverage +# +# Checks: deny | audit | coverage | mutants | all (default) +set -euo pipefail +source "$HOME/.cargo/env" 2>/dev/null || true + +COVERAGE_THRESHOLD="${COVERAGE_THRESHOLD:-70}" +CHECK="${1:-all}" + +have() { command -v "$1" >/dev/null 2>&1; } + +ensure() { + # ensure + if ! have "$1"; then + echo ">> installing $2 ..." + cargo install "$2" --locked + fi +} + +run_deny() { + echo "== cargo-deny (advisories, bans, licenses, sources) ==" + ensure cargo-deny cargo-deny + cargo deny check +} + +run_audit() { + echo "== cargo-audit (dependency vulnerabilities) ==" + ensure cargo-audit cargo-audit + cargo audit +} + +run_coverage() { + echo "== cargo-tarpaulin (enforce >= ${COVERAGE_THRESHOLD}%) ==" + ensure cargo-tarpaulin cargo-tarpaulin + cargo tarpaulin --workspace --out Xml --output-dir coverage \ + --fail-under "${COVERAGE_THRESHOLD}" +} + +run_mutants() { + echo "== cargo-mutants (mutation testing) ==" + ensure cargo-mutants cargo-mutants + cargo mutants --no-shuffle -p ip_registry -p atomic_swap +} + +case "$CHECK" in + deny) run_deny ;; + audit) run_audit ;; + coverage) run_coverage ;; + mutants) run_mutants ;; + all) + run_deny + run_audit + run_coverage + run_mutants + ;; + *) + echo "Unknown check: $CHECK" >&2 + echo "Valid: deny | audit | coverage | mutants | all" >&2 + exit 2 + ;; +esac + +echo "Security checks complete."