Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/branch-protection/develop.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 5 additions & 1 deletion .github/branch-protection/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions .github/security/pip-audit-ignore.txt
Original file line number Diff line number Diff line change
@@ -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.
105 changes: 105 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: Security

# Action SHAs are pinned, not floating tags. To bump:
# gh api repos/<owner>/<repo>/commits/<tag> --jq .sha
# (use /commits/<tag>, NOT /git/refs/tags/<tag> — 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
Loading