From 91f90d382d293f335ac8dfd8098b7afeb05c8123 Mon Sep 17 00:00:00 2001 From: "const.koutsakis@aurecongroup.com" Date: Mon, 27 Apr 2026 02:36:44 +1000 Subject: [PATCH] chore: security workflow (gitleaks, pip-audit, npm audit, trivy) (#11) --- .github/branch-protection/develop.json | 6 +- .github/branch-protection/main.json | 6 +- .github/security/pip-audit-ignore.txt | 10 +++ .github/workflows/security.yml | 105 +++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 .github/security/pip-audit-ignore.txt create mode 100644 .github/workflows/security.yml diff --git a/.github/branch-protection/develop.json b/.github/branch-protection/develop.json index 25ac197..414c27b 100644 --- a/.github/branch-protection/develop.json +++ b/.github/branch-protection/develop.json @@ -10,7 +10,11 @@ "Pre-commit", "Branch-protection contexts sync", "Commit-type sync", - "Lint PR title (conventional commits)" + "Lint PR title (conventional commits)", + "Secret scan (gitleaks)", + "Python deps (pip-audit)", + "Frontend deps (npm audit)", + "Container image scan (trivy)" ] }, "enforce_admins": false, diff --git a/.github/branch-protection/main.json b/.github/branch-protection/main.json index 9d7db2c..f43a6d3 100644 --- a/.github/branch-protection/main.json +++ b/.github/branch-protection/main.json @@ -10,7 +10,11 @@ "Pre-commit", "Branch-protection contexts sync", "Commit-type sync", - "Lint PR title (conventional commits)" + "Lint PR title (conventional commits)", + "Secret scan (gitleaks)", + "Python deps (pip-audit)", + "Frontend deps (npm audit)", + "Container image scan (trivy)" ] }, "enforce_admins": false, diff --git a/.github/security/pip-audit-ignore.txt b/.github/security/pip-audit-ignore.txt new file mode 100644 index 0000000..57dc94a --- /dev/null +++ b/.github/security/pip-audit-ignore.txt @@ -0,0 +1,10 @@ +# pip-audit per-CVE ignore list. One CVE id per line. Lines starting with +# `#` are comments and are skipped. Add a justification + tracking issue +# (or a sunset date) above every entry so the next owner can audit it. +# +# Format: +# # CVE-XXXX-NNNN — short reason; tracking issue / fix ETA. +# CVE-XXXX-NNNN +# +# Currently empty for the harness scaffold — add entries as upstream +# advisories require. diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..bebb111 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,105 @@ +name: Security + +# Action SHAs are pinned, not floating tags. To bump: +# gh api repos///commits/ --jq .sha +# (use /commits/, NOT /git/refs/tags/ — annotated tags would +# return the tag-object SHA, which Actions can't resolve.) + +on: + push: + branches: [develop, main] + pull_request: + branches: [develop, main] + schedule: + # Weekly Monday 06:00 UTC — re-runs the suite against the latest + # vulnerability databases even when the repo is quiet. + - cron: "0 6 * * 1" + +permissions: + contents: read + +jobs: + gitleaks: + name: Secret scan (gitleaks) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 # full history so gitleaks can scan every commit + # Install and run the binary directly — the v2 action attempts to upload + # a SARIF artifact, which trips the account-wide artifact storage quota + # on this repo. Running the CLI keeps us to a single blocking step with + # no artifact output. + - name: Install gitleaks + run: | + VERSION=8.24.3 + curl -sSL "https://github.com/zricethezav/gitleaks/releases/download/v${VERSION}/gitleaks_${VERSION}_linux_x64.tar.gz" \ + | tar -xz -C /usr/local/bin gitleaks + gitleaks version + - name: Run gitleaks + run: gitleaks detect --source . --redact --verbose --no-banner --exit-code 1 + + pip-audit: + name: Python deps (pip-audit) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.14" + - run: uv sync --frozen --extra dev + - name: Audit production deps + # --strict fails on ANY surfaced CVE, not just HIGH/CRITICAL. We don't + # have a volume problem and any CVE in pinned deps is worth a + # conscious accept-or-upgrade decision. + # + # Per-CVE ignores live in .github/security/pip-audit-ignore.txt with + # a comment per ignore. xargs ferries them onto the command line so + # the policy stays in version control rather than buried in this YAML. + run: | + uv pip install pip-audit + IGNORES=$(grep -v '^\s*#' .github/security/pip-audit-ignore.txt 2>/dev/null \ + | grep -v '^\s*$' \ + | sed 's/^/--ignore-vuln /' \ + | tr '\n' ' ') + uv run pip-audit --strict --progress-spinner off \ + --vulnerability-service osv \ + $IGNORES + + npm-audit: + name: Frontend deps (npm audit) + runs-on: ubuntu-latest + # Skips cleanly until #21 lands frontend/package.json. + if: hashFiles('frontend/package-lock.json') != '' + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: "24" + cache: npm + cache-dependency-path: frontend/package-lock.json + - run: cd frontend && npm ci + # --audit-level=high — fail only on high/critical; moderate/low noted + # but not blocking. + - run: cd frontend && npm audit --audit-level=high + + trivy-image: + name: Container image scan (trivy) + runs-on: ubuntu-latest + # Blocking: any fixable HIGH/CRITICAL CVE in the built image fails the PR. + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - name: Build image + run: docker build -t harness-python-react:ci . + - name: Run Trivy vulnerability scanner + # Pinned to a SHA — never @master. A moving branch in a + # supply-chain workflow defeats the point of the scan. + uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 + with: + image-ref: harness-python-react:ci + format: table + severity: HIGH,CRITICAL + exit-code: "1" + ignore-unfixed: true + vuln-type: os,library