Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 20 additions & 15 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,42 +215,47 @@ jobs:
# `--ignore-unfixed` keeps the output to what a base-image bump can actually fix.
# A multi-arch manifest resolves to the runner's platform (linux/amd64); the arm64
# leg shares the same base image and package set, so one pass is representative.
#
# CRITICAL/HIGH only. Widening to MEDIUM/LOW was tried and reverted on review
# (constructorfabric/insight#2016): every finding here comes from the base layer and the
# remedy is the same either way — refresh the base image — while the wider filter triples
# the alert count and buries the CRITICALs that drive action.
env:
TRIVY_IMAGE: aquasec/trivy:0.72.0@sha256:cffe3f5161a47a6823fbd23d985795b3ed72a4c806da4c4df16266c02accdd6f
IMAGE_REF: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.merge.outputs.build_tag }}
TRIVY_USERNAME: ${{ github.actor }}
TRIVY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
# One pull and one analysis: `trivy convert` renders both the log table and the SARIF
# from a single JSON report instead of scanning twice. `-w /work` so a repo-root
# `.trivyignore` is resolved — Trivy reads the default ignore file relative to the
# working directory, and without this the file is looked for in the image's own cwd
# and silently ignored. `--ignorefile` is not used instead: Trivy exits FATAL when the
# named file is absent, which would break every run until someone adds a waiver.
run: |
set -euo pipefail
echo "Scanning ${IMAGE_REF}"
# Two passes over the same warm DB cache: a readable table in the log, SARIF for
# Code Scanning.
docker run --rm \
-v /tmp/trivy-cache:/root/.cache \
-e TRIVY_USERNAME -e TRIVY_PASSWORD \
"$TRIVY_IMAGE" image \
--severity CRITICAL,HIGH \
--pkg-types os,library \
--ignore-unfixed \
--exit-code 0 \
--no-progress \
--format table \
"$IMAGE_REF"
docker run --rm \
-v "${{ github.workspace }}:/work" \
-v /tmp/trivy-cache:/root/.cache \
-w /work \
-e TRIVY_USERNAME -e TRIVY_PASSWORD \
"$TRIVY_IMAGE" image \
--severity CRITICAL,HIGH \
--pkg-types os,library \
--ignore-unfixed \
--exit-code 0 \
--no-progress \
--format sarif --output /work/trivy-image.sarif \
--format json --output /work/trivy-image.json \
"$IMAGE_REF"
docker run --rm -v "${{ github.workspace }}:/work" \
"$TRIVY_IMAGE" convert --format table /work/trivy-image.json
docker run --rm -v "${{ github.workspace }}:/work" \
"$TRIVY_IMAGE" convert --format sarif --output /work/trivy-image.sarif /work/trivy-image.json

- name: Upload SARIF to GitHub Code Scanning
if: always()
# The guard keeps a failed registry pull from reporting twice — once for the pull, once
# for a SARIF file that was never written.
if: ${{ always() && hashFiles('trivy-image.sarif') != '' }}
uses: github/codeql-action/upload-sarif@fb0994ef1c058010acf1efccff928b0a83b1ed54 # v4.32.6
with:
sarif_file: trivy-image.sarif
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/semgrep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ name: Semgrep SAST
on:
pull_request:
branches: [main]
push:
# A main-branch baseline the moment something lands, rather than only at 03:27 the next
# morning: without this, `branch:main` in Code Scanning carries no SAST data between a merge
# and the nightly run, and a merge that introduces a finding is invisible until then.
branches: [main]
schedule:
# Nightly full-tree baseline (independent of what any PR touched), 03:27 UTC.
- cron: "27 3 * * *"
Expand Down
92 changes: 54 additions & 38 deletions .github/workflows/trivy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,27 @@ name: Trivy SCA
# Software-composition analysis over the source tree (constructorfabric/insight#1478).
# Two passes, matching the org's GitLab trivy-scan component:
#
# gate — CRITICAL only, blocks. Stop-the-line: a CRITICAL CVE or an embedded secret in a
# dependency manifest must be fixed or waived in `.trivyignore` before merge.
# Baseline today: 0 CRITICAL, so the gate starts green.
# report — HIGH/MEDIUM/LOW, never blocks. Findings land in "Security -> Code scanning"
# (SARIF) plus a job-summary table. Baseline today: 10 HIGH / 16 MEDIUM / 3 LOW,
# almost all from build-time tooling declared under `dependencies`.
# critical — CRITICAL only, and the one pass that also runs the secret scanner. Report-only
# (`--exit-code 0`), like everything else here: a CRITICAL surfaces in the log without
# stopping a merge. Kept as a separate pass because it is the only place secrets are
# scanned, and those must never reach a SARIF upload on a public repository.
# Baseline today: 0 CRITICAL.
# report — every severity, never blocks. Findings land in "Security -> Code scanning"
# (SARIF) plus a job-summary table. Baseline today: 37 vulnerabilities and 2
# misconfigurations, most of them build-time tooling declared under `dependencies`.
#
# Scope: this scans the repository (`trivy fs`) — lock file, Dockerfile, configs. It does NOT
# see the image that ships: base-image and OS-package CVEs are covered by the `trivy-image`
# job in docker.yml, which runs against the pushed manifest.
#
# The report pass deliberately drops the secret scanner (`--scanners vuln,misconfig`): Code
# Scanning alerts on a public repository are world-readable, and Trivy's secret findings quote
# the surrounding match. Secrets stay in the blocking gate, where TruffleHog is the primary
# owner anyway (trufflehog.yml).
# the surrounding match. Secrets stay in the `critical` pass, whose output goes to the log only,
# and TruffleHog owns the domain anyway (trufflehog.yml).
#
# Dev dependencies are excluded by Trivy's pnpm default — they are not in the shipped bundle.
# Add `--include-dev-deps` if we ever want build-time supply-chain coverage here too.
# Dev dependencies are included (`--include-dev-deps`). They are not in the shipped bundle, but a
# compromised one executes on the runner during the build, so leaving them out understates the
# supply-chain surface rather than simplifying it.

on:
pull_request:
Expand All @@ -46,32 +49,39 @@ env:

jobs:
gate:
name: gate (CRITICAL)
name: critical (report-only)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
persist-credentials: false

- name: Trivy fs scan — CRITICAL only (blocking)
# `--ignore-unfixed` keeps the gate actionable: a CRITICAL with no released fix cannot
# be resolved by the PR author, so it belongs in the report pass, not in a merge block.
# Waivers go in `.trivyignore` at the repo root, date-scoped:
- name: Trivy fs scan — CRITICAL only (report-only)
# `--ignore-unfixed` keeps the output actionable: a CRITICAL with no released fix cannot
# be resolved by the PR author. Waivers go in `.trivyignore` at the repo root,
# date-scoped — they matter again the moment this pass becomes blocking:
# CVE-2026-12345 exp:2026-12-31 # tracked in <issue>, fix queued
#
# `-w /src` is load-bearing for that: Trivy resolves the default ignore file relative to
# the working directory, so without it a repo-root `.trivyignore` is read from the
# image's own cwd and silently ignored. `--ignorefile` is not used instead because Trivy
# exits FATAL when the named file does not exist, which would break every run until
# someone adds a waiver.
run: |
docker run --rm \
-v "${{ github.workspace }}:/src:ro" \
-w /src \
"$TRIVY_IMAGE" fs \
--scanners vuln,secret,misconfig \
--severity CRITICAL \
--ignore-unfixed \
--exit-code 1 \
--exit-code 0 \
--no-progress \
/src

report:
name: report (HIGH/MEDIUM/LOW)
name: report (all severities)
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
Expand All @@ -82,31 +92,36 @@ jobs:
with:
persist-credentials: false

- name: Trivy fs scan — HIGH/MEDIUM/LOW (never blocks)
# Two output formats from two runs over the same cached DB: SARIF for Code Scanning,
# JSON for the summary table below. `--exit-code 0` on both — this pass only reports.
- name: Trivy fs scan — all severities, dev deps included
# One scan, two formats: JSON feeds the summary below, and `trivy convert` derives the
# SARIF from that same JSON instead of rescanning. `-w /src` for the ignore-file reason
# documented on the `critical` pass. `--exit-code 0` — nothing here blocks.
#
# CRITICAL is in the severity list even though the `critical` pass covers it: that pass
# only prints a table, so without this a CRITICAL would never appear as an alert.
#
# `--include-dev-deps` because a compromised build-time dependency executes on the
# runner and can alter the bundle. Trivy's pnpm default hides them, which understates
# the supply-chain surface — on this repository it hides 8 of 37 findings.
run: |
set -euo pipefail
docker run --rm \
-v "${{ github.workspace }}:/src" \
-v /tmp/trivy-cache:/root/.cache \
-w /src \
"$TRIVY_IMAGE" fs \
--scanners vuln,misconfig \
--severity HIGH,MEDIUM,LOW \
--severity CRITICAL,HIGH,MEDIUM,LOW \
--include-dev-deps \
Comment on lines +114 to +115

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

echo "Locate workflow files"
fd -a 'trivy\.yml$|trivy\.yaml$' .github/workflows 2>/dev/null || true

echo
echo "Relevant workflow excerpt"
if [ -f .github/workflows/trivy.yml ]; then
  nl -ba .github/workflows/trivy.yml | sed -n '90,180p'
else
  echo ".github/workflows/trivy.yml not found"
fi

echo
echo "Search for UNKNOWN and trivy severity/reporting"
rg -n "unknown|UNKNOWN|trivy" .github/workflows/trivy.yml .github/workflows -S || true

Repository: constructorfabric/insight-front

Length of output: 306


🏁 Script executed:

#!/bin/bash
set -u

echo "Workflow excerpt"
awk 'NR>=90 && NR<=180 { printf "%6d\t%s\n", NR, $0 }' .github/workflows/trivy.yml

echo
echo "Search for UNKNOWN and relevant reporting lines"
grep -n "unknown\|UNKNOWN\|--severity\|for s in\|rank\|every severity" .github/workflows/trivy.yml .github/workflows -S || true

Repository: constructorfabric/insight-front

Length of output: 5971


🌐 Web query:

Trivy --severity UNKNOWN documentation

💡 Result:

In Trivy, the UNKNOWN severity signifies that a vulnerability's severity rating could not be determined from the available data sources [1][2]. Key aspects regarding the UNKNOWN severity include: Severity Assignment Logic: Trivy automatically aggregates severity data from multiple sources (such as NVD, GHSA, and OS-specific vendors) to provide the most accurate assessment [1][3]. If no configured vendor or data source provides a severity rating for a vulnerability, Trivy defaults to UNKNOWN [1][2]. Customizing Severity Sources: Users can control how severities are determined using the --vuln-severity-source flag [1][4]. This allows you to specify a priority list of data sources [4]. Trivy will check these sources in the order provided; if none of the specified sources contain a severity, it will fall back to UNKNOWN [1][4]. For example: --vuln-severity-source nvd,ghsa: Trivy checks NVD first, then GHSA, and returns UNKNOWN if neither provides a severity [4]. Reasons for UNKNOWN Severity: - Data Delay: Some vendors (like NVD) may delay severity analysis for new vulnerabilities, while others may provide it more quickly [1][5]. - Missing Detail: Vulnerabilities detected via OS advisories (e.g., Alpine secdb) may lack severity information in the underlying vulnerability database if the detail entry has not yet been populated [6]. - Light Database Mode: Using the --light option may result in UNKNOWN severities because the lightweight database lacks detailed vulnerability information (such as CVSS scores) often used to calculate severity [7]. Trivy Severity Mapping: In Trivy's internal classification, UNKNOWN is mapped to the value 0, while others are assigned as follows [1][2]: 0: Unknown 1: Low 2: Medium 3: High 4: Critical

Citations:


🌐 Web query:

Trivy UNKNOWN severity documentation

💡 Result:

In Trivy, the "UNKNOWN" severity indicates that a vulnerability's severity level could not be determined from any of the available data sources (such as NVD, GHSA, or vendor-specific databases) [1][2][3]. Key points regarding the UNKNOWN severity: Determination Logic: Trivy automatically attempts to assign a severity based on a prioritized list of sources [1][2][4]. If no vendor or data source provides a specific severity rating, and it cannot be calculated via a CVSS score, Trivy assigns it the UNKNOWN severity [1][3]. Avoiding UNKNOWNs: To minimize the occurrence of UNKNOWN severities, Trivy is designed to use severity ratings from various vendors as fallbacks. For example, even if an NVD severity is not yet available, Trivy may use severity data from other vendors (like Red Hat) to provide a more actionable assessment [1][3][5]. Customization: Users can influence how severity is determined using the --vuln-severity-source flag [1][2][6]. This allows you to define a custom order of priority for severity sources. If you specify a sequence of sources, Trivy will check them in that order; if none of the specified sources contain a severity, Trivy will result in an UNKNOWN severity [1][2][6]. Reporting: In the context of reporting, UNKNOWN is considered a severity level (mapped to numerical value 0), distinct from other levels like Low, Medium, High, and Critical [1][2][3]. It is also important to distinguish this from vulnerability "status" (e.g., "unknown" status), which refers to whether a package is affected by a vulnerability on a specific platform [7]. For technical verification, you can check the SeveritySource field in Trivy's JSON output to identify exactly which source provided the severity (or if it was unable to be determined) for a specific vulnerability [2][3].

Citations:


Include Trivy’s UNKNOWN severity end to end.

--severity CRITICAL,HIGH,MEDIUM,LOW filters out UNKNOWN findings, while the summary later says it reports every severity. Add UNKNOWN to the scan and include it in the summary counts/ranking so vulnerabilities without a determined severity are not silently omitted.

🤖 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/trivy.yml around lines 114 - 115, Update the Trivy
workflow command to include UNKNOWN in the --severity list, and update the later
summary’s severity counts and ranking to include UNKNOWN as well. Keep the scan
and summary severity sets consistent so undetermined-severity findings are
reported end to end.

Source: MCP tools

--exit-code 0 \
--no-progress \
--format sarif --output /src/trivy-fs.sarif \
--format json --output /src/trivy-fs.json \
/src
docker run --rm \
-v "${{ github.workspace }}:/src" \
-v /tmp/trivy-cache:/root/.cache \
"$TRIVY_IMAGE" fs \
--scanners vuln,misconfig \
--severity HIGH,MEDIUM,LOW \
--exit-code 0 \
--no-progress \
--format json --output /src/trivy-fs.json \
/src
"$TRIVY_IMAGE" convert \
--format sarif --output /src/trivy-fs.sarif \
/src/trivy-fs.json

- name: Summarize findings in the job summary
if: always()
Expand All @@ -132,24 +147,23 @@ jobs:
vulns += r.get("Vulnerabilities") or []
miscfg += [(r.get("Target"), m) for m in (r.get("Misconfigurations") or [])]
w("## Trivy SCA (report-only)\n\n")
w(f"**{len(vulns)}** vulnerabilit(ies) + **{len(miscfg)}** misconfiguration(s) at "
f"HIGH/MEDIUM/LOW. CRITICAL is handled by the blocking `gate` job. "
f"Full details in [Security -> Code scanning]({cs_url}).\n\n")
w(f"**{len(vulns)}** vulnerabilit(ies) + **{len(miscfg)}** misconfiguration(s), every "
f"severity, production and development dependencies. Nothing here blocks a merge. Full details in [Security -> Code scanning]({cs_url}).\n\n")
if vulns:
sev = collections.Counter(v["Severity"] for v in vulns)
w("| Severity | Count |\n|---|---:|\n")
for s in ("HIGH", "MEDIUM", "LOW"):
for s in ("CRITICAL", "HIGH", "MEDIUM", "LOW"):
if sev.get(s):
w(f"| {s} | {sev[s]} |\n")
# One row per (package, version): that is what an upgrade actually fixes.
bundles = collections.defaultdict(list)
for v in vulns:
bundles[(v["PkgName"], v.get("InstalledVersion", "?"))].append(v)
rank = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}
rank = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
w("\n<details><summary>By package</summary>\n\n| Package | Max | CVEs | Fixed in |\n|---|---|---:|---|\n")
for (pkg, ver), items in sorted(
bundles.items(), key=lambda kv: (min(rank.get(i["Severity"], 3) for i in kv[1]), kv[0])):
worst = min(items, key=lambda i: rank.get(i["Severity"], 3))["Severity"]
bundles.items(), key=lambda kv: (min(rank.get(i["Severity"], 4) for i in kv[1]), kv[0])):
worst = min(items, key=lambda i: rank.get(i["Severity"], 4))["Severity"]
fixes = sorted({i.get("FixedVersion") or "-" for i in items})
w(f"| `{pkg}@{ver}` | {worst} | {len(items)} | {', '.join(fixes)} |\n")
w("\n</details>\n")
Expand All @@ -162,7 +176,9 @@ jobs:

- name: Upload SARIF to GitHub Code Scanning
# Skip on fork PRs: read-only token cannot upload, which would red-X this report-only job.
if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
# The hashFiles guard keeps a failed scan from producing a second, misleading error that
# points at Code Scanning rather than at the scan that actually broke.
if: ${{ always() && hashFiles('trivy-fs.sarif') != '' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
uses: github/codeql-action/upload-sarif@fb0994ef1c058010acf1efccff928b0a83b1ed54 # v4.32.6
with:
sarif_file: trivy-fs.sarif
Expand Down
Loading