name: Security # Two gates that can actually fail, plus one report: # - pip-audit against requirements.txt: any known vulnerability in a runtime # dependency fails the job (dev-only tools are reported, not gated) # - bandit on src/: HIGH severity findings fail; medium/low are listed. # B104 (bind 0.0.0.0) is skipped: the service is meant to listen on all # interfaces behind Cloudflare/Caddy. # - pip-licenses report as an artifact (informational; the project is MIT # and its runtime deps are MIT/BSD/Apache/PSF) # The old file ran safety/bandit/semgrep with `|| true` and could not go red. on: schedule: - cron: "0 3 * * 1" # weekly, Monday 03:00 UTC workflow_dispatch: push: paths: - "requirements*.txt" - "pyproject.toml" - "uv.lock" - "src/**/*.py" - ".gitea/workflows/security.yml" pull_request: paths: - "requirements*.txt" - "pyproject.toml" - "src/**/*.py" env: PYTHON_VERSION: "3.11" jobs: dependencies: name: Dependency vulnerabilities runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install pip-audit run: | python -m pip install --upgrade pip --root-user-action=ignore pip install --root-user-action=ignore pip-audit - name: Runtime dependencies (gate) run: pip-audit -r requirements.txt --strict --desc on - name: Dev dependencies (report only) run: pip-audit -r requirements-dev.txt --desc on || echo "::warning::dev-only dependency advisories above" code: name: Static analysis runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install bandit run: | python -m pip install --upgrade pip --root-user-action=ignore pip install --root-user-action=ignore bandit - name: bandit (HIGH fails; medium/low listed) run: | bandit -r src/ -q --skip B104 -ll -ii || true bandit -r src/ -q --skip B104 --severity-level high --confidence-level medium licenses: name: License report runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: pip cache-dependency-path: requirements.txt - name: Install run: | python -m pip install --upgrade pip --root-user-action=ignore pip install --root-user-action=ignore -r requirements.txt pip-licenses - name: Report run: | pip-licenses --format=markdown --with-urls --output-file=licenses.md pip-licenses --format=json --output-file=licenses.json echo "Copyleft licenses among runtime deps (informational):" pip-licenses --format=plain | grep -iE 'GPL|AGPL|LGPL' || echo " none" - uses: actions/upload-artifact@v3 with: name: licenses-${{ github.run_number }} path: | licenses.md licenses.json