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
Comment on lines +25 to +29

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ref="$(awk '/uses: aquasecurity\/setup-trivy@/ {print $2}' .github/steps/trivy/action.yml)"
[[ "$ref" =~ ^aquasecurity/setup-trivy@[0-9a-f]{40}$ ]]

Repository: flamingo-stack/meshcentral

Length of output: 164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## action.yml relevant lines"
sed -n '1,45p' .github/steps/trivy/action.yml

echo
echo "## current Trivy pin"
ref="$(awk '/uses: aquasecurity\/setup-trivy@/ {print $2}' .github/steps/trivy/action.yml)"
printf 'pin=%s\n' "$ref"
[[ "$ref" =~ ^aquasecurity/setup-trivy@[0-9a-f]{40}$ ]]
exit_code=$?
echo "matches_40_char_sha=$exit_code"

Repository: flamingo-stack/meshcentral

Length of output: 2174


🌐 Web query:

aquasecurity setup-trivy advisory GHSA-69fq-xp46-6x23 March 2026 full commit SHA pin recommendation

💡 Result:

In response to the supply chain compromise identified as GHSA-69fq-xp46-6x23 (CVE-2026-33634) in March 2026, where a threat actor force-pushed malicious code to all existing tags (v0.2.0–v0.2.6) of the aquasecurity/setup-trivy GitHub Action, users are strongly advised to pin to a verified, safe commit SHA [1][2][3]. The specific commit SHA identified as the safe version for setup-trivy v0.2.6 following the incident is 3fb12ec12f41e471780db15c232d5dd185dcb514 [4][5]. Security best practices for using GitHub Actions, particularly following this incident, include: 1. Pinning to full, immutable commit SHA hashes rather than relying on mutable version tags (e.g., v0.2.6) to prevent similar supply chain attacks where tags are overwritten [6][3]. 2. Treating any pipeline secrets used during the exposure window (March 19, 2026, 17:43 UTC to approximately March 19, 2026, 21:44 UTC for setup-trivy) as compromised and rotating them immediately [1][4][2]. For additional context, aquasecurity restored setup-trivy v0.2.6 with safe content after the incident, but because the attacker force-pushed to the tags, users who had previously cached or pulled the malicious version of v0.2.6 must ensure they update to the corrected SHA [1][2].

Citations:


Pin aquasecurity/setup-trivy to a full commit SHA.

The pin aquasecurity/setup-trivy@v0.3.1 uses a mutable third-party action tag. A changed tag can execute code with the Maven token. Use a reviewed 40-character commit SHA instead.

🤖 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 13 - 17, Update the Install
Trivy step’s uses reference from the mutable aquasecurity/setup-trivy@v0.3.1 tag
to a reviewed, immutable 40-character commit SHA, keeping the existing version
and cache inputs unchanged.


- 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
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 @@ -39,6 +39,39 @@ jobs:
!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
Comment on lines +46 to +57

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 \
  'head\.repo|persist-credentials|maven-token|GITHUB_TOKEN|dependency:go-offline' \
  .github/workflows/test.yml .github/steps/trivy/action.yml

Repository: flamingo-stack/meshcentral

Length of output: 2778


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow relevant lines =="
sed -n '1,110p' .github/workflows/test.yml | cat -n

echo
echo "== trivy action relevant lines =="
sed -n '1,80p' .github/steps/trivy/action.yml | cat -n

echo
echo "== workflow references to head repo / forks / persist-credentials =="
rg -n 'pull_request|fork|persist-credentials|maven-token|GITHUB_TOKEN|actions/checkout|trivy' .github/workflows/test.yml .github/steps/trivy/action.yml

Repository: flamingo-stack/meshcentral

Length of output: 10873


Do not run Maven dependency resolution for fork pull requests.

The trivy_scan job runs for all open non-draft pull requests and checks out the pull request head. actions/checkout stores credentials by default, and the Trivy composite action exports GITHUB_TOKEN to Maven during dependency:go-offline. Maven can load pull-request-controlled build configuration such as .mvn/extensions.xml, so a fork PR could use that configuration to read the token. Set persist-credentials: false, remove Maven resolution from fork workflows, or scope tokenized Maven resolution to same-repository pull requests.

🧰 Tools
🪛 zizmor (1.28.0)

[warning] 50-53: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 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/workflows/test.yml around lines 45 - 56, Update the trivy_scan
workflow so fork pull requests never run tokenized Maven dependency resolution:
disable credential persistence in the Checkout step and remove or conditionally
skip the Trivy composite action’s Maven resolution for fork-originated pull
requests, while preserving it for same-repository pull requests as appropriate.

Source: Linters/SAST tools

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:
name: "Test meshcentral"
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