Skip to content
Draft
90 changes: 90 additions & 0 deletions .github/steps/trivy/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: "Trivy"
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 build matrix (scan: image)"
required: false
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 }}
Comment on lines +34 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repository files of interest:"
git ls-files | grep -E '^\.(github/(steps/trivy/action\.yml|workflows/test\.yml)|mvn/settings\.xml|pom\.xml)' || true

echo
echo "test.yml excerpt:"
if [ -f .github/workflows/test.yml ]; then
  nl -ba .github/workflows/test.yml | sed -n '1,120p'
fi

echo
echo "trivy action.yml excerpt:"
if [ -f .github/steps/trivy/action.yml ]; then
  nl -ba .github/steps/trivy/action.yml | sed -n '1,120p'
fi

echo
echo "Search for maven-token / package permission / settings usage:"
rg -n "maven-token|GITHUB_TOKEN|permissions:|packages:|persist-credentials|\.mvn/settings\.xml|settings\.xml|mvn\s+-|dependency:go-offline" .github pom.xml || true

Repository: flamingo-stack/fleetmdm

Length of output: 309


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "test.yml excerpt:"
if [ -f .github/workflows/test.yml ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' .github/workflows/test.yml | sed -n '1,140p'
fi

echo
echo "trivy action.yml excerpt:"
if [ -f .github/steps/trivy/action.yml ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' .github/steps/trivy/action.yml | sed -n '1,120p'
fi

echo
echo "Search for relevant identifiers:"
rg -n "maven-token|GITHUB_TOKEN|permissions:|packages:|persist-credentials|\.mvn/settings\.xml|settings\.xml|mvn\s|dependency:go-offline|github\.event\.pull_request" .github pom.xml || true

Repository: flamingo-stack/fleetmdm

Length of output: 12620


🌐 Web query:

Maven Settings reference -s settings and -gs global settings can invoke code plugin extensions

💡 Result:

Maven's settings.xml and global settings files cannot directly invoke or register code plugin extensions [1][2][3]. The Maven settings files (~/.m2/settings.xml and ${MAVEN_HOME}/conf/settings.xml) are designed for environment-specific configuration, such as local repository paths, authentication credentials, proxies, and profile definitions [2][4][3]. They do not possess the capability to load or activate Maven extensions [1][5]. Maven extensions, which adjust build behavior or add functionality to the Maven Core, are instead configured through the following dedicated mechanisms [5][6]: 1..mvn/extensions.xml: The standard, project-level approach for registering extensions [1][5]. You define the extension using its Maven coordinates (groupId, artifactId, version) within this file located in the project's root .mvn/ directory [1][7]. 2. Project-level Build Extensions: Configured within a project's pom.xml under <build><extensions> or by setting <extensions>true</extensions> within a specific plugin configuration [5]. 3. Command Line: Extensions can be loaded temporarily using the -Dmaven.ext.class.path=extension.jar argument [1][5][6]. While settings files can define plugin groups (via the <pluginGroups> element) to simplify command-line execution by allowing Maven to resolve plugin group IDs automatically, this is distinct from invoking or loading code extensions [2][8].

Citations:


🌐 Web query:

Maven dependency plugin lifecycle phases extensions settings include extensions in dependency tree documentation

💡 Result:

The Apache Maven Dependency Plugin does not have a dedicated configuration setting or lifecycle phase parameter to explicitly "include extensions" in the dependency tree. In Maven, extensions (artifacts with the type maven-extension) are typically loaded at the start of the build process to extend core Maven functionality and are generally not managed as standard project dependencies that appear in the typical mvn dependency:tree output [1][2][3]. Key points regarding dependency tree and plugin configuration: 1. Dependency Tree Filtering: The dependency:tree goal allows filtering by group ID, artifact ID, type, and version using the includes and excludes parameters [1][4][5]. Because extensions have a specific maven-extension type, you can attempt to include them in the tree output by specifying this type in your filter pattern if they are present in the project's dependency graph [1][4]. The filter syntax follows the pattern [groupId]:[artifactId]:[type]:[version] [1][4]. 2. Dependency Tree Scope: You can use the scope parameter to filter the dependency tree resolution by a specific scope (e.g., compile, runtime, test), or leave it empty to include all scopes [1][2]. 3. Plugin Lifecycle: The Maven Dependency Plugin goals (like tree, list, copy-dependencies) can be bound to any lifecycle phase via the standard Maven plugin configuration in your pom.xml [6]. However, they do not inherently alter how extensions are loaded or reported [1][7]. If you are trying to view or verify build extensions, these are usually defined in the <build><extensions> section of the pom.xml or via project-level metadata, rather than as standard dependencies that the dependency:tree goal is designed to visualize [3].

Citations:


🌐 Web query:

search.maven.org metadata maven extension type artifact extensions repository documentation

💡 Result:

In Apache Maven, the term "extension" is used in two distinct, unrelated contexts that are often confused [1]. 1. Maven Build Extensions (Functional Extensions) These are JAR artifacts used to extend Maven's core functionality (e.g., adding wagon providers for repository transport or lifecycle enhancements) [2]. They are not defined by a "type" in the dependency sense, but are instead loaded via specific configurations in the project POM or command line [2]. - Configuration: Declared in the section of the pom.xml or via.mvn/extensions.xml [2][3]. - Documentation: Refer to the Maven guide on Using Extensions [2]. 2. Artifact Types and Extensions (File Extensions) In the context of repository metadata and dependency declarations, "extension" refers to the file format of an artifact (e.g., jar, pom, war) [1]. - Type: The element in a dependency declaration is used to determine the artifact's file extension, classifier, and how it is handled in the build (e.g., whether it is added to the classpath) [1][4][5]. - Mapping: Maven core defines default handlers that map specific "types" to their corresponding file extensions (e.g., the 'java-source' type maps to a 'jar' extension with a 'sources' classifier) [1][5]. Regarding search.maven.org: - Status: This service has been superseded by https://central.sonatype.com [6]. While the web interface has been redirected, the API remains available for programmatic access [6]. - Metadata: Repository metadata, including artifact file extensions, can be explored via the Central Repository REST API [7][8]. When searching, you can filter by artifact characteristics, though the distinction between a "build extension" and an "artifact extension" (file type) remains essential for interpreting results correctly [1][8].

Citations:


Remove package credentials from the pull-request scan.

trivy_scan checks out PR code, grants packages: read, and passes the GitHub token into Maven while also loading .mvn/settings.xml from the PR. Treat the packages scope as untrusted for this workflow and do not expose the token to untrusted Maven configuration.

  • .github/steps/trivy/action.yml#L21-L23: do not export maven-token as GITHUB_TOKEN for pull-request scans.
  • .github/steps/trivy/action.yml#L37-L39: do not load .mvn/settings.xml from the pull request when a credential is available; use a trusted settings file if credentials are required.
  • .github/workflows/test.yml#L40-L42: remove packages: read from the untrusted PR scan or keep credentials out of untrusted Maven execution.
📍 Affects 2 files
  • .github/steps/trivy/action.yml#L21-L23 (this comment)
  • .github/steps/trivy/action.yml#L37-L39
  • .github/workflows/test.yml#L40-L48
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/steps/trivy/action.yml around lines 21 - 23, Remove the maven-token
export as GITHUB_TOKEN in .github/steps/trivy/action.yml lines 21-23, and
prevent credentialed Maven execution from loading the PR-controlled
.mvn/settings.xml at lines 37-39 by using a trusted settings file when
credentials are required. In .github/workflows/test.yml lines 40-48, remove
packages: read from the untrusted PR scan or otherwise ensure no credentials
reach PR-controlled Maven execution.

Source: Linters/SAST tools

run: |
set -euo pipefail
if [ -f 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 .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
trivy fs --no-progress --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --offline-scan --format json . |
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"
find . -name 'Dockerfile*' -not -path './.git/*' -not -path '*/node_modules/*' -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' && format('trivy-{0}-code-report', github.event.repository.name) || format('trivy-{0}-image-report-{1}', github.event.repository.name, inputs.image-name) }}
path: trivy-*.tsv
if-no-files-found: ignore

- name: Evaluate
shell: bash
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) in: ${files//$'\n'/, } (full report in the run artifacts)"
exit 1
33 changes: 33 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,39 @@ jobs:
github.event_name == 'pull_request' &&
!github.event.pull_request.draft

trivy_code:
name: "Trivy Code Scan"
runs-on: ubuntu-latest
needs: [changes]
permissions:
contents: read
packages: read
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false

- name: Trivy scan (dependencies + Dockerfile base images)
uses: ./.github/steps/trivy
with:
scan: code

trivy_report:
name: "Trivy Report"
runs-on: ubuntu-latest
needs: [trivy_code]
if: always()
steps:
- name: Merge all scan reports into one artifact
continue-on-error: true
uses: actions/upload-artifact/merge@v4
with:
name: trivy-${{ github.event.repository.name }}-report
pattern: trivy-*-report*
delete-merged: true

test_client:
name: "Test Client (${{ matrix.name }})"
needs: [changes]
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