Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bd63998
ci: add Trivy security scan to PR pipeline
yaroslavmokflmg Aug 3, 2026
255961e
ci: harden trivy gate — no persisted git credentials, visible warning…
yaroslavmokflmg Aug 3, 2026
1131656
ci: make trivy gate non-blocking (allow fail) until devs have capacity
yaroslavmokflmg Aug 5, 2026
82375c8
ci: trivy code+docker scan in one job, in-build image scan, per-scan …
yaroslavmokflmg Aug 5, 2026
2ebf681
ci: merge all trivy reports into one artifact per run, skip empty rep…
yaroslavmokflmg Aug 5, 2026
75f55d2
ci: merge all trivy reports into one artifact per run, dedupe upload …
yaroslavmokflmg Aug 5, 2026
5583c66
ci: compact trivy action — piped scans, unified evaluate, single merg…
yaroslavmokflmg Aug 5, 2026
d91e74e
ci: report unique vulnerability count in trivy error message
yaroslavmokflmg Aug 5, 2026
0d93015
ci: rename scan_code/report jobs, split scan_code and scan_image repo…
yaroslavmokflmg Aug 18, 2026
33f0c04
ci: rename scan steps — Scan code / Scan image, action Trivy -> Scan
yaroslavmokflmg Aug 18, 2026
6e1dffb
ci: rename scan steps — Scan code / Scan image, action name Vulnerabi…
yaroslavmokflmg Aug 18, 2026
4ba807f
ci: point error message to scan_code/scan_image artifact
yaroslavmokflmg Aug 18, 2026
31ad4c3
ci: per-service code scan matrix with root coverage, merge artifacts …
yaroslavmokflmg Aug 21, 2026
a410198
ci: strip trailing whitespace in test.yml
yaroslavmokflmg Aug 21, 2026
f29156b
Fix image scan input format, add per-module scan matrix and dependenc…
yaroslavmokflmg Aug 26, 2026
776bb3d
Bump Trivy to v0.74.0
yaroslavmokflmg Aug 26, 2026
af88376
Address review: compact scan action, changes-driven module matrix, re…
yaroslavmokflmg Sep 2, 2026
3549a10
Scan built image for OS packages only, java deps stay in code scan
yaroslavmokflmg Sep 2, 2026
7b210e4
Split scan action into small named steps, extract maven mirror and jq…
yaroslavmokflmg Sep 2, 2026
741f033
Root scan skips root pom (covered by service scans); revert changes.y…
yaroslavmokflmg Sep 3, 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
134 changes: 134 additions & 0 deletions .github/steps/trivy/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
name: "Vulnerability Scan"
description: "Trivy scan: repository dependencies and Dockerfile base images (code) or a built service image (image)"

inputs:
scan:
description: "'code' or 'image'"
required: true
path: { default: "." }
skip-dirs: { default: "" }
skip-files: { default: "" }
image-name: { default: "" }
dockerfile: { default: "" }
context: { default: "." }
maven-token: { default: "${{ github.token }}" }

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

- name: Resolve Maven dependencies
if: inputs.scan == 'code'
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.maven-token }}
GITHUB_ACTOR: ${{ github.actor }}
Comment thread
yaroslavmokflmg marked this conversation as resolved.
SCAN_DIR: ${{ inputs.path }}
ACTION_PATH: ${{ github.action_path }}
run: |
set -euo pipefail
[ -f "$SCAN_DIR/pom.xml" ] || exit 0
mvn_args=(-B -q -fn -DskipTests -gs "$ACTION_PATH/central-mirror.xml" -f "$SCAN_DIR/pom.xml")
[ -f .mvn/settings.xml ] && mvn_args+=(-s .mvn/settings.xml)
mvn "${mvn_args[@]}" 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"

- name: Scan dependencies
if: inputs.scan == 'code'
shell: bash
env:
SCAN_DIR: ${{ inputs.path }}
SKIP_DIRS: ${{ inputs.skip-dirs }}
SKIP_FILES: ${{ inputs.skip-files }}
ACTION_PATH: ${{ github.action_path }}
run: |
set -euo pipefail
args=(--no-progress --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --offline-scan --format json)
[ -n "$SKIP_DIRS" ] && args+=(--skip-dirs "$SKIP_DIRS")
[ -n "$SKIP_FILES" ] && args+=(--skip-files "$SKIP_FILES")
report="trivy-${GITHUB_REPOSITORY##*/}-code-report.tsv"
trivy fs "${args[@]}" "$SCAN_DIR" | jq -r --arg src "" -f "$ACTION_PATH/to-tsv.jq" | sort -u > "$report"
while IFS=$'\t' read -r severity pkg installed fixed cve target; do
origin="-"
if [[ "$target" == *pom.xml ]]; then
grep -q "<artifactId>${pkg##*:}</artifactId>" "$SCAN_DIR/$target" 2>/dev/null && origin=direct || origin=transitive
fi
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$severity" "$pkg" "$installed" "$fixed" "$cve" "$target" "$origin"
done < "$report" > "$report.tmp" && mv "$report.tmp" "$report"

- name: Scan Dockerfile base images
if: inputs.scan == 'code'
shell: bash
env:
SCAN_DIR: ${{ inputs.path }}
SKIP_DIRS: ${{ inputs.skip-dirs }}
ACTION_PATH: ${{ github.action_path }}
run: |
set -euo pipefail
find_args=(-name 'Dockerfile*' -not -path '*/.git/*' -not -path '*/node_modules/*')
for dir in ${SKIP_DIRS//,/ }; do find_args+=(-not -path "*/$dir/*" -not -path "$dir/*"); done
find "$SCAN_DIR" "${find_args[@]}" -print0 |
xargs -0 -r awk 'toupper($1)=="FROM" { img=$2; if (img ~ /^--/) img=$3; print img; if (toupper($3)=="AS") print "~"$4; if (toupper($4)=="AS") print "~"$5 }' |
sort -u > /tmp/base-images.txt
report="trivy-${GITHUB_REPOSITORY##*/}-docker-report.tsv"
: > "$report"
while read -r img; do
case "$img" in "~"*|scratch) continue ;; *'$'*) echo "::warning::Skipping base image with unresolved variable: $img"; continue ;; esac
grep -qxF "~$img" /tmp/base-images.txt && continue
trivy image --no-progress --scanners vuln --severity CRITICAL --ignore-unfixed --format json "$img" |
jq -r --arg src "$img" -f "$ACTION_PATH/to-tsv.jq" >> "$report" ||
echo "::warning::Base image $img could not be scanned (pull/scan error); it was NOT checked"
done < /tmp/base-images.txt
sort -u "$report" -o "$report"
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 }}
IMAGE_NAME: ${{ inputs.image-name }}
ACTION_PATH: ${{ github.action_path }}
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=docker,dest=/tmp/image.tar "${{ inputs.context }}"
trivy image --no-progress --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --pkg-types os --format json --input /tmp/image.tar |
jq -r --arg src "" -f "$ACTION_PATH/to-tsv.jq" | sort -u > "trivy-${GITHUB_REPOSITORY##*/}-image-report-${IMAGE_NAME}.tsv"
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' }}
GH_TOKEN: ${{ github.token }}
STATUS_CONTEXT: "${{ inputs.scan == 'image' && format('Scan Image: {0}', inputs.image-name) || '' }}"
STATUS_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
report_status() {
[ -n "$STATUS_CONTEXT" ] && [ -n "$STATUS_SHA" ] || return 0
gh api "repos/$GITHUB_REPOSITORY/statuses/$STATUS_SHA" -f state="$1" -f context="$STATUS_CONTEXT" -f description="$2" \
-f target_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" > /dev/null ||
echo "::warning::Could not publish the $STATUS_CONTEXT commit status"
}
files=$(ls trivy-*.tsv 2>/dev/null) || { echo "No vulnerabilities found."; report_status success "No HIGH/CRITICAL vulnerabilities found"; exit 0; }
column -t -s "$(printf '\t')" $files
unique=$(cut -f1,2,5 $files | sort -u | wc -l | tr -d ' ')
report_status failure "$unique HIGH/CRITICAL vulnerabilities — full report in the $ARTIFACT artifact"
echo "::error::Trivy found $unique unique HIGH/CRITICAL vulnerabilities ($(cat $files | wc -l | tr -d ' ') occurrences across modules) — full report in the ${ARTIFACT} artifact"
exit 1
9 changes: 9 additions & 0 deletions .github/steps/trivy/central-mirror.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<settings>
<mirrors>
<mirror>
<id>google-central</id>
<url>https://maven-central.storage-download.googleapis.com/maven2/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
</settings>
5 changes: 5 additions & 0 deletions .github/steps/trivy/to-tsv.jq
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.Results[]? as $r
| $r.Vulnerabilities[]?
| [.Severity, .PkgName, .InstalledVersion, (.FixedVersion // "-"), .VulnerabilityID,
(if $src == "" then $r.Target else $src end)]
| @tsv
22 changes: 21 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@ jobs:
github.event_name == 'pull_request' &&
!github.event.pull_request.draft

scan:
name: "Scan Code"
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 }}
persist-credentials: false

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

test_client:
name: "Test Client (${{ matrix.name }})"
needs: [changes]
Expand Down Expand Up @@ -140,7 +160,7 @@ jobs:

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Install GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
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
Loading