mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
`pnpm audit --prod` answers a question about the published advisory database, not about the diff. The same lockfile passes on Monday and fails on Tuesday because someone else published, and the branch that turns red changed nothing: seventeen of the forty-one Security & Quality failures in the last thirty days were advisory publications on branches that touched no dependency (GHSA-jqff-g426-hqxp, GHSA-fph4-wmhf-6fwf, GHSA-f65p-4m7j-42xc and GHSA-5jgf-p345-68v8 in one run alone). A gate that fails for a reason the author cannot act on is a gate people re-run instead of read. It keeps running on every push to `main` and once a day on a new schedule trigger, which is the cadence the question actually has. The other three jobs sit out the scheduled run. Nothing about what ships is loosened: the container scan still builds and scans the image on every push to `main`, and the daily Trivy workflow still scans the lockfile with dev dependencies included and the published image. Gate — the trigger set, read back from the parsed workflow: triggers: push, pull_request, schedule quality | if: github.event_name != 'schedule' dependency-audit | if: github.event_name != 'pull_request' secret-scan | if: github.event_name != 'schedule' docker-security | if: github.event_name == 'push' && github.ref == 'refs/heads/main'
165 lines
6.9 KiB
YAML
165 lines
6.9 KiB
YAML
name: Trivy Scheduled Scan
|
|
|
|
# Two scans that the per-change gates cannot do, on a daily cadence.
|
|
#
|
|
# What already exists and is NOT repeated here:
|
|
# - `security.yml` → `dependency-audit` runs `pnpm audit --prod` on every
|
|
# push to `main` and once a day, blocking on HIGH/CRITICAL.
|
|
# - `security.yml` → `docker-security` builds the image on a push to `main`
|
|
# and runs `trivy image` against it, blocking on HIGH/CRITICAL.
|
|
#
|
|
# Both are change-triggered, and both answer "is this change safe". Neither
|
|
# answers "is what we shipped last week still safe", because an advisory
|
|
# published today lands against an image nobody is about to rebuild. That is
|
|
# what the schedule is for. There is deliberately no push or pull_request
|
|
# trigger: adding one would run the same scan the `docker-security` job
|
|
# already ran, minutes later, on the same tree.
|
|
on:
|
|
schedule:
|
|
# Daily, 05:17 UTC.
|
|
- cron: "17 5 * * *"
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: trivy-scheduled
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
filesystem:
|
|
name: Lockfile (including dev dependencies)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
permissions:
|
|
contents: read
|
|
security-events: write
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
# `pnpm audit --prod` is the blocking gate and it is scoped to
|
|
# production dependencies on purpose: a dev-only advisory should not
|
|
# stop a release. The gap that scoping leaves is that "dev dependency"
|
|
# and "not in the image" are not the same statement.
|
|
#
|
|
# `pdfjs-dist` is the worked example. It sits in `devDependencies`,
|
|
# and `src/lib/documents/rasterize-pdf.ts` imports it at runtime, so
|
|
# Next's standalone output tracing copies it into the production
|
|
# image. `pnpm audit --prod` cannot see it. This scan can: without
|
|
# the flag Trivy reads 627 packages out of `pnpm-lock.yaml`, with it
|
|
# 1455, and `pdfjs-dist` is only in the second set.
|
|
#
|
|
# Trivy's own `--help` lists the flag as supported for npm, yarn and
|
|
# gradle. It works on `pnpm-lock.yaml` too; the help text is behind
|
|
# the parser. Verified against this repository before this workflow
|
|
# was written, and worth re-checking if the package count ever
|
|
# collapses back to the production figure.
|
|
#
|
|
# Report-only, and that is a deliberate choice rather than a soft
|
|
# one. The findings this widening surfaces are overwhelmingly
|
|
# transitive dev-tool advisories with fixes available upstream;
|
|
# failing the run on them from day one produces a red badge that gets
|
|
# muted within a week, and a muted signal is worse than none. The
|
|
# SARIF upload puts every finding in the repository's code-scanning
|
|
# alerts, which persist, notify, and can be triaged one at a time.
|
|
# The blocking answer to "does this ship" stays where it is, in
|
|
# `security.yml`.
|
|
- name: Trivy filesystem scan
|
|
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
|
env:
|
|
# No action input maps to `--include-dev-deps`; Trivy reads every
|
|
# flag from a `TRIVY_`-prefixed environment variable, so this is
|
|
# the documented way to reach it through the action.
|
|
TRIVY_INCLUDE_DEV_DEPS: "true"
|
|
with:
|
|
scan-type: fs
|
|
scan-ref: .
|
|
scanners: vuln
|
|
format: sarif
|
|
output: trivy-filesystem.sarif
|
|
severity: "CRITICAL,HIGH,MEDIUM"
|
|
limit-severities-for-sarif: true
|
|
exit-code: "0"
|
|
# Generated Prisma client. Nobody edits it, the fix for anything
|
|
# found in it is `pnpm prisma generate`, and it is ~9 MB.
|
|
skip-dirs: src/generated
|
|
|
|
- name: Upload results
|
|
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
|
with:
|
|
sarif_file: trivy-filesystem.sarif
|
|
category: trivy-filesystem
|
|
|
|
published-image:
|
|
name: Published image
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
permissions:
|
|
contents: read
|
|
packages: read
|
|
security-events: write
|
|
|
|
steps:
|
|
# The `docker-security` job scans an image it just built from the
|
|
# current `main`. This one scans the image operators actually pull,
|
|
# which is the one built from the last release tag and unchanged
|
|
# since. The two answer different questions and the gap between them
|
|
# widens every day a release stays out.
|
|
#
|
|
# Trivy resolves the multi-arch manifest to the runner's platform, so
|
|
# this covers `linux/amd64`. The arm64 manifest shares every
|
|
# application layer and differs only in native binaries.
|
|
- name: Log in to GitHub Container Registry
|
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
# `.trivyignore` lives at the repository root and Trivy reads it
|
|
# automatically, so the suppressions the blocking gate already
|
|
# carries apply here too and the two scans cannot disagree about
|
|
# what is accepted.
|
|
- name: Checkout for .trivyignore
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
# Fails on CRITICAL only. A critical advisory against the image
|
|
# self-hosters are running right now is worth a red badge and a
|
|
# notification; HIGH goes to code scanning with everything else,
|
|
# because the release pipeline already refuses to publish an image
|
|
# carrying one and anything that appears here appeared after the
|
|
# fact.
|
|
- name: Trivy published-image scan
|
|
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
|
with:
|
|
scan-type: image
|
|
image-ref: ghcr.io/mbombeck/healthlog:latest
|
|
scanners: vuln
|
|
format: sarif
|
|
output: trivy-published-image.sarif
|
|
severity: "CRITICAL,HIGH,MEDIUM"
|
|
limit-severities-for-sarif: true
|
|
exit-code: "0"
|
|
|
|
- name: Upload results
|
|
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
|
with:
|
|
sarif_file: trivy-published-image.sarif
|
|
category: trivy-published-image
|
|
|
|
# Separate table pass so the SARIF upload above always runs, even
|
|
# when this one fails the job. A scan whose failure suppresses its
|
|
# own report is a scan you have to re-run by hand to read.
|
|
- name: Fail on a critical finding in the published image
|
|
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
|
with:
|
|
scan-type: image
|
|
image-ref: ghcr.io/mbombeck/healthlog:latest
|
|
scanners: vuln
|
|
format: table
|
|
severity: "CRITICAL"
|
|
exit-code: "1"
|