Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a68f695
ci: add Trivy security scan to PR pipeline
yaroslavmokflmg Aug 3, 2026
c02de14
ci: harden trivy gate — no persisted git credentials, visible warning…
yaroslavmokflmg Aug 3, 2026
1a8b5cc
ci: make trivy gate non-blocking (allow fail) until devs have capacity
yaroslavmokflmg Aug 5, 2026
3b5cf43
ci: add Trivy security scan to PR pipeline
yaroslavmokflmg Aug 3, 2026
761f158
ci: harden trivy gate — no persisted git credentials, visible warning…
yaroslavmokflmg Aug 3, 2026
7071174
ci: make trivy gate non-blocking (allow fail) until devs have capacity
yaroslavmokflmg Aug 5, 2026
f2b4238
ci: trivy code+docker scan in one job, in-build image scan, per-scan …
yaroslavmokflmg Aug 5, 2026
a7bcb07
Merge branch 'security/trivy' of https://github.com/flamingo-stack/op…
yaroslavmokflmg Aug 5, 2026
89ce205
ci: merge all trivy reports into one artifact per run, skip empty rep…
yaroslavmokflmg Aug 5, 2026
4333c55
ci: merge all trivy reports into one artifact per run, dedupe upload …
yaroslavmokflmg Aug 5, 2026
1fbc99f
ci: compact trivy action — piped scans, unified evaluate, single merg…
yaroslavmokflmg Aug 5, 2026
adf92ad
ci: report unique vulnerability count in trivy error message
yaroslavmokflmg Aug 5, 2026
fc5cf7f
ci: rename scan_code/report jobs, split scan_code and scan_image repo…
yaroslavmokflmg Aug 18, 2026
d7ed1ac
Merge remote-tracking branch 'origin/main' into security/trivy
yaroslavmokflmg Aug 18, 2026
b8b1e8f
ci: rename scan steps — Scan code / Scan image, action Trivy -> Scan
yaroslavmokflmg Aug 18, 2026
8517b05
ci: rename scan steps — Scan code / Scan image, action name Vulnerabi…
yaroslavmokflmg Aug 18, 2026
3feb686
ci: point error message to scan_code/scan_image artifact
yaroslavmokflmg Aug 18, 2026
9022129
ci: per-service code scan matrix with root coverage, merge artifacts …
yaroslavmokflmg Aug 21, 2026
7765f58
ci: strip trailing whitespace in test.yml
yaroslavmokflmg Aug 21, 2026
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
106 changes: 106 additions & 0 deletions .github/steps/trivy/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: "Vulnerability Scan"
description: "Trivy PR security scans. scan: code = repo dependencies (HIGH/CRITICAL) + Dockerfile base images (CRITICAL), image = image built in the pipeline (HIGH/CRITICAL). Each scan uploads its TSV report as an artifact"

inputs:
scan:
description: "code | image"
required: true
maven-token:
description: "Token for internal GitHub Packages: Maven resolution (scan: code) and docker build secret (scan: image)"
required: false
default: ${{ github.token }}
image-name:
description: "Service name from the matrix; used in the artifact name"
required: false
path:
description: "Directory to scan (scan: code), e.g. a single service; defaults to the whole repo"
required: false
default: "."
skip-dirs:
description: "Comma-separated directories to exclude from the code scan (e.g. service dirs already scanned by the matrix)"
required: false
default: ""
dockerfile:
description: "Path to the service Dockerfile (scan: image)"
required: false
context:
description: "Docker build context (scan: image)"
required: false

runs:
using: composite
steps:
- name: Install Trivy
uses: aquasecurity/setup-trivy@v0.3.1
with:
version: v0.73.0
cache: true

- name: Scan dependencies and Dockerfile base images
if: inputs.scan == 'code'
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.maven-token }}
GITHUB_ACTOR: ${{ github.actor }}
run: |
set -euo pipefail
dir="${{ inputs.path }}"
if [ -f "$dir/pom.xml" ]; then # warm ~/.m2 via Google's Central mirror so trivy sees parent-managed versions
echo '<settings><mirrors><mirror><id>google-central</id><url>https://maven-central.storage-download.googleapis.com/maven2/</url><mirrorOf>central</mirrorOf></mirror></mirrors></settings>' > /tmp/mirror.xml
mvn -B -q -fn -DskipTests -gs /tmp/mirror.xml -f "$dir/pom.xml" $([ -f .mvn/settings.xml ] && echo '-s .mvn/settings.xml') dependency:go-offline | tee /tmp/mvn.log || true
! grep -q '\[ERROR\]' /tmp/mvn.log || echo "::warning::Some Maven dependencies could not be resolved; scan depth may be reduced"
fi
skip="${{ inputs.skip-dirs }}"
targs=(--no-progress --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --offline-scan --format json)
[ -n "$skip" ] && targs+=(--skip-dirs "$skip")
trivy fs "${targs[@]}" "$dir" |
jq -r '.Results[]? as $r | $r.Vulnerabilities[]? | [.Severity, .PkgName, .InstalledVersion, (.FixedVersion // "-"), .VulnerabilityID, $r.Target] | @tsv' | sort -u > "trivy-${GITHUB_REPOSITORY##*/}-code-report.tsv"

out="trivy-${GITHUB_REPOSITORY##*/}-docker-report.tsv"; : > "$out"
fargs=(-name 'Dockerfile*' -not -path '*/.git/*' -not -path '*/node_modules/*')
[ -n "$skip" ] && for d in ${skip//,/ }; do fargs+=(-not -path "*/${d}/*" -not -path "${d}/*"); done
find "$dir" "${fargs[@]}" -print0 |
xargs -0 -r awk 'toupper($1)=="FROM" {i=$2; if (i ~ /^--/) i=$3; print i; if (toupper($3)=="AS") print "~"$4; if (toupper($4)=="AS") print "~"$5}' | sort -u > /tmp/from.txt
for img in $(grep -v '^~' /tmp/from.txt); do
[ "$img" = scratch ] && continue
case "$img" in *'$'*) echo "::warning::Skipping base image with unresolved variable: ${img}"; continue ;; esac
grep -qxF "~$img" /tmp/from.txt && continue
trivy image --no-progress --scanners vuln --severity CRITICAL --ignore-unfixed --format json "$img" |
jq -r --arg img "$img" '.Results[]? as $r | $r.Vulnerabilities[]? | [.Severity, .PkgName, .InstalledVersion, (.FixedVersion // "-"), .VulnerabilityID, $img] | @tsv' >> "$out" ||
echo "::warning::Base image ${img} could not be scanned (pull/scan error); it was NOT checked"
done
sort -u "$out" -o "$out"
find . -maxdepth 1 -name 'trivy-*.tsv' -empty -delete

- name: Scan built image
if: inputs.scan == 'image'
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.maven-token }}
GITHUB_ACTOR: ${{ github.actor }}
run: |
set -euo pipefail
docker buildx build --platform linux/amd64 --secret id=GITHUB_TOKEN,env=GITHUB_TOKEN --build-arg GITHUB_ACTOR="$GITHUB_ACTOR" \
-f "${{ inputs.dockerfile }}" --output type=oci,dest=/tmp/image.tar "${{ inputs.context }}"
out="trivy-${GITHUB_REPOSITORY##*/}-image-report-${{ inputs.image-name }}.tsv"
trivy image --no-progress --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --input /tmp/image.tar --format json |
jq -r '.Results[]? as $r | $r.Vulnerabilities[]? | [.Severity, .PkgName, .InstalledVersion, (.FixedVersion // "-"), .VulnerabilityID, $r.Target] | @tsv' | sort -u > "$out"
find . -maxdepth 1 -name 'trivy-*.tsv' -empty -delete

- name: Upload report
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.scan == 'code' && (inputs.image-name && format('scan_code-{0}', inputs.image-name) || 'scan_code') || format('scan_image-{0}', inputs.image-name) }}
path: trivy-*.tsv
if-no-files-found: ignore

- name: Evaluate
shell: bash
env:
ARTIFACT: ${{ inputs.scan == 'code' && 'scan_code' || 'scan_image' }}
run: |
set -euo pipefail
files=$(ls trivy-*.tsv 2>/dev/null) || { echo "No vulnerabilities found."; exit 0; }
column -t -s "$(printf '\t')" $files
echo "::error::Trivy found $(cut -f1,2,5 $files | sort -u | wc -l | tr -d ' ') unique HIGH/CRITICAL vulnerabilities ($(cat $files | wc -l | tr -d ' ') occurrences across modules) — full report in the ${ARTIFACT} artifact"
exit 1
24 changes: 22 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ jobs:
if: github.event.pull_request.state == 'open' && !github.event.pull_request.draft


scan:
name: "Scan"
runs-on: ubuntu-latest
needs: [changes]
permissions:
contents: read
packages: read
if: github.event_name == 'pull_request'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
persist-credentials: false

- name: Scan code
uses: ./.github/steps/trivy
with:
scan: code

test-java:
name: Test Java
runs-on: ubuntu-latest
Expand Down Expand Up @@ -80,7 +100,7 @@ jobs:
name: Test Node (${{ matrix.name }})
runs-on: ubuntu-latest
needs: [changes]
# npm's defaults allow 970s of silence per hung registry socket; these bound it to 115s
# npm's defaults allow 970s of silence per hung registry socket; these bound it to 115s
env:
NPM_CONFIG_FETCH_TIMEOUT: 30000
NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: 15000
Expand Down Expand Up @@ -222,7 +242,7 @@ jobs:
needs: [changes, test-java, test-node, test-rust]
runs-on: ubuntu-latest
if: |
always() &&
always() &&
(contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || !contains(needs.*.result, 'skipped'))
steps:
- if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
Expand Down
3 changes: 3 additions & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Vulnerabilities to skip in the Trivy scan: one CVE/GHSA id per line, e.g.:
#
# CVE-2026-12345