@@ -3,34 +3,131 @@ name: Security Vulnerability Scan
33on :
44 workflow_dispatch :
55
6+ env :
7+ JAVA_VERSION : ' 11'
8+
69jobs :
710 grype-scan :
11+ name : " Java/Gradle Vulnerability Scan"
812 runs-on : ubuntu-latest
913
1014 steps :
11- - uses : actions/checkout@v3
15+ - name : Checkout repository
16+ uses : actions/checkout@v4
1217
13- - name : Setup Java version
14- uses : actions/setup-java@v3
18+ - name : Setup Java
19+ uses : actions/setup-java@v4
1520 with :
16- java-version : ' 11 '
21+ java-version : ${{ env.JAVA_VERSION }}
1722 distribution : ' zulu'
1823
19- - name : Generate CycloneDX SBOM
20- # Resolves all transitive dependencies and produces build/reports/bom.json
21- run : ./gradlew cyclonedxBom --no-daemon
24+ # Collect all runtime dependency JARs into .dep-jars/ inside the workspace.
25+ #
26+ # A one-off Gradle init script copies the runtimeClasspath configuration
27+ # directly — no cache-path guessing, works with any layout.
28+ # Keeping JARs inside $GITHUB_WORKSPACE is critical: anchore/sbom-action
29+ # mounts only the workspace into its container, so any path outside it
30+ # is silently invisible to syft.
31+ - name : Collect runtime dependency JARs
32+ id : collect-deps
33+ continue-on-error : true
34+ run : |
35+ mkdir -p .dep-jars
36+ [ -f "gradlew" ] && chmod +x gradlew
37+ GRADLEW=$([ -f "gradlew" ] && echo "./gradlew" || echo "gradle")
38+
39+ # Init script: copies runtimeClasspath (or nearest equivalent) for
40+ # every subproject into .dep-jars/ without touching build.gradle.
41+ # Written with printf to avoid the '<<' heredoc operator, which
42+ # GitHub's YAML parser misreads as a merge key inside a block scalar.
43+ printf '%s\n' \
44+ 'allprojects {' \
45+ " tasks.register('_copyDepsForScan') {" \
46+ ' doLast {' \
47+ " def cfg = project.configurations.findByName('runtimeClasspath') ?:" \
48+ " project.configurations.findByName('runtime') ?:" \
49+ " project.configurations.findByName('compileClasspath')" \
50+ ' if (cfg) {' \
51+ " def destDir = rootProject.file('.dep-jars')" \
52+ ' destDir.mkdirs()' \
53+ ' try {' \
54+ ' cfg.resolvedConfiguration.lenientConfiguration.artifacts' \
55+ ' .findAll { art ->' \
56+ " art.file.name.endsWith('.jar') &&" \
57+ " !art.file.name.endsWith('-sources.jar') &&" \
58+ " !art.file.name.endsWith('-javadoc.jar')" \
59+ ' }' \
60+ ' .each { art ->' \
61+ ' def dest = new File(destDir, art.file.name)' \
62+ ' java.nio.file.Files.copy(' \
63+ ' art.file.toPath(),' \
64+ ' dest.toPath(),' \
65+ ' java.nio.file.StandardCopyOption.REPLACE_EXISTING' \
66+ ' )' \
67+ ' }' \
68+ ' } catch (ignored) {}' \
69+ ' }' \
70+ ' }' \
71+ ' }' \
72+ '}' \
73+ > /tmp/copy-deps.init.gradle
74+
75+ $GRADLEW --no-daemon \
76+ --init-script /tmp/copy-deps.init.gradle \
77+ _copyDepsForScan 2>/dev/null || \
78+ $GRADLEW --no-daemon \
79+ --init-script /tmp/copy-deps.init.gradle \
80+ :_copyDepsForScan 2>/dev/null || true
81+
82+ JAR_COUNT=$(find .dep-jars -maxdepth 1 -name '*.jar' | wc -l | tr -d ' ')
83+ echo "jar_count=$JAR_COUNT" >> "$GITHUB_OUTPUT"
84+ echo "Collected $JAR_COUNT runtime JARs into .dep-jars/"
85+
86+ - name : Resolve scan target
87+ id : scan-target
88+ run : |
89+ JAR_COUNT="${{ steps.collect-deps.outputs.jar_count }}"
90+ if [ "${JAR_COUNT:-0}" -gt 0 ]; then
91+ echo "ref=.dep-jars" >> "$GITHUB_OUTPUT"
92+ echo "Scan target: .dep-jars (${JAR_COUNT} runtime JARs)"
93+ else
94+ echo "ref=." >> "$GITHUB_OUTPUT"
95+ echo "::warning::No runtime JARs collected — falling back to source scan (results may be incomplete)"
96+ fi
97+
98+ # Generate SBOM via syft (anchore/sbom-action).
99+ # syft reads META-INF/maven/*/pom.properties from each JAR to extract
100+ # precise GAV coordinates, correctly surfacing bundled libraries inside
101+ # fat/shaded JARs (e.g. testsigma-java-sdk's embedded jackson-databind).
102+ # This is more reliable than the Gradle CycloneDX plugin, which only
103+ # sees declared dependencies and misses shaded transitive content.
104+ - name : Generate Java SBOM (syft)
105+ uses : anchore/sbom-action@v0
106+ with :
107+ path : ${{ steps.scan-target.outputs.ref }}
108+ format : cyclonedx-json
109+ output-file : sbom-java.cdx.json
110+ artifact-name : sbom-java.cdx.json
22111
23112 - name : Install Grype
24113 run : curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
25114
26115 - name : Run Grype vulnerability scanner
27116 run : |
28- echo "=== Vulnerability table ==="
29- grype "sbom:build/reports/bom.json" \
30- --output table
117+ echo "=== Grype vulnerability table ==="
118+ grype "sbom:sbom-java.cdx.json" --output table
31119
32- grype "sbom:build/reports/bom.json" \
33- --output sarif > grype-results.sarif
120+ grype "sbom:sbom-java.cdx.json" --output sarif > grype-results.sarif
121+
122+ - name : Trivy Java scan (JSON)
123+ uses : aquasecurity/trivy-action@0.34.2
124+ with :
125+ scan-type : sbom
126+ scan-ref : sbom-java.cdx.json
127+ format : json
128+ output : trivy-java.json
129+ severity : LOW,MEDIUM,HIGH,CRITICAL
130+ exit-code : " 0"
34131
35132 - name : Upload scan results as artifact
36133 uses : actions/upload-artifact@v4
39136 name : grype-scan-results
40137 path : |
41138 grype-results.sarif
42- build/reports/bom.json
139+ sbom-java.cdx.json
140+ trivy-java.json
0 commit comments