Skip to content

Commit 7a9fa46

Browse files
fix: enhance security scan with JAR collection, syft SBOM, and Trivy
Replace the Gradle CycloneDX plugin approach with a proper multi-stage scan: - Add Gradle init-script step that copies runtimeClasspath JARs into .dep-jars/ inside the workspace (no cache-path guessing, workspace-mounted so syft sees them) - Add scan-target fallback step that warns and falls back to source scan if no JARs - Switch SBOM generation to anchore/sbom-action (syft), which reads META-INF/maven/*/pom.properties from each JAR — correctly surfaces bundled dependencies inside fat/shaded JARs (e.g. testsigma-java-sdk's embedded jackson-databind 2.12.1 that the Gradle plugin missed) - Feed the syft SBOM into both Grype (SARIF) and Trivy (JSON) for dual-scanner coverage - Remove org.cyclonedx.bom Gradle plugin; SBOM generation is now CI-only Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 846d691 commit 7a9fa46

2 files changed

Lines changed: 111 additions & 14 deletions

File tree

.github/workflows/security-scan.yml

Lines changed: 111 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,131 @@ name: Security Vulnerability Scan
33
on:
44
workflow_dispatch:
55

6+
env:
7+
JAVA_VERSION: '11'
8+
69
jobs:
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
@@ -39,4 +136,5 @@ jobs:
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

build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ plugins {
1717
id 'maven-publish'
1818
id 'signing'
1919
id 'com.vanniktech.maven.publish' version '0.34.0'
20-
id 'org.cyclonedx.bom' version '1.10.0'
2120
}
2221

2322
repositories {

0 commit comments

Comments
 (0)