In modern software development, security can't be an afterthought. Trivy has emerged as a comprehensive, easy-to-use vulnerability scanner that fits perfectly into CI/CD pipelines. For Java teams, integrating Trivy ensures that vulnerabilities in dependencies, container images, and infrastructure code are caught early, before they reach production.
Why Trivy for Java Applications?
Trivy's advantages for Java teams:
- Comprehensive scanning - JAR files, Maven/Gradle dependencies, Docker images, Kubernetes manifests
- Accuracy - Minimal false positives compared to other scanners
- Speed - Fast scanning suitable for CI pipeline constraints
- Zero-config - Works out of the box with sensible defaults
- Open source - No licensing costs or limitations
Trivy Scanning Targets for Java
- Dependency Scanning - Maven/Gradle dependencies for known CVEs
- Container Scanning - Docker images for OS packages and library vulnerabilities
- Infrastructure Scanning - Kubernetes manifests, Dockerfiles, and Helm charts
- SBOM Generation - Software Bill of Materials for compliance and audit
GitHub Actions Integration
1. Complete Java CI Pipeline with Trivy
# .github/workflows/ci-security.yml
name: Java CI with Trivy Security Scanning
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: 'maven'
- name: Run tests
run: mvn test
trivy-dependency-scan:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build application
run: mvn compile -DskipTests
- name: Run Trivy vulnerability scanner on dependencies
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results-dependency.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-results-dependency.sarif'
trivy-container-scan:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build Docker image
run: |
docker build -t my-java-app:${{ github.sha }} .
docker tag my-java-app:${{ github.sha }} my-java-app:latest
- name: Run Trivy container scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'image'
scan-ref: 'my-java-app:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results-container.sarif'
severity: 'CRITICAL,HIGH,MEDIUM'
- name: Upload container scan results
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-results-container.sarif'
security-gate:
runs-on: ubuntu-latest
needs: [trivy-dependency-scan, trivy-container-scan]
steps:
- name: Check Trivy results
run: |
echo "Security scans completed"
echo "Check GitHub Security tab for detailed results"
2. Advanced Security Pipeline with Quality Gates
# .github/workflows/security-scan.yml name: Advanced Security Scanning on: schedule: - cron: '0 6 * * 1' # Weekly on Monday workflow_dispatch: # Manual trigger jobs: comprehensive-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' cache: 'maven' - name: Build and test run: mvn clean compile test -DskipIntegrationTests - name: Download Trivy run: | curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.45.0 - name: FS Scan with custom config run: | trivy filesystem . \ --format json \ --output trivy-fs-report.json \ --severity CRITICAL,HIGH,MEDIUM \ --exit-code 0 \ --ignorefile .trivyignore - name: SBOM Generation run: | trivy filesystem . \ --format cyclonedx \ --output sbom.json - name: Build container image run: docker build -t my-java-app:security-scan . - name: Container Scan with threshold run: | trivy image \ --exit-code 1 \ --severity CRITICAL,HIGH \ --ignore-unfixed \ my-java-app:security-scan - name: Upload SBOM uses: actions/upload-artifact@v3 with: name: sbom path: sbom.json - name: Generate Security Report run: | echo "# Security Scan Report" > security-report.md echo "## Dependency Vulnerabilities" >> security-report.md trivy filesystem . --format template --template "@contrib/gitlab.tpl" --output security-report.md cat security-report.md
Jenkins Pipeline Integration
1. Jenkinsfile with Trivy Scanning
// Jenkinsfile
pipeline {
agent any
tools {
maven 'Maven-3.8'
jdk 'OpenJDK-17'
}
environment {
TRIVY_VERSION = '0.45.0'
DOCKER_IMAGE = "my-company/my-java-app:${env.BUILD_ID}"
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build and Test') {
steps {
sh 'mvn clean compile test'
}
}
stage('Dependency Security Scan') {
steps {
script {
// Download and install Trivy
sh """
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v${TRIVY_VERSION}
"""
// Scan dependencies
sh """
trivy filesystem . \
--format json \
--output trivy-dependency-report.json \
--severity CRITICAL,HIGH \
--exit-code 0
"""
// Parse results and set quality gate
def scanResults = readJSON file: 'trivy-dependency-report.json'
def criticalVulns = scanResults.Results?.findAll { it.Vulnerabilities?.any { it.Severity == 'CRITICAL' } }?.size() ?: 0
if (criticalVulns > 0) {
unstable "Found ${criticalVulns} critical vulnerabilities in dependencies"
}
}
}
post {
always {
archiveArtifacts artifacts: 'trivy-dependency-report.json', fingerprint: true
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: '.',
reportFiles: 'trivy-dependency-report.json',
reportName: 'Trivy Dependency Report'
])
}
}
}
stage('Build Container') {
steps {
sh "docker build -t ${DOCKER_IMAGE} ."
}
}
stage('Container Security Scan') {
steps {
script {
sh """
trivy image \
--format template \
--template @contrib/html.tpl \
--output trivy-container-report.html \
--exit-code 0 \
--severity CRITICAL,HIGH,MEDIUM \
${DOCKER_IMAGE}
"""
// Check for critical vulnerabilities that should fail build
sh """
trivy image \
--exit-code 1 \
--severity CRITICAL \
--ignore-unfixed \
${DOCKER_IMAGE} || echo "Critical vulnerabilities found - check report"
"""
}
}
post {
always {
archiveArtifacts artifacts: 'trivy-container-report.html', fingerprint: true
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: '.',
reportFiles: 'trivy-container-report.html',
reportName: 'Trivy Container Report'
])
}
}
}
stage('SBOM Generation') {
steps {
sh """
trivy image \
--format cyclonedx \
--output sbom-${env.BUILD_ID}.json \
${DOCKER_IMAGE}
"""
}
post {
always {
archiveArtifacts artifacts: "sbom-${env.BUILD_ID}.json", fingerprint: true
}
}
}
}
post {
always {
emailext (
subject: "Build ${currentBuild.result}: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: """
Security scan completed for ${env.JOB_NAME} build ${env.BUILD_NUMBER}
Build Result: ${currentBuild.result}
Build URL: ${env.BUILD_URL}
Check Trivy reports for vulnerability details.
""",
to: "${env.BUILD_USER_EMAIL}",
attachLog: true
)
}
}
}
GitLab CI Integration
1. .gitlab-ci.yml with Trivy
# .gitlab-ci.yml image: maven:3.8-openjdk-17 variables: MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository" stages: - test - security - build - container-security cache: paths: - .m2/repository - target/ test: stage: test script: - mvn clean test trivy-dependency-scan: stage: security image: aquasec/trivy:0.45.0 dependencies: [] script: - trivy filesystem . --format gitlab --output gl-dependency-scanning-report.json artifacts: reports: dependency_scanning: gl-dependency-scanning-report.json allow_failure: true trivy-container-scan: stage: container-security image: docker:latest services: - docker:dind variables: DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA DOCKER_TLS_CERTDIR: "/certs" before_script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY script: - docker build -t $DOCKER_IMAGE . - docker push $DOCK_IMAGE - | apk add --no-cache curl curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.45.0 - trivy image --exit-code 0 --format template --template "@contrib/gitlab.tpl" --output gl-container-scanning-report.json $DOCKER_IMAGE # Fail build on critical vulnerabilities - trivy image --exit-code 1 --severity CRITICAL --ignore-unfixed $DOCKER_IMAGE artifacts: reports: container_scanning: gl-container-scanning-report.json only: - main - develop generate-sbom: stage: security image: aquasec/trivy:0.45.0 script: - trivy filesystem . --format cyclonedx --output sbom.json artifacts: paths: - sbom.json expire_in: 1 week
Java-Specific Trivy Configuration
1. Custom Trivy Configuration File
# .trivy.yaml db: repository: aquasecurity/trivy-db skip-update: false cache: dir: /tmp/trivy scan: skip-dirs: - .git - node_modules - tmp skip-files: - package-lock.json - yarn.lock severity: - CRITICAL - HIGH - MEDIUM vulnerability: type: - os - library format: table ignorefile: .trivyignore exit-code: 1 ignore-policy: .trivyignorepolicy
2. Trivy Ignore File for Java
# .trivyignore # Ignore specific vulnerabilities with justification # Spring Framework vulnerability - mitigated by our usage pattern CVE-2022-22965 # Log4j 1.x - we use Log4j 2.x CVE-2021-44228 CVE-2021-45046 # Jackson Databind - false positive in our usage CVE-2020-36518 until=2024-12-31 # Acceptable risk for legacy library CVE-2019-12384
3. Custom Ignore Policies
# .trivyignorepolicy ignoreRules: - vulnerability: CVE-2023-12345 reason: "Vulnerability in test scope dependency" expires: "2024-12-31" - vulnerability: CVE-2023-54321 reason: "Mitigated by network configuration" expires: "never"
Java Application Security Helper
1. Maven Plugin Integration
// SecurityScanHelper.java
@Component
public class SecurityScanHelper {
private final ProcessBuilder processBuilder;
public SecurityScanHelper() {
this.processBuilder = new ProcessBuilder();
}
public ScanResult runDependencyScan() throws IOException, InterruptedException {
List<String> command = Arrays.asList(
"trivy", "filesystem", ".",
"--format", "json",
"--severity", "CRITICAL,HIGH",
"--exit-code", "0"
);
Process process = processBuilder.command(command).start();
String output = new String(process.getInputStream().readAllBytes());
String error = new String(process.getErrorStream().readAllBytes());
int exitCode = process.waitFor();
if (exitCode != 0 && exitCode != 1) { // Trivy returns 1 when vulnerabilities found
throw new SecurityScanException("Trivy scan failed: " + error);
}
return parseScanResults(output);
}
public ScanResult runContainerScan(String imageName) throws IOException, InterruptedException {
List<String> command = Arrays.asList(
"trivy", "image",
"--format", "json",
"--severity", "CRITICAL,HIGH,MEDIUM",
"--exit-code", "0",
imageName
);
Process process = processBuilder.command(command).start();
String output = new String(process.getInputStream().readAllBytes());
int exitCode = process.waitFor();
if (exitCode != 0 && exitCode != 1) {
throw new SecurityScanException("Container scan failed");
}
return parseScanResults(output);
}
public void generateSBOM(String outputPath) throws IOException, InterruptedException {
List<String> command = Arrays.asList(
"trivy", "filesystem", ".",
"--format", "cyclonedx",
"--output", outputPath
);
Process process = processBuilder.command(command).start();
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new SecurityScanException("SBOM generation failed");
}
}
private ScanResult parseScanResults(String jsonOutput) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(jsonOutput, ScanResult.class);
} catch (Exception e) {
throw new SecurityScanException("Failed to parse scan results", e);
}
}
public static class ScanResult {
private List< Vulnerability> vulnerabilities;
private int criticalCount;
private int highCount;
private int mediumCount;
// Getters and setters
public List<Vulnerability> getVulnerabilities() { return vulnerabilities; }
public void setVulnerabilities(List<Vulnerability> vulnerabilities) {
this.vulnerabilities = vulnerabilities;
this.criticalCount = (int) vulnerabilities.stream()
.filter(v -> "CRITICAL".equals(v.getSeverity())).count();
this.highCount = (int) vulnerabilities.stream()
.filter(v -> "HIGH".equals(v.getSeverity())).count();
this.mediumCount = (int) vulnerabilities.stream()
.filter(v -> "MEDIUM".equals(v.getSeverity())).count();
}
public boolean hasCriticalVulnerabilities() {
return criticalCount > 0;
}
public boolean hasHighOrCriticalVulnerabilities() {
return criticalCount > 0 || highCount > 0;
}
}
public static class Vulnerability {
private String vulnerabilityID;
private String pkgName;
private String installedVersion;
private String fixedVersion;
private String severity;
private String title;
private String description;
// Getters and setters
}
}
2. Secure Dockerfile for Java Applications
# Dockerfile FROM eclipse-temurin:17-jre-alpine@sha256:1234567890abcdef # Security: Run as non-root user RUN addgroup -S spring && adduser -S spring -G spring USER spring # Security: Copy application with correct permissions COPY --chown=spring:spring target/*.jar app.jar # Security: Use secure JVM options ENV JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom -XX:+UseContainerSupport" # Security: Expose on non-privileged port EXPOSE 8080 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1 ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app.jar"]
Quality Gates and Reporting
1. Security Quality Gate
@Component
public class SecurityQualityGate {
private final SecurityScanHelper scanHelper;
public SecurityQualityGate(SecurityScanHelper scanHelper) {
this.scanHelper = scanHelper;
}
public QualityGateResult evaluateBuild() {
try {
ScanResult dependencyScan = scanHelper.runDependencyScan();
ScanResult containerScan = scanHelper.runContainerScan("my-app:latest");
QualityGateResult result = new QualityGateResult();
result.setDependencyScan(dependencyScan);
result.setContainerScan(containerScan);
result.setPassed(evaluateResults(dependencyScan, containerScan));
return result;
} catch (Exception e) {
throw new QualityGateException("Security evaluation failed", e);
}
}
private boolean evaluateResults(ScanResult dependencyScan, ScanResult containerScan) {
// Fail quality gate if critical vulnerabilities found
if (dependencyScan.hasCriticalVulnerabilities() ||
containerScan.hasCriticalVulnerabilities()) {
return false;
}
// Warn but pass for high vulnerabilities (configurable threshold)
int totalHighVulns = dependencyScan.getHighCount() + containerScan.getHighCount();
if (totalHighVulns > 5) { // Configurable threshold
return false;
}
return true;
}
public String generateSecurityReport(QualityGateResult result) {
StringBuilder report = new StringBuilder();
report.append("# Security Quality Gate Report\n\n");
report.append("## Dependency Scan Results\n");
report.append("- Critical: ").append(result.getDependencyScan().getCriticalCount()).append("\n");
report.append("- High: ").append(result.getDependencyScan().getHighCount()).append("\n");
report.append("- Medium: ").append(result.getDependencyScan().getMediumCount()).append("\n");
report.append("\n## Container Scan Results\n");
report.append("- Critical: ").append(result.getContainerScan().getCriticalCount()).append("\n");
report.append("- High: ").append(result.getContainerScan().getHighCount()).append("\n");
report.append("- Medium: ").append(result.getContainerScan().getMediumCount()).append("\n");
report.append("\n## Quality Gate: ").append(result.isPassed() ? "PASSED ✅" : "FAILED ❌").append("\n");
return report.toString();
}
}
Best Practices for Trivy in Java CI
- Scan Early, Scan Often - Integrate scanning in development and CI
- Fail Build on Critical - Block deployments with critical vulnerabilities
- Use SBOMs - Generate Software Bill of Materials for compliance
- Regular Updates - Keep Trivy and vulnerability databases updated
- Custom Ignore Policies - Document and justify ignored vulnerabilities
- Multi-stage Scanning - Scan dependencies, containers, and infrastructure
- Security Gates - Implement quality gates based on vulnerability severity
- Reporting - Generate human-readable reports for different stakeholders
Conclusion
Integrating Trivy into Java CI pipelines transforms security from a periodic audit to a continuous, automated process. By catching vulnerabilities at every stage—dependencies, container images, and infrastructure—Java teams can significantly reduce their attack surface and maintain secure software delivery.
The key benefits include:
- Early Detection - Find vulnerabilities before they reach production
- Comprehensive Coverage - Scan all components of your Java application
- Developer-Friendly - Integrates seamlessly with existing CI/CD tools
- Actionable Results - Clear reporting with severity levels and remediation guidance
- Compliance Ready - SBOM generation for regulatory requirements
By implementing the patterns and configurations shown here, Java teams can build security into their development process, ensuring that every release meets organizational security standards without slowing down delivery.