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
94 changes: 93 additions & 1 deletion .github/workflows/python-sbom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
#
# Features:
# - CycloneDX SBOM generation
# - Trivy vulnerability scanning
# - Trivy vulnerability scanning (gating, SBOM-ingest)
# - Grype vulnerability scanning (advisory parallel-run; see issue #152)
# - OSV-Scanner vulnerability scanning (fast keyless gate, SBOM-ingest)
# - License compliance checking
# - SARIF upload to GitHub Security tab
# ============================================================================
Expand Down Expand Up @@ -68,6 +70,11 @@ on:
required: false
type: string
default: '.grype.yaml'
run-osv:
description: 'Run OSV-Scanner against the SBOM artifact as a fast, keyless CI gate. No API key required; covers transitive dependencies.'
required: false
type: boolean
default: true

# Optional secrets - callers may pass these even if unused by this workflow
# This prevents startup_failure when callers pass extra secrets
Expand Down Expand Up @@ -447,6 +454,91 @@ jobs:
retention-days: 7
if-no-files-found: warn

# ============================================================================
# Job 2c: Scan Runtime Dependencies (OSV-Scanner - fast keyless gate)
# ============================================================================
# OSV-Scanner ingests the shared SBOM artifact produced by generate-sbom so
# both tools share one source of truth and no second resolver pass occurs.
# Benefits over Trivy/Grype for this slot: sub-second on typical Python
# dependency graphs, no API key required, and cross-database coverage (OSV,
# NVD, GitHub Advisory, PyPI Advisory) that catches transitives sometimes
# missed by Snyk free tier. Gating by default; opt out with run-osv: false.
#
# #CRITICAL: security-events: write is required for upload-sarif. This
# permission is inherited by the caller in a reusable workflow context.
# #VERIFY: confirm `osv-sbom-runtime-deps` category appears separately in
# Security > Code scanning alerts after the first run alongside Trivy and
# Grype categories.
# ============================================================================
scan-runtime-osv:
name: Scan Runtime Dependencies (OSV-Scanner)
needs: generate-sbom
if: inputs.run-osv && (needs.generate-sbom.outputs.state == 'uv-locked' || needs.generate-sbom.outputs.state == 'uv-no-lock')
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
security-events: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
with:
egress-policy: audit

- name: Checkout repository (for SARIF context)
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
sparse-checkout: |
.git

- name: Download SBOM artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sboms

# Primary gating scan: table format for readable log output.
# continue-on-error mirrors fail-on-vulnerabilities so the gate is
# consistent with Trivy's exit-code behavior.
# #ASSUME: osv-scanner exits 1 when vulnerabilities are found, 0 for
# clean, 127 for unexpected error. continue-on-error only suppresses
# non-zero exits; step-level failure is still visible in the log.
- name: OSV-Scanner SBOM scan (runtime)
uses: google/osv-scanner-action/osv-scanner-action@9a498708959aeaef5ef730655706c5a1df1edbc2 # v2.3.8
continue-on-error: ${{ !inputs.fail-on-vulnerabilities }}
with:
scan-args: |-
--sbom=sbom-runtime.json

# Secondary SARIF run: always non-gating; output written to file for
# Security tab upload. Runs even if the gating step above failed.
- name: OSV-Scanner SBOM scan (runtime - SARIF report)
id: osv-sarif
if: always()
uses: google/osv-scanner-action/osv-scanner-action@9a498708959aeaef5ef730655706c5a1df1edbc2 # v2.3.8
continue-on-error: true
with:
scan-args: |-
--sbom=sbom-runtime.json
--format=sarif
--output=osv-sbom-runtime.sarif

- name: Upload OSV-Scanner results to GitHub Security tab
if: always()
uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
continue-on-error: true
with:
sarif_file: osv-sbom-runtime.sarif
category: osv-sbom-runtime-deps

- name: Upload OSV-Scanner SARIF artifact
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: osv-sbom-sarif
path: osv-sbom-runtime.sarif
retention-days: ${{ inputs.artifact-retention-days }}
if-no-files-found: warn

# ============================================================================
# Job 3: License Compliance Check
# ============================================================================
Expand Down
71 changes: 71 additions & 0 deletions .github/workflows/sbom-nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# ============================================================================
# Org-Level Nightly SBOM Scan
# ============================================================================
# Runs python-sbom.yml on a nightly schedule so CVE database updates that
# land between PR builds are caught automatically. The org self-test skips
# cleanly here (no pyproject.toml in .github), but it proves the trigger
# machinery works and keeps the workflow "live" in Actions.
#
# Downstream pattern: add a schedule trigger alongside your existing
# pull_request trigger in your own python-sbom.yml caller workflow:
#
# on:
# pull_request:
# schedule:
# - cron: '0 2 * * *'
# jobs:
# sbom:
# uses: ByronWilliamsCPA/.github/.github/workflows/python-sbom.yml@main
# with:
# python-version: '3.12'
# run-osv: true
#
# The schedule trigger causes GitHub to pull a fresh CVE database snapshot
# on every run, so vulnerabilities disclosed after your last PR build are
# caught within 24 hours without waiting for the next code change.
# ============================================================================

name: SBOM Nightly Scan

on:
schedule:
# 02:17 UTC daily - offset from the hour to reduce Actions queue contention
- cron: '17 2 * * *'
workflow_dispatch:
inputs:
python-version:
description: 'Python version to scan'
required: false
default: '3.12'
fail-on-vulnerabilities:
description: 'Fail on CRITICAL/HIGH vulnerabilities (true/false)'
required: false
default: 'true'
run-osv:
description: 'Run OSV-Scanner gate (true/false)'
required: false
default: 'true'

# #CRITICAL: permissions are set to empty at the workflow level; the
# reusable workflow (python-sbom.yml) grants its own per-job permissions.
# Reusable workflows run with the permissions of the CALLEE, not the caller,
# when the callee defines its own permissions blocks. This means this
# workflow does not need elevated permissions even though the child jobs
# write to security-events.
# #VERIFY: confirm that upload-sarif succeeds in the first nightly run by
# checking the Security > Code scanning alerts tab for nightly-triggered
# entries distinct from PR-triggered ones (different ref/sha on the alert).
permissions: {}

jobs:
sbom:
# #ASSUME: when triggered via schedule, inputs.* is empty; the || fallbacks
# below supply the production defaults. workflow_dispatch overrides them.
uses: ./.github/workflows/python-sbom.yml
with:
python-version: ${{ inputs.python-version || '3.12' }}
# #EDGE: workflow_dispatch inputs are always strings; cast to boolean via
# comparison. When triggered by schedule, both expressions resolve to true
# via the || fallback before the == comparison.
fail-on-vulnerabilities: ${{ (inputs.fail-on-vulnerabilities || 'true') == 'true' }}
run-osv: ${{ (inputs.run-osv || 'true') == 'true' }}
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@ are no numbered releases.

### Added

- `python-sbom.yml`: OSV-Scanner runs alongside Trivy and Grype as a third
SBOM-ingest sibling job (`scan-runtime-osv`) for fast keyless CVE coverage
per issue #152 follow-up. Adds new optional input `run-osv` (default `true`)
that lets callers opt out. The job ingests the same `sbom-runtime.json`
artifact that Trivy and Grype use, so no second resolver pass occurs.
SARIF is uploaded under category `osv-sbom-runtime-deps`, surfacing
alongside Trivy and Grype categories on the Security > Code scanning tab.
Reuses the OSV-Scanner action SHA already pinned in
`python-security-analysis.yml` (no new third-party surface). Gating mirrors
Trivy: when `fail-on-vulnerabilities: true` (the default), an OSV finding
fails the workflow. Caller surface is backwards-compatible; one new check
entry appears in PR Checks UI for repos that keep the default.
- `sbom-nightly.yml`: org-level nightly workflow that calls `python-sbom.yml`
on a daily 02:17 UTC schedule plus `workflow_dispatch`. Skips cleanly in
`.github` (no `pyproject.toml`); serves as the reference pattern for
downstream repos that want nightly CVE database coverage between PR builds.
Documents the schedule-trigger snippet downstream repos can paste into
their own `python-sbom.yml` caller workflows so vulnerabilities disclosed
after the last PR build are caught within 24 hours.

- `python-sbom.yml`: Grype scanning runs alongside Trivy as a non-gating sibling
job (`scan-runtime-grype`) for a 30-day parity window per issue #152. Adds new
optional input `grype-config-path` (default `.grype.yaml`) and a
Expand Down
9 changes: 7 additions & 2 deletions workflow-templates/python-sbom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
#
# This workflow:
# - Generates CycloneDX SBOM
# - Scans for vulnerabilities with Trivy
# - Scans for vulnerabilities with Trivy (gating), Grype (advisory parallel-run),
# and OSV-Scanner (gating, fast keyless SBOM-ingest gate)
# - Checks license compliance
# - Uploads SARIF to GitHub Security tab
# - Uploads SARIF to GitHub Security tab (3 categories: trivy, grype, osv-sbom-runtime-deps)
#
# Triggered by:
# - Push to main branch
# - Release creation
# - Weekly schedule
#
# Optional toggles (defaults shown):
# - run-osv: true # OSV-Scanner SBOM-ingest gate; set false to disable
# - grype-config-path: '.grype.yaml' # path to Grype config (CVE ignores etc.)

name: SBOM

Expand Down
Loading