From ddc7e9b934d6ddf50d221057611d4bedbbcfb38c Mon Sep 17 00:00:00 2001 From: Rundeck CI Date: Mon, 6 Apr 2026 20:49:36 -0700 Subject: [PATCH 1/9] Snyk 2.3.0 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cd6d7ca4..4ad21375 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ version: 2.1 orbs: - snyk: snyk/snyk@2.3.3 # https://github.com/snyk/snyk-orb/ + snyk: snyk/snyk@2.3.0 # https://github.com/snyk/snyk-orb/ jobs: build: From b58bd44ccfdb1bb16f55e4cb131209da5878e2f6 Mon Sep 17 00:00:00 2001 From: Rundeck CI Date: Mon, 6 Apr 2026 20:55:38 -0700 Subject: [PATCH 2/9] improve snyk scanning --- .circleci/config.yml | 27 +++++- scripts/convert_snyk_junit.sh | 177 ++++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+), 1 deletion(-) create mode 100755 scripts/convert_snyk_junit.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 4ad21375..b9fb16ce 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -68,7 +68,32 @@ jobs: else echo "Skipping monitor for non-main branch - no data sent to dashboard" fi - - run: snyk test --severity-threshold=high --all-projects --detection-depth=10 --scan-all-unmanaged || true # will NOT fail step if any results are found + - run: snyk test --severity-threshold=high --all-projects --detection-depth=10 --scan-all-unmanaged --json > snyk-test-results.json || true # will NOT fail step if any results are found + - run: + name: CVE Summary + command: | + if [ -f snyk-results.json ]; then + # Check if scan failed (error field present) + if jq -e '.error' snyk-results.json > /dev/null 2>&1; then + echo "Snyk scan failed - see snyk-results.json for details" + exit 0 + fi + echo "=== CVEs Found ===" + jq -r '.[] | select(type == "object") | select(.vulnerabilities != null) | .vulnerabilities[] | select(.identifiers.CVE | length > 0) | [(.identifiers.CVE | join(",")), .severity, .moduleName, .title] | @csv' snyk-results.json | sort -u | tee snyk-summary.csv || echo "No CVEs found - clean scan!" + echo "==================" + else + echo "No snyk-results.json found — skipping CVE summary" + fi + - run: + name: Convert Snyk results to JUnit XML + when: always + command: | + if [ -f snyk-results.json ]; then + mkdir -p test-results/junit + bash scripts/convert_snyk_junit.sh snyk-results.json > test-results/junit/snyk-junit.xml + fi + - store_test_results: + path: test-results workflows: version: 2 diff --git a/scripts/convert_snyk_junit.sh b/scripts/convert_snyk_junit.sh new file mode 100755 index 00000000..d558ec46 --- /dev/null +++ b/scripts/convert_snyk_junit.sh @@ -0,0 +1,177 @@ +#!/bin/bash +set -euo pipefail + +# Converts Snyk JSON output (from `snyk test --all-projects --json`) to JUnit XML +# for native display in CircleCI's Tests tab. +# +# Key behaviors: +# - Deduplicates findings by moduleName (package) across all scanned projects +# - Filters out license issues (snyk:lic: prefix), keeping only security vulnerabilities +# - Uses moduleName as classname for consistent CircleCI test tracking across runs +# - Aggregates all CVEs/CWEs/Snyk IDs for a package into one test case +# - Reports highest severity and highest CVSS score per package + +readonly IN=${1:-/dev/stdin} + +xml_escape() { + local s="$1" + s="${s//&/&}" + s="${s///>}" + s="${s//\"/"}" + s="${s//\'/'}" + echo "$s" +} + +emit_empty() { + echo '' +} + +convert_snyk_junit() { + local inputfile="$IN" + + # If reading from stdin, buffer to a temp file so we can read multiple times + if [ "$inputfile" = "/dev/stdin" ]; then + inputfile=$(mktemp) + cat > "$inputfile" + fi + + echo "input file: $IN" >&2 + + if ! jq empty < "$inputfile" 2>/dev/null; then + echo "ERROR: Invalid JSON in $IN" >&2 + emit_empty + return 0 + fi + + if jq -e 'type == "object" and has("error")' < "$inputfile" > /dev/null 2>&1; then + echo "Snyk scan had errors - generating empty JUnit output" >&2 + emit_empty + return 0 + fi + + local tmpfile + tmpfile=$(mktemp) + trap "rm -f '$tmpfile'" EXIT + + # Flatten all vulnerabilities across projects, filter out license issues, + # deduplicate by moduleName, and aggregate findings per package. + jq -c ' + def sev_rank: + if . == "critical" then 0 + elif . == "high" then 1 + elif . == "medium" then 2 + elif . == "low" then 3 + else 4 end; + + [ .[] | select(type == "object") | .vulnerabilities[]? ] + | map(select(.id | startswith("snyk:lic:") | not)) + | group_by(.moduleName) + | map({ + moduleName: .[0].moduleName, + versions: ([.[].version] | unique | sort), + snykIds: ([.[].id] | unique), + cves: [.[].identifiers.CVE[]?] | unique | sort, + cwes: [.[].identifiers.CWE[]?] | unique | sort, + titles: ([.[].title] | unique), + highestSeverity: ([ .[].severity ] | map({sev: ., rank: (. | sev_rank)}) | sort_by(.rank) | .[0].sev), + maxCvssScore: ([.[].cvssScore // 0] | max), + fixedIn: [.[].fixedIn[]?] | unique | sort, + isUpgradable: ([.[].isUpgradable] | any), + totalOccurrences: length + }) + | sort_by((.highestSeverity | sev_rank), .moduleName) + ' < "$inputfile" > "$tmpfile" + + local uniquePackages + uniquePackages=$(jq 'length // 0' < "$tmpfile") + uniquePackages=${uniquePackages:-0} + local totalOccurrences + totalOccurrences=$(jq '[.[].totalOccurrences] | add // 0' < "$tmpfile") + totalOccurrences=${totalOccurrences:-0} + + echo "uniquePackages: $uniquePackages (from $totalOccurrences total occurrences across projects)" >&2 + + echo '' + echo "" + + for sev in critical high medium low; do + local sevPkgs + sevPkgs=$(jq -c "[.[] | select(.highestSeverity == \"$sev\")]" < "$tmpfile") + local sevCount + sevCount=$(echo "$sevPkgs" | jq 'length') + sevCount=${sevCount:-0} + + if [ "$sevCount" -eq 0 ]; then + continue + fi + + echo " " + + echo "$sevPkgs" | jq -c '.[]' | while IFS= read -r pkg; do + local moduleName highestSev maxCvss isUpgradable totalOcc + moduleName=$(echo "$pkg" | jq -r '.moduleName') + highestSev=$(echo "$pkg" | jq -r '.highestSeverity') + maxCvss=$(echo "$pkg" | jq -r '.maxCvssScore') + isUpgradable=$(echo "$pkg" | jq -r '.isUpgradable') + totalOcc=$(echo "$pkg" | jq -r '.totalOccurrences') + + local versions snykIds cves cwes titles fixedIn + versions=$(echo "$pkg" | jq -r '.versions | join(", ")') + snykIds=$(echo "$pkg" | jq -r '.snykIds | join(", ")') + cves=$(echo "$pkg" | jq -r '.cves | join(", ")') + cwes=$(echo "$pkg" | jq -r '.cwes | join(", ")') + titles=$(echo "$pkg" | jq -r '.titles | join("; ")') + fixedIn=$(echo "$pkg" | jq -r '.fixedIn | join(", ")') + + local idLabel="" + if [ -n "$cves" ]; then + idLabel="$cves" + elif [ -n "$cwes" ]; then + idLabel="$cwes" + else + idLabel="$snykIds" + fi + + local sevUpper + sevUpper=$(echo "$highestSev" | tr '[:lower:]' '[:upper:]') + + local escapedName escapedClass escapedTitle + escapedName=$(xml_escape "${sevUpper} ${moduleName} (${totalOcc} findings) ${idLabel}") + escapedClass=$(xml_escape "$moduleName") + escapedTitle=$(xml_escape "$titles") + + cat < + + + + +END + done + + echo " " + done + + if [ "$uniquePackages" -eq 0 ]; then + echo " " + echo " " + echo " " + fi + + echo "" +} + +convert_snyk_junit From c0ffd2768001a5e2a440ddc9fbbe8db25a1004d8 Mon Sep 17 00:00:00 2001 From: Rundeck CI Date: Mon, 6 Apr 2026 20:59:39 -0700 Subject: [PATCH 3/9] bad auto-complete... bad --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b9fb16ce..797af2c7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -68,7 +68,7 @@ jobs: else echo "Skipping monitor for non-main branch - no data sent to dashboard" fi - - run: snyk test --severity-threshold=high --all-projects --detection-depth=10 --scan-all-unmanaged --json > snyk-test-results.json || true # will NOT fail step if any results are found + - run: snyk test --severity-threshold=high --all-projects --detection-depth=10 --scan-all-unmanaged --json > snyk-results.json || true # will NOT fail step if any results are found - run: name: CVE Summary command: | From 3115e8af939e2f34499411a4889af1accb6f3365 Mon Sep 17 00:00:00 2001 From: Rundeck CI Date: Mon, 6 Apr 2026 21:12:27 -0700 Subject: [PATCH 4/9] Jackson bump for CWE-770 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 48 ++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0932bd95..0bce0a7a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ shadow = "8.1.1" ospackage = "11.11.2" buildInfo = "0.9" buildConfig = "3.1.0" -jacksonDatabind = "2.21.1" +jacksonDatabind = "2.21.2" picocli = "4.7.7" snakeYaml = "2.0" #used for authz lib integration diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 072d09cf..d35678dd 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -172,6 +172,16 @@ + + + + + + + + + + @@ -182,6 +192,11 @@ + + + + + @@ -190,6 +205,17 @@ + + + + + + + + + + + @@ -198,6 +224,17 @@ + + + + + + + + + + + @@ -206,6 +243,17 @@ + + + + + + + + + + + From 3fd66746188c29143022d616b93c958c67e0a735 Mon Sep 17 00:00:00 2001 From: Rundeck CI Date: Tue, 7 Apr 2026 09:52:32 -0700 Subject: [PATCH 5/9] store test results --- .circleci/config.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 797af2c7..b9d88c06 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -94,6 +94,12 @@ jobs: fi - store_test_results: path: test-results + - store_artifacts: + path: snyk-results.json + destination: snyk-results.json + - store_artifacts: + path: snyk-summary.csv + destination: snyk-summary.csv # will NOT fail step if any results are found workflows: version: 2 From f4c10dc54692337c5df4d14684e1be654c3410dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:51:27 +0000 Subject: [PATCH 6/9] Fix CVE summary: use separate jq count check before displaying results Agent-Logs-Url: https://github.com/rundeck/rundeck-cli/sessions/ae9aebd5-dbbd-430d-815f-1187802fe114 Co-authored-by: gschueler <55603+gschueler@users.noreply.github.com> --- .circleci/config.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b9d88c06..85d95a42 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -78,9 +78,14 @@ jobs: echo "Snyk scan failed - see snyk-results.json for details" exit 0 fi - echo "=== CVEs Found ===" - jq -r '.[] | select(type == "object") | select(.vulnerabilities != null) | .vulnerabilities[] | select(.identifiers.CVE | length > 0) | [(.identifiers.CVE | join(",")), .severity, .moduleName, .title] | @csv' snyk-results.json | sort -u | tee snyk-summary.csv || echo "No CVEs found - clean scan!" - echo "==================" + cve_count=$(jq '[.[] | select(type == "object") | select(.vulnerabilities != null) | .vulnerabilities[] | select(.identifiers.CVE | length > 0)] | length' snyk-results.json) + if [ "$cve_count" -gt 0 ]; then + echo "=== CVEs Found ===" + jq -r '.[] | select(type == "object") | select(.vulnerabilities != null) | .vulnerabilities[] | select(.identifiers.CVE | length > 0) | [(.identifiers.CVE | join(",")), .severity, .moduleName, .title] | @csv' snyk-results.json | sort -u | tee snyk-summary.csv + echo "==================" + else + echo "No CVEs found - clean scan!" + fi else echo "No snyk-results.json found — skipping CVE summary" fi From 71b53d381611ebc89e65991722483ace0a1f4dc8 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Tue, 7 Apr 2026 10:56:47 -0700 Subject: [PATCH 7/9] TEST: downgrade jackson to verify snyk failure --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0bce0a7a..8907a6b0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ shadow = "8.1.1" ospackage = "11.11.2" buildInfo = "0.9" buildConfig = "3.1.0" -jacksonDatabind = "2.21.2" +jacksonDatabind = "2.21.0" picocli = "4.7.7" snakeYaml = "2.0" #used for authz lib integration From d294219a0651d69b5e454457615f4a11ba9ab7fe Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Tue, 7 Apr 2026 11:20:57 -0700 Subject: [PATCH 8/9] Summary selects vulns that don't have CVEs --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 85d95a42..284f463d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -78,10 +78,10 @@ jobs: echo "Snyk scan failed - see snyk-results.json for details" exit 0 fi - cve_count=$(jq '[.[] | select(type == "object") | select(.vulnerabilities != null) | .vulnerabilities[] | select(.identifiers.CVE | length > 0)] | length' snyk-results.json) + cve_count=$(jq '[.[] | select(type == "object") | select(.vulnerabilities != null) | .vulnerabilities[] | select( (.identifiers.CVE | length > 0 ) or (.identifiers.GHSA | length > 0) or (.identifiers.CWE | length > 0 ) )] | length' snyk-results.json) if [ "$cve_count" -gt 0 ]; then echo "=== CVEs Found ===" - jq -r '.[] | select(type == "object") | select(.vulnerabilities != null) | .vulnerabilities[] | select(.identifiers.CVE | length > 0) | [(.identifiers.CVE | join(",")), .severity, .moduleName, .title] | @csv' snyk-results.json | sort -u | tee snyk-summary.csv + jq -r '.[] | select(type == "object") | select(.vulnerabilities != null) | .vulnerabilities[] | select( (.identifiers.CVE | length > 0) or (.identifiers.GHSA | length > 0) or (.identifiers.CWE | length > 0 ) ) | [( [.identifiers.CVE,.identifiers.GHSA,.identifiers.CWE] | map(select(.!=null and .!=[])) | flatten | join(",")), .severity, .moduleName, .title] | @csv' snyk-results.json | sort -u | tee snyk-summary.csv echo "==================" else echo "No CVEs found - clean scan!" @@ -114,4 +114,4 @@ workflows: context: - PagerDuty - Snyk - - build \ No newline at end of file + - build From e062d2a5a29553edc345a818a1f3fd512ce45807 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Tue, 7 Apr 2026 11:27:28 -0700 Subject: [PATCH 9/9] Restore: update jackson version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8907a6b0..0bce0a7a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ shadow = "8.1.1" ospackage = "11.11.2" buildInfo = "0.9" buildConfig = "3.1.0" -jacksonDatabind = "2.21.0" +jacksonDatabind = "2.21.2" picocli = "4.7.7" snakeYaml = "2.0" #used for authz lib integration