Files

116 lines
3.2 KiB
YAML

name: Quarterly Security Audit
on:
schedule:
# Quarterly cadence: Jan/Apr/Jul/Oct 1 at 09:00 UTC.
- cron: '0 9 1 1,4,7,10 *'
workflow_dispatch:
inputs:
zap_target_url:
description: 'Optional authorized URL for OWASP ZAP baseline scan'
required: false
default: ''
type: string
run_snyk:
description: 'Run Snyk when SNYK_TOKEN is configured'
required: false
default: false
type: boolean
permissions:
contents: read
jobs:
quarterly-security-audit:
name: Quarterly security audit
runs-on: ubuntu-latest
timeout-minutes: 30
env:
PIP_DISABLE_PIP_VERSION_CHECK: '1'
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
ZAP_TARGET_URL: ${{ github.event.inputs.zap_target_url }}
RUN_SNYK: ${{ github.event.inputs.run_snyk }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
cache: pip
cache-dependency-path: requirements-dev.txt
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
- name: Generate sanitized audit template
run: |
python scripts/security_audit.py --output security-audit-template.md --force
- name: Supply-chain dependency gate
run: |
python scripts/check_supply_chain.py --skip-install-trees
- name: Install Python audit dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements-dev.txt
- name: Outbound safety check
run: |
python scripts/check_outbound_safety.py
- name: Focused Python security gate
run: |
python scripts/focused_gate.py --fast
- name: Semgrep SAST
continue-on-error: true
run: |
python -m pip install semgrep
semgrep scan --config p/python --config p/javascript --error --metrics=off .
- name: Snyk dependency scan
run: |
if [ "$RUN_SNYK" != "true" ]; then
echo "Skipping Snyk; workflow input run_snyk is false."
exit 0
fi
if [ -z "$SNYK_TOKEN" ]; then
echo "Skipping Snyk; SNYK_TOKEN is not configured."
exit 0
fi
npm install
npx snyk test --severity-threshold=high
- name: OWASP ZAP baseline scan
run: |
if [ -z "$ZAP_TARGET_URL" ]; then
echo "Skipping ZAP; no authorized zap_target_url was provided."
exit 0
fi
set +e
docker run -v "$PWD:/zap/wrk/:rw" -t ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py -t "$ZAP_TARGET_URL" -r zap-baseline.html
code=$?
if [ "$code" -eq 0 ] || [ "$code" -eq 2 ]; then
exit 0
fi
exit "$code"
- name: Upload sanitized audit artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: security-audit-artifacts
path: |
security-audit-template.md
zap-baseline.html
if-no-files-found: ignore
retention-days: 14