[WIP] Replace trivy with grype#2285
Conversation
Assisted-By: Gemini 3 Flash
There was a problem hiding this comment.
Code Review
This pull request replaces Trivy with Grype and Syft for container image vulnerability scanning and SBOM generation. The changes include updating documentation, configuration files, and the scan-images.sh script to use the new tools and their respective output formats. Feedback focuses on improving the robustness of the shell script, specifically regarding YAML generation for Grype, handling edge cases in jq processing of scan results, and ensuring proper error handling when set -e is active.
| image_vulnerabilities=$(yq ."$imagename"'_allowed_vulnerabilities[]' src/kayobe-config/etc/kayobe/grype/allowed-vulnerabilities.yml 2> /dev/null) | ||
|
|
||
| truncate -s 0 .trivyignore # ensure we start from a clean slate | ||
| echo "ignore:" > .grype.yaml |
There was a problem hiding this comment.
If both global_vulnerabilities and image_vulnerabilities are empty, the generated .grype.yaml will contain only ignore:. While valid YAML, some parsers might interpret this as a null value rather than an empty list, which could cause issues with Grype. It is safer to initialize it with an empty list ignore: [] or only create the file if there are vulnerabilities to ignore.
| echo "ignore:" > .grype.yaml | |
| echo "ignore: []" > .grype.yaml |
| .[0].Severity | ||
| (map(.artifact.name) | unique | join(";")), | ||
| (map(.artifact.locations[]?.path // empty) | unique | join(";")), | ||
| .[0].artifact.purl, |
There was a problem hiding this comment.
When multiple matches are grouped by vulnerability ID, they might correspond to different artifacts (packages). Using .[0].artifact.purl only captures the PURL of the first match in the group, potentially losing information about other affected packages. It is better to aggregate all unique PURLs in the group.
| .[0].artifact.purl, | |
| (map(.artifact.purl) | unique | join(";")), |
| (map(.artifact.locations[]?.path // empty) | unique | join(";")), | ||
| .[0].artifact.purl, | ||
| .[0].vulnerability.id, | ||
| (.[0].vulnerability.fix.versions | join(";")), |
There was a problem hiding this comment.
Similar to the PURL issue, different packages in the same vulnerability group might have different fixed versions. Additionally, if the fix object is null for a match, .[0].vulnerability.fix.versions will cause a jq error. Aggregating the versions and using the optional chaining operator ? is more robust.
| (.[0].vulnerability.fix.versions | join(";")), | |
| (map(.vulnerability.fix.versions[]?) | unique | join(";")), |
| .[0].vulnerability.id, | ||
| (.[0].vulnerability.fix.versions | join(";")), | ||
| (.[0].vulnerability.urls | first), | ||
| (.[0].vulnerability.severity | ascii_upcase) |
There was a problem hiding this comment.
If vulnerability.severity is missing or null, ascii_upcase will throw a jq error. Providing a default value ensures the script continues to run even if the scan data is incomplete.
| (.[0].vulnerability.severity | ascii_upcase) | |
| (.[0].vulnerability.severity // "unknown" | ascii_upcase) |
| syft "$image" \ | ||
| -o spdx-json \ | ||
| > "$sbom" 2> "$sbom.log" | ||
|
|
||
| if [ ! -s "$sbom" ]; then |
There was a problem hiding this comment.
Since set -e is active, if syft fails with a non-zero exit code, the script will terminate immediately. This prevents the custom error handling and log output starting at line 120 from being executed. Wrapping the command in an if statement or using || allows the script to catch the error and provide the intended diagnostic information.
| syft "$image" \ | |
| -o spdx-json \ | |
| > "$sbom" 2> "$sbom.log" | |
| if [ ! -s "$sbom" ]; then | |
| if ! syft "$image" -o spdx-json > "$sbom" 2> "$sbom.log" || [ ! -s "$sbom" ]; then | |
| ( | |
| echo "ERROR: syft failed or didn't produce the sbom file $sbom for $image" 1>&2 | |
| echo "==== syft log ====" | |
| cat "$sbom.log" | |
| ) 1>&2 | |
| exit 1 | |
| else |
Assisted-By: Gemini 3 Flash