name: CI/CD with Security Hardening # Hard rule (branch + cadence policy): # 1) Contributors branch from `dev` and open PRs into `dev`. # 2) PRs into `main` are promotion PRs from `dev` (or explicit hotfix override). # 3) Full CI/CD runs on merge/direct push to `main` and manual dispatch only. # 3a) Main/manual build triggers are restricted to maintainers: # `theonlyhennygod`, `jordanthejet`. # 4) release published: run publish path on every release. # Cost policy: no daily auto-release and no heavy PR-triggered release pipeline. on: workflow_dispatch: release: types: [published] concurrency: group: ci-cd-security-${{ github.event.pull_request.number || github.ref || github.run_id }} cancel-in-progress: true permissions: contents: read env: GIT_CONFIG_COUNT: "1" GIT_CONFIG_KEY_0: core.hooksPath GIT_CONFIG_VALUE_0: /dev/null CARGO_TERM_COLOR: always jobs: authorize-main-build: name: Access and Execution Gate runs-on: [self-hosted, Linux, X64, light, cpu40] outputs: run_pipeline: ${{ steps.gate.outputs.run_pipeline }} steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 1 - name: Enforce actor policy and skip rules id: gate shell: bash run: | set -euo pipefail actor="${GITHUB_ACTOR}" actor_lc="$(echo "${actor}" | tr '[:upper:]' '[:lower:]')" event="${GITHUB_EVENT_NAME}" allowed_humans_lc="theonlyhennygod,jordanthejet" allowed_bot="github-actions[bot]" run_pipeline="true" if [[ "${event}" == "push" ]]; then commit_msg="$(git log -1 --pretty=%B | tr -d '\r')" if [[ "${commit_msg}" == *"[skip ci]"* ]]; then run_pipeline="false" echo "Skipping heavy pipeline because commit message includes [skip ci]." fi if [[ "${run_pipeline}" == "true" && ",${allowed_humans_lc}," != *",${actor_lc},"* ]]; then echo "::error::Only maintainer actors (${allowed_humans_lc}) can trigger main build runs. Actor: ${actor}" exit 1 fi elif [[ "${event}" == "workflow_dispatch" ]]; then if [[ ",${allowed_humans_lc}," != *",${actor_lc},"* ]]; then echo "::error::Only maintainer actors (${allowed_humans_lc}) can run manual CI/CD dispatches. Actor: ${actor}" exit 1 fi elif [[ "${event}" == "release" ]]; then if [[ ",${allowed_humans_lc}," != *",${actor_lc},"* && "${actor}" != "${allowed_bot}" ]]; then echo "::error::Only maintainer actors (${allowed_humans_lc}) or ${allowed_bot} can trigger release build lanes. Actor: ${actor}" exit 1 fi fi echo "run_pipeline=${run_pipeline}" >> "$GITHUB_OUTPUT" build-and-test: needs: authorize-main-build if: needs.authorize-main-build.outputs.run_pipeline == 'true' runs-on: [self-hosted, Linux, X64, blacksmith-2vcpu-ubuntu-2404] timeout-minutes: 90 steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Ensure C toolchain shell: bash run: bash ./scripts/ci/ensure_c_toolchain.sh - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: 1.92.0 components: clippy, rustfmt - name: Ensure C toolchain for Rust builds shell: bash run: ./scripts/ci/ensure_cc.sh - name: Cache Cargo dependencies uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v3 with: prefix-key: ci-cd-security-build cache-bin: false - name: Build shell: bash run: cargo build --locked --verbose --all-features - name: Run tests shell: bash run: cargo test --locked --verbose --all-features - name: Run benchmarks shell: bash run: cargo bench --locked --verbose - name: Lint with Clippy shell: bash run: cargo clippy --locked --all-targets --all-features -- -D warnings - name: Check formatting shell: bash run: cargo fmt -- --check security-scans: runs-on: [self-hosted, Linux, X64, blacksmith-2vcpu-ubuntu-2404] timeout-minutes: 60 needs: build-and-test permissions: contents: read security-events: write steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Ensure C toolchain shell: bash run: bash ./scripts/ci/ensure_c_toolchain.sh - name: Install Rust toolchain uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: 1.92.0 - name: Ensure C toolchain for Rust builds shell: bash run: ./scripts/ci/ensure_cc.sh - name: Cache Cargo dependencies uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v3 with: prefix-key: ci-cd-security-security cache-bin: false - name: Install cargo-audit shell: bash run: cargo install cargo-audit --locked --features=fix - name: Install cargo-deny shell: bash run: cargo install cargo-deny --locked - name: Dependency vulnerability audit shell: bash run: cargo audit --deny warnings - name: Dependency license and security check shell: bash run: cargo deny check - name: Install gitleaks shell: bash run: | set -euo pipefail bin_dir="${RUNNER_TEMP}/bin" mkdir -p "${bin_dir}" bash ./scripts/ci/install_gitleaks.sh "${bin_dir}" echo "${bin_dir}" >> "$GITHUB_PATH" - name: Scan for secrets shell: bash run: gitleaks detect --source=. --verbose --config=.gitleaks.toml - name: Static analysis with Semgrep uses: semgrep/semgrep-action@713efdd345f3035192eaa63f56867b88e63e4e5d # v1 with: config: auto fuzz-testing: runs-on: [self-hosted, Linux, X64, blacksmith-2vcpu-ubuntu-2404] timeout-minutes: 90 needs: build-and-test strategy: fail-fast: false matrix: target: - fuzz_config_parse - fuzz_tool_params - fuzz_webhook_payload - fuzz_provider_response - fuzz_command_validation steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Ensure C toolchain shell: bash run: bash ./scripts/ci/ensure_c_toolchain.sh - name: Install Rust nightly uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable with: toolchain: nightly components: llvm-tools-preview - name: Cache Cargo dependencies uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v3 with: prefix-key: ci-cd-security-fuzz cache-bin: false - name: Run fuzz tests shell: bash run: | set -euo pipefail cargo install cargo-fuzz --locked cargo +nightly fuzz run ${{ matrix.target }} -- -max_total_time=300 -max_len=4096 container-build-and-scan: runs-on: [self-hosted, Linux, X64, blacksmith-2vcpu-ubuntu-2404] timeout-minutes: 45 needs: security-scans steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Set up Blacksmith Docker builder uses: useblacksmith/setup-docker-builder@ef12d5b165b596e3aa44ea8198d8fde563eab402 # v1 - name: Build Docker image uses: useblacksmith/build-push-action@30c71162f16ea2c27c3e21523255d209b8b538c1 # v2 with: context: . push: false load: true tags: ghcr.io/${{ github.repository }}:ci-security - name: Scan Docker image for vulnerabilities shell: bash run: | set -euo pipefail docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy:0.58.2 image \ --exit-code 1 \ --no-progress \ --severity HIGH,CRITICAL \ ghcr.io/${{ github.repository }}:ci-security publish: runs-on: [self-hosted, Linux, X64, blacksmith-2vcpu-ubuntu-2404] timeout-minutes: 60 if: github.event_name == 'release' needs: - build-and-test - security-scans - fuzz-testing - container-build-and-scan permissions: contents: read packages: write steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Set up Blacksmith Docker builder uses: useblacksmith/setup-docker-builder@ef12d5b165b596e3aa44ea8198d8fde563eab402 # v1 - name: Login to GHCR uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GHCR_TOKEN }} - name: Build and push Docker image uses: useblacksmith/build-push-action@30c71162f16ea2c27c3e21523255d209b8b538c1 # v2 with: context: . push: true tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }},ghcr.io/${{ github.repository }}:latest build-args: | ZEROCLAW_CARGO_ALL_FEATURES=true