6.9 KiB · text History b868907
name: audit
# Known vulnerabilities in what the forge is built from: every lockfile
# in the repository (mix.lock, the CLI's and the NIFs' Cargo.lock)
# checked against the OSV database (https://osv.dev), sorted by
# scripts/osv-audit: a finding with a fixed version fails the run, which
# is how it gets noticed — the CI page, the panel widget, `gg ci list`;
# one with no fix yet makes the job a warning (its `gitgud-conclusion`
# output), since there's nothing to do but wait. Nightly, since
# advisories land without anyone pushing, and by hand from the Actions
# page.
#
# On a pull request it compares the lockfiles the pull request changes
# with the same files on the base branch: it fails only on a
# vulnerability the pull request brings in, and says what it fixed and
# what's still there (scripts/osv-diff). A security update PR goes green
# for what it fixes, not red for what it couldn't.
#
# The Android app isn't covered: osv-scanner reads Gradle lockfiles,
# and the app has a version catalog (gradle/libs.versions.toml) only.
#
# `image` checks what's deployed rather than what it's built from: the
# published :latest, for Debian security updates it hasn't got. Those
# alone make it a warning (actionable soon, not an outage: what's
# running still runs). Debian leaves some CVEs unfixed in a stable
# release, and warning on those would mean warning forever. The fix is
# a newer base tag (the Dockerfile's RUNNER_DEBIAN_VERSION, which
# dependency-updates.yml bumps) or a rebuild. osv-scanner's view of the
# image, unfixed CVEs included, is printed after it for reading.
on:
  schedule:
    - cron: "17 6 * * *"
  workflow_dispatch:
  pull_request:

permissions:
  contents: read

# A newer push supersedes this run: on a branch or PR the run in
# progress is cancelled; on master it finishes, and only a run still
# waiting is replaced.
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}

jobs:
  osv:
    runs-on: self-hosted
    # `warning` when only unfixable findings remain (scripts/osv-audit).
    # GitHub and Forgejo read it as an ordinary output.
    outputs:
      gitgud-conclusion: ${{ steps.scan.outputs.conclusion }}
      # "2 to fix, 1 no fix": what the workflow's badge says, rather
      # than just "warning" (GitGud.Badges).
      gitgud-summary: ${{ steps.scan.outputs.summary }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      # Pinned, and checked against the release's published checksum
      # before it runs: a scanner is a strange thing to take on trust.
      # To move it on, change both together, from
      # https://github.com/google/osv-scanner/releases (…_SHA256SUMS).
      - name: Fetch osv-scanner
        env:
          OSV_VERSION: v2.6.0
          OSV_SHA256: ca69b3d3cd08f889a49dc0a383122f71cc528b83803671df5fd874d97485b108
        run: |
          curl -sSfL -o /tmp/osv-scanner \
            "https://github.com/google/osv-scanner/releases/download/${OSV_VERSION}/osv-scanner_linux_amd64"
          echo "${OSV_SHA256}  /tmp/osv-scanner" | sha256sum -c -
          chmod +x /tmp/osv-scanner

      # Exit 1 when anything has a fixed version, so the run fails; the
      # tables name each package, its version, its advisories, and the
      # version that fixes it or that none does.
      - name: Scan the lockfiles
        id: scan
        if: github.event_name != 'pull_request'
        env:
          OSV_SCANNER: /tmp/osv-scanner
        run: python3 scripts/osv-audit --recursive .

      - name: Scan the lockfiles this pull request changes
        if: github.event_name == 'pull_request'
        env:
          BASE: ${{ github.base_ref }}
        run: |
          git fetch --quiet origin "$BASE"
          changed=$(git diff --name-only "origin/$BASE"...HEAD | grep -E '(^|/)(mix|Cargo)\.lock$' || true)
          if [ -z "$changed" ]; then
            echo "No lockfile changed."
            exit 0
          fi
          mkdir -p /tmp/base
          for f in $changed; do
            mkdir -p "/tmp/base/$(dirname "$f")"
            git show "origin/$BASE:$f" > "/tmp/base/$f" 2>/dev/null || true
          done
          # 0 and 1 (found some) are both answers; with nothing to scan, none.
          scan() {
            if [ $# -eq 0 ]; then echo '{}'; return; fi
            /tmp/osv-scanner scan source --format json $(printf -- '--lockfile %s ' "$@") || [ $? -eq 1 ]
          }
          (cd /tmp/base && scan $(for f in $changed; do [ -s "$f" ] && echo "$f"; done)) > /tmp/base.json
          scan $changed > /tmp/head.json
          python3 scripts/osv-diff /tmp/base.json /tmp/head.json

  image:
    if: github.event_name != 'pull_request'
    runs-on: self-hosted
    outputs:
      gitgud-conclusion: ${{ steps.pending.outputs.conclusion }}
      gitgud-summary: ${{ steps.pending.outputs.summary }}
    steps:
      # The job's own token reads its repository's image, as build.yml's
      # pushes it.
      - uses: docker/login-action@v3
        with:
          registry: ${{ vars.CI_REGISTRY }}
          username: gitgud-actions
          password: ${{ github.token }}

      # As root, since the image runs as nobody; apt only reads here.
      - name: Security updates the published image hasn't got
        id: pending
        env:
          IMAGE: ${{ vars.CI_REGISTRY }}/${{ github.repository }}:latest
        run: |
          docker pull --quiet "$IMAGE"
          pending=$(docker run --rm --user root --entrypoint sh "$IMAGE" -c \
            'apt-get update -qq >/dev/null && apt list --upgradable 2>/dev/null | grep -- -security' || true)
          if [ -n "$pending" ]; then
            count=$(echo "$pending" | wc -l)
            echo "$pending"
            echo "::warning::$count Debian security update(s) not in the published image: bump RUNNER_DEBIAN_VERSION, or rebuild."
            echo "conclusion=warning" >> "$GITHUB_OUTPUT"
            echo "summary=$count image updates" >> "$GITHUB_OUTPUT"
            exit 0
          fi
          echo "No Debian security updates pending."
          echo "conclusion=success" >> "$GITHUB_OUTPUT"

      - name: Fetch osv-scanner
        if: always()
        env:
          OSV_VERSION: v2.6.0
          OSV_SHA256: ca69b3d3cd08f889a49dc0a383122f71cc528b83803671df5fd874d97485b108
        run: |
          curl -sSfL -o /tmp/osv-scanner \
            "https://github.com/google/osv-scanner/releases/download/${OSV_VERSION}/osv-scanner_linux_amd64"
          echo "${OSV_SHA256}  /tmp/osv-scanner" | sha256sum -c -
          chmod +x /tmp/osv-scanner

      # For reading, not judging: most of what it lists Debian hasn't
      # fixed in this release.
      - name: Known vulnerabilities in the image (informational)
        if: always()
        env:
          IMAGE: ${{ vars.CI_REGISTRY }}/${{ github.repository }}:latest
        run: |
          docker save -o /tmp/image.tar "$IMAGE"
          /tmp/osv-scanner scan image --archive /tmp/image.tar || true