zeroclaw/.github/workflows/ci-cd-security.yml
2026-03-05 10:24:39 -05:00

297 lines
11 KiB
YAML

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