Introduction
Software Composition Analysis (SCA) tools are essential for identifying security vulnerabilities, license compliance issues, and quality problems in third-party dependencies. This comprehensive guide covers integrating popular SCA tools into Java projects throughout the development lifecycle.
Popular SCA Tools Overview
Tool Comparison
/**
* SCA Tools Feature Matrix
*/
public enum SCATool {
OWASP_DEPENDENCY_CHECK("Dependency-Check", "Vulnerability scanning"),
SNYK("Snyk", "Vulnerability scanning, license compliance"),
SONATYPE_OSS_INDEX("Sonatype OSS Index", "Vulnerability scanning"),
BLACK_DUCK("Black Duck", "Comprehensive SCA"),
WHITESOURCE("WhiteSource", "Automated security & compliance"),
GITHUB_ADVISORIES("GitHub Advisory Database", "GitHub-native scanning"),
GITLAB_DEPENDENCY_SCANNING("GitLab", "Integrated dependency scanning"),
MAVEN_DEPENDENCY_PLUGIN("Maven Dependency Plugin", "Basic dependency analysis");
private final String name;
private final String primaryPurpose;
// Constructor, getters...
}
Maven Integration
OWASP Dependency-Check Setup
<!-- pom.xml configuration -->
<project>
<build>
<plugins>
<!-- OWASP Dependency Check Plugin -->
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>8.2.1</version>
<configuration>
<format>HTML</format>
<format>JSON</format>
<failBuildOnAnyVulnerability>true</failBuildOnAnyVulnerability>
<failOnCVSS>7</failOnCVSS>
<skipTestScope>true</skipTestScope>
<suppressionFiles>
<suppressionFile>dependency-check-suppressions.xml</suppressionFile>
</suppressionFiles>
<dataDirectory>${project.build.directory}/dependency-check-data</dataDirectory>
<cveValidForHours>24</cveValidForHours>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Maven Dependency Plugin for basic analysis -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<id>analyze</id>
<goals>
<goal>analyze-only</goal>
</goals>
<configuration>
<failOnWarning>true</failOnWarning>
<ignoreNonCompile>true</ignoreNonCompile>
</configuration>
</execution>
<execution>
<id>tree</id>
<goals>
<goal>tree</goal>
</goals>
</execution>
<execution>
<id>copy-dependencies</id>
<goals>
<goal>copy-dependencies</goal>
</goal>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- Reporting -->
<reporting>
<plugins>
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>8.2.1</version>
</plugin>
</plugins>
</reporting>
</project>
Advanced Maven Configuration
<!-- profiles for different environments -->
<profiles>
<profile>
<id>security-scan</id>
<build>
<plugins>
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<configuration>
<scanSet>
<scan>
<fileSet>
<directory>${project.build.directory}</directory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</scan>
</scanSet>
<retireJsAnalyzerEnabled>false</retireJsAnalyzerEnabled>
<nodeAnalyzerEnabled>false</nodeAnalyzerEnabled>
<nspAnalyzerEnabled>false</nspAnalyzerEnabled>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Gradle Integration
Gradle Build Configuration
// build.gradle
plugins {
id 'java'
id 'org.owasp.dependencycheck' version '8.2.1'
}
repositories {
mavenCentral()
}
dependencyCheck {
format = ['HTML', 'JSON', 'JUNIT']
failBuildOnCVSS = 7
suppressionFile = 'dependency-check-suppressions.xml'
analyzers {
assemblyEnabled = false
retirejs {
enabled = false
}
}
skipConfigurations = [
'checkstyle',
'pmd',
'codenarc'
]
}
// Custom task for security scanning
task securityScan(dependsOn: dependencyCheckAnalyze) {
doLast {
def report = file('build/reports/dependency-check-report.json')
if (report.exists()) {
def vulnerabilities = parseVulnerabilityReport(report)
generateSecurityReport(vulnerabilities)
}
}
}
// Dependency analysis task
task dependencyAnalysis {
doLast {
def dependencies = configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts
dependencies.each { dep ->
println "Dependency: ${dep.moduleVersion.id}"
println " File: ${dep.file.name}"
println " Size: ${dep.file.length()} bytes"
}
}
}
Advanced Gradle Configuration
// build.gradle continued
import groovy.json.JsonSlurper
def parseVulnerabilityReport(File reportFile) {
def json = new JsonSlurper().parse(reportFile)
return json.dependencies.findAll { dep ->
dep.vulnerabilities && !dep.vulnerabilities.empty
}
}
def generateSecurityReport(vulnerabilities) {
def reportFile = file('build/reports/security-summary.txt')
reportFile.parentFile.mkdirs()
reportFile.withWriter { writer ->
writer.println "Security Vulnerability Summary"
writer.println "Generated: ${new Date()}"
writer.println "=" * 50
vulnerabilities.each { dep ->
writer.println "Dependency: ${dep.fileName}"
writer.println "Package: ${dep.packages[0].id}"
dep.vulnerabilities.each { vuln ->
writer.println " Vulnerability: ${vuln.name}"
writer.println " Severity: ${vuln.severity}"
writer.println " CVSS Score: ${vuln.cvssv3?.baseScore ?: vuln.cvssv2?.score}"
writer.println " Description: ${vuln.description?.take(100)}..."
}
writer.println()
}
}
}
// Custom configuration for different environments
configurations {
securityScan {
description = 'Configuration for security scanning dependencies'
}
}
dependencies {
securityScan 'org.owasp.dependency-check:org.owasp.dependencycheck.gradle:8.2.1'
}
CI/CD Pipeline Integration
Jenkins Pipeline
// Jenkinsfile
pipeline {
agent any
tools {
maven 'Maven-3.8.4'
jdk 'JDK-17'
}
stages {
stage('Build') {
steps {
sh 'mvn clean compile'
}
}
stage('Dependency Check') {
steps {
sh 'mvn org.owasp:dependency-check-maven:check'
}
post {
always {
dependencyCheckPublisher pattern: '**/dependency-check-report.xml'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'target',
reportFiles: 'dependency-check-report.html',
reportName: 'Dependency Check Report'
])
}
}
}
stage('Security Gate') {
steps {
script {
def report = readJSON file: 'target/dependency-check-report.json'
def criticalVulnerabilities = report.dependencies.count { dep ->
dep.vulnerabilities.any { vuln ->
vuln.severity == 'CRITICAL'
}
}
if (criticalVulnerabilities > 0) {
error "Build failed: ${criticalVulnerabilities} critical vulnerabilities found"
}
}
}
}
}
post {
always {
emailext (
subject: "Dependency Scan Results: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: """
Dependency Scan completed for ${env.JOB_NAME}
Build: ${env.BUILD_URL}
Report: ${env.BUILD_URL}/dependency-check-report/
""",
to: '[email protected]'
)
}
}
}
GitHub Actions Workflow
# .github/workflows/security-scan.yml
name: Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 2 * * 1' # Weekly on Monday at 2 AM
jobs:
dependency-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Cache OWASP data
uses: actions/cache@v3
with:
path: ~/.dependency-check
key: ${{ runner.os }}-dependency-check-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-dependency-check-
- name: Run OWASP Dependency Check
run: mvn org.owasp:dependency-check-maven:check -DfailBuildOnCVSS=7
- name: Upload Dependency Check Report
uses: actions/upload-artifact@v3
with:
name: dependency-check-report
path: target/dependency-check-report.*
- name: Run Snyk Security Scan
uses: snyk/actions/maven@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high --fail-on=upgradable
- name: Check for license compliance
run: |
mvn org.codehaus.mojo:license-maven-plugin:2.0.0:download-licenses
mvn org.codehaus.mojo:license-maven-plugin:2.0.0:check
sonatype-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Sonatype Scan
uses: sonatype-nexus-community/scan-github-action@main
with:
username: ${{ secrets.SONATYPE_USERNAME }}
token: ${{ secrets.SONATYPE_TOKEN }}
notify:
needs: [dependency-scan, sonatype-scan]
runs-on: ubuntu-latest
if: always()
steps:
- name: Notify Security Team
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: Dependency scan completed for ${{ github.repository }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Custom SCA Integration Framework
Java-based SCA Manager
package com.company.sca;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import java.io.File;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
@Component
public class SCAManager {
private final ObjectMapper objectMapper;
private final List<VulnerabilityScanner> scanners;
private final ReportGenerator reportGenerator;
public SCAManager(ObjectMapper objectMapper,
List<VulnerabilityScanner> scanners,
ReportGenerator reportGenerator) {
this.objectMapper = objectMapper;
this.scanners = scanners;
this.reportGenerator = reportGenerator;
}
public ScanResult performSecurityScan(ProjectContext context) {
List<DependencyVulnerability> allVulnerabilities = new ArrayList<>();
Map<String, ScanToolResult> toolResults = new HashMap<>();
for (VulnerabilityScanner scanner : scanners) {
try {
ScanToolResult result = scanner.scan(context);
toolResults.put(scanner.getName(), result);
allVulnerabilities.addAll(result.getVulnerabilities());
} catch (ScanException e) {
System.err.println("Scanner " + scanner.getName() + " failed: " + e.getMessage());
}
}
return new ScanResult(allVulnerabilities, toolResults, context);
}
public ComplianceReport checkLicenseCompliance(ProjectContext context) {
Set<Dependency> dependencies = extractDependencies(context);
List<LicenseViolation> violations = new ArrayList<>();
for (Dependency dependency : dependencies) {
if (!isLicenseAllowed(dependency.getLicense())) {
violations.add(new LicenseViolation(dependency,
"License " + dependency.getLicense() + " is not allowed"));
}
}
return new ComplianceReport(violations, dependencies);
}
public void generateReports(ScanResult scanResult,
ComplianceReport complianceReport,
Path outputDir) {
reportGenerator.generateHtmlReport(scanResult, complianceReport, outputDir);
reportGenerator.generateJsonReport(scanResult, complianceReport, outputDir);
reportGenerator.generateMarkdownReport(scanResult, complianceReport, outputDir);
}
}
// Core domain models
class ProjectContext {
private final Path projectRoot;
private final BuildTool buildTool;
private final List<Path> dependencyFiles;
public ProjectContext(Path projectRoot, BuildTool buildTool) {
this.projectRoot = projectRoot;
this.buildTool = buildTool;
this.dependencyFiles = discoverDependencyFiles(projectRoot, buildTool);
}
private List<Path> discoverDependencyFiles(Path projectRoot, BuildTool tool) {
// Implementation to find pom.xml, build.gradle, etc.
return Collections.emptyList();
}
// Getters...
}
class Dependency {
private final String groupId;
private final String artifactId;
private final String version;
private final String license;
private final List<String> vulnerabilities;
public Dependency(String groupId, String artifactId, String version,
String license, List<String> vulnerabilities) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
this.license = license;
this.vulnerabilities = vulnerabilities;
}
// Getters, equals, hashCode...
}
class DependencyVulnerability {
private final Dependency dependency;
private final String cveId;
private final Severity severity;
private final double cvssScore;
private final String description;
private final List<String> fixedVersions;
public DependencyVulnerability(Dependency dependency, String cveId,
Severity severity, double cvssScore,
String description, List<String> fixedVersions) {
this.dependency = dependency;
this.cveId = cveId;
this.severity = severity;
this.cvssScore = cvssScore;
this.description = description;
this.fixedVersions = fixedVersions;
}
// Getters...
}
enum Severity {
CRITICAL, HIGH, MEDIUM, LOW, INFO
}
enum BuildTool {
MAVEN, GRADLE, SBT, IVY
}
Vulnerability Scanner Interface
package com.company.sca;
import java.util.List;
public interface VulnerabilityScanner {
String getName();
String getVersion();
ScanToolResult scan(ProjectContext context) throws ScanException;
boolean isEnabled();
void configure(ScannerConfiguration config);
}
@Component
public class OWASPScanner implements VulnerabilityScanner {
private final ProcessExecutor processExecutor;
private final ReportParser reportParser;
public OWASPScanner(ProcessExecutor processExecutor, ReportParser reportParser) {
this.processExecutor = processExecutor;
this.reportParser = reportParser;
}
@Override
public String getName() {
return "OWASP Dependency Check";
}
@Override
public String getVersion() {
return "8.2.1";
}
@Override
public ScanToolResult scan(ProjectContext context) throws ScanException {
try {
// Execute OWASP Dependency Check
List<String> command = buildCommand(context);
ProcessResult result = processExecutor.execute(command, context.getProjectRoot());
if (result.getExitCode() != 0) {
throw new ScanException("OWASP scan failed: " + result.getErrorOutput());
}
// Parse the generated report
Path reportPath = context.getProjectRoot().resolve("target/dependency-check-report.json");
return reportParser.parseOWASPReport(reportPath, context);
} catch (Exception e) {
throw new ScanException("OWASP scan execution failed", e);
}
}
private List<String> buildCommand(ProjectContext context) {
List<String> command = new ArrayList<>();
command.add("mvn");
command.add("org.owasp:dependency-check-maven:check");
command.add("-Dformat=JSON");
command.add("-DfailBuildOnCVSS=7");
command.add("-DskipTestScope=true");
return command;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public void configure(ScannerConfiguration config) {
// Configure scanner with custom settings
}
}
@Component
public class SnykScanner implements VulnerabilityScanner {
private final SnykClient snykClient;
private final String apiToken;
public SnykScanner(SnykClient snykClient, @Value("${snyk.api.token}") String apiToken) {
this.snykClient = snykClient;
this.apiToken = apiToken;
}
@Override
public ScanToolResult scan(ProjectContext context) throws ScanException {
try {
// Authenticate with Snyk
snykClient.authenticate(apiToken);
// Test the project
SnykTestResult testResult = snykClient.testProject(context.getProjectRoot());
// Convert to common format
return convertToScanResult(testResult, context);
} catch (SnykException e) {
throw new ScanException("Snyk scan failed", e);
}
}
// Other interface methods...
}
Report Generation
package com.company.sca;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component
public class ReportGenerator {
private final ObjectMapper objectMapper;
private final TemplateEngine templateEngine;
public ReportGenerator(ObjectMapper objectMapper, TemplateEngine templateEngine) {
this.objectMapper = objectMapper;
this.templateEngine = templateEngine;
}
public void generateHtmlReport(ScanResult scanResult,
ComplianceReport complianceReport,
Path outputDir) throws ReportGenerationException {
try {
Map<String, Object> model = createReportModel(scanResult, complianceReport);
String htmlContent = templateEngine.process("security-report", model);
Path htmlReport = outputDir.resolve("security-report.html");
Files.write(htmlReport, htmlContent.getBytes());
} catch (Exception e) {
throw new ReportGenerationException("Failed to generate HTML report", e);
}
}
public void generateJsonReport(ScanResult scanResult,
ComplianceReport complianceReport,
Path outputDir) throws ReportGenerationException {
try {
Map<String, Object> consolidatedReport = Map.of(
"scanResult", scanResult,
"complianceReport", complianceReport,
"generatedAt", java.time.Instant.now(),
"summary", generateSummary(scanResult, complianceReport)
);
Path jsonReport = outputDir.resolve("security-report.json");
objectMapper.writerWithDefaultPrettyPrinter()
.writeValue(jsonReport.toFile(), consolidatedReport);
} catch (IOException e) {
throw new ReportGenerationException("Failed to generate JSON report", e);
}
}
public void generateMarkdownReport(ScanResult scanResult,
ComplianceReport complianceReport,
Path outputDir) throws ReportGenerationException {
try {
StringBuilder markdown = new StringBuilder();
markdown.append("# Security Scan Report\n\n");
markdown.append("Generated: ").append(java.time.LocalDateTime.now()).append("\n\n");
// Vulnerabilities section
markdown.append("## Vulnerabilities\n\n");
List<DependencyVulnerability> criticalVulns = getCriticalVulnerabilities(scanResult);
if (criticalVulns.isEmpty()) {
markdown.append("✅ No critical vulnerabilities found\n\n");
} else {
markdown.append("❌ ").append(criticalVulns.size()).append(" critical vulnerabilities found:\n\n");
for (DependencyVulnerability vuln : criticalVulns) {
markdown.append("### ").append(vuln.getCveId()).append("\n");
markdown.append("- **Dependency**: ").append(vuln.getDependency().getArtifactId()).append("\n");
markdown.append("- **Severity**: ").append(vuln.getSeverity()).append("\n");
markdown.append("- **CVSS Score**: ").append(vuln.getCvssScore()).append("\n");
markdown.append("- **Fixed Versions**: ").append(String.join(", ", vuln.getFixedVersions())).append("\n\n");
}
}
// License compliance section
markdown.append("## License Compliance\n\n");
if (complianceReport.getViolations().isEmpty()) {
markdown.append("✅ All dependencies have approved licenses\n\n");
} else {
markdown.append("⚠️ ").append(complianceReport.getViolations().size()).append(" license violations found\n\n");
}
Path mdReport = outputDir.resolve("SECURITY.md");
Files.write(mdReport, markdown.toString().getBytes());
} catch (IOException e) {
throw new ReportGenerationException("Failed to generate Markdown report", e);
}
}
private Map<String, Object> createReportModel(ScanResult scanResult,
ComplianceReport complianceReport) {
return Map.of(
"scanResult", scanResult,
"complianceReport", complianceReport,
"criticalVulnerabilities", getCriticalVulnerabilities(scanResult),
"highVulnerabilities", getHighVulnerabilities(scanResult),
"totalDependencies", countTotalDependencies(complianceReport),
"generationTime", java.time.LocalDateTime.now(),
"projectName", scanResult.getContext().getProjectRoot().getFileName().toString()
);
}
private List<DependencyVulnerability> getCriticalVulnerabilities(ScanResult scanResult) {
return scanResult.getVulnerabilities().stream()
.filter(v -> v.getSeverity() == Severity.CRITICAL)
.sorted(Comparator.comparing(DependencyVulnerability::getCvssScore).reversed())
.collect(Collectors.toList());
}
private List<DependencyVulnerability> getHighVulnerabilities(ScanResult scanResult) {
return scanResult.getVulnerabilities().stream()
.filter(v -> v.getSeverity() == Severity.HIGH)
.sorted(Comparator.comparing(DependencyVulnerability::getCvssScore).reversed())
.collect(Collectors.toList());
}
private long countTotalDependencies(ComplianceReport complianceReport) {
return complianceReport.getDependencies().size();
}
private Map<String, Object> generateSummary(ScanResult scanResult,
ComplianceReport complianceReport) {
Map<Severity, Long> vulnCounts = scanResult.getVulnerabilities().stream()
.collect(Collectors.groupingBy(DependencyVulnerability::getSeverity,
Collectors.counting()));
return Map.of(
"totalVulnerabilities", scanResult.getVulnerabilities().size(),
"vulnerabilityCounts", vulnCounts,
"licenseViolations", complianceReport.getViolations().size(),
"totalDependencies", complianceReport.getDependencies().size(),
"scanSuccess", true
);
}
}
Advanced Configuration and Suppression
Suppression Files
<!-- dependency-check-suppressions.xml --> <?xml version="1.0" encoding="UTF-8"?> <suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd"> <!-- Suppress false positives --> <suppress> <notes><![CDATA[ False positive - CVE affects different component ]]></notes> <cve>CVE-2022-31129</cve> </suppress> <!-- Suppress by package --> <suppress> <notes><![CDATA[ This vulnerability is not exploitable in our usage context ]]></notes> <packageUrl regex="true">^pkg:maven/com\.fasterxml\.jackson\.core/jackson-databind@.*$</packageUrl> <cve>CVE-2022-42003</cve> </suppress> <!-- Suppress until fixed version is available --> <suppress until="2023-12-31"> <notes><![CDATA[ No fixed version available yet, suppress until 2023-12-31 ]]></notes> <packageUrl regex="true">^pkg:maven/org\.springframework/spring-core@.*$</packageUrl> <vulnerabilityName regex="true">CVE-2023-.*</vulnerabilityName> </suppress> </suppressions>
Custom Security Policies
package com.company.sca.policy;
import com.company.sca.Dependency;
import com.company.sca.Severity;
import com.company.sca.DependencyVulnerability;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
public class SecurityPolicy {
private final Set<String> allowedLicenses;
private final Set<String> bannedDependencies;
private final double maxAllowedCVSS;
private final boolean failOnCritical;
private final List<Pattern> allowedCVEPatterns;
public SecurityPolicy(Set<String> allowedLicenses,
Set<String> bannedDependencies,
double maxAllowedCVSS,
boolean failOnCritical,
List<Pattern> allowedCVEPatterns) {
this.allowedLicenses = allowedLicenses;
this.bannedDependencies = bannedDependencies;
this.maxAllowedCVSS = maxAllowedCVSS;
this.failOnCritical = failOnCritical;
this.allowedCVEPatterns = allowedCVEPatterns;
}
public PolicyViolation checkDependency(Dependency dependency) {
// Check for banned dependencies
if (bannedDependencies.contains(dependency.getArtifactId())) {
return new PolicyViolation(PolicyViolation.Type.BANNED_DEPENDENCY,
"Dependency " + dependency.getArtifactId() + " is banned");
}
// Check license compliance
if (!allowedLicenses.contains(dependency.getLicense())) {
return new PolicyViolation(PolicyViolation.Type.LICENSE_VIOLATION,
"License " + dependency.getLicense() + " is not allowed");
}
return null; // No violation
}
public PolicyViolation checkVulnerability(DependencyVulnerability vulnerability) {
// Check CVSS score
if (vulnerability.getCvssScore() > maxAllowedCVSS) {
return new PolicyViolation(PolicyViolation.Type.HIGH_SEVERITY_VULNERABILITY,
"Vulnerability " + vulnerability.getCveId() +
" has CVSS score " + vulnerability.getCvssScore() +
" exceeding maximum allowed " + maxAllowedCVSS);
}
// Check for critical vulnerabilities
if (failOnCritical && vulnerability.getSeverity() == Severity.CRITICAL) {
return new PolicyViolation(PolicyViolation.Type.CRITICAL_VULNERABILITY,
"Critical vulnerability " + vulnerability.getCveId() + " found");
}
// Check if CVE is allowed
if (isCVEAllowed(vulnerability.getCveId())) {
return null; // This CVE is explicitly allowed
}
return null; // No violation
}
private boolean isCVEAllowed(String cveId) {
return allowedCVEPatterns.stream()
.anyMatch(pattern -> pattern.matcher(cveId).matches());
}
// Builder pattern for easy policy creation
public static class Builder {
private Set<String> allowedLicenses = Set.of("Apache-2.0", "MIT", "BSD-3-Clause");
private Set<String> bannedDependencies = Set.of();
private double maxAllowedCVSS = 7.0;
private boolean failOnCritical = true;
private List<Pattern> allowedCVEPatterns = List.of();
public Builder withAllowedLicenses(Set<String> licenses) {
this.allowedLicenses = licenses;
return this;
}
public Builder withBannedDependencies(Set<String> banned) {
this.bannedDependencies = banned;
return this;
}
public Builder withMaxCVSS(double maxCVSS) {
this.maxAllowedCVSS = maxCVSS;
return this;
}
public Builder failOnCritical(boolean fail) {
this.failOnCritical = fail;
return this;
}
public Builder withAllowedCVEPatterns(List<Pattern> patterns) {
this.allowedCVEPatterns = patterns;
return this;
}
public SecurityPolicy build() {
return new SecurityPolicy(allowedLicenses, bannedDependencies,
maxAllowedCVSS, failOnCritical, allowedCVEPatterns);
}
}
}
class PolicyViolation {
enum Type {
BANNED_DEPENDENCY,
LICENSE_VIOLATION,
HIGH_SEVERITY_VULNERABILITY,
CRITICAL_VULNERABILITY
}
private final Type type;
private final String message;
public PolicyViolation(Type type, String message) {
this.type = type;
this.message = message;
}
// Getters...
}
Monitoring and Alerting
Integration with Monitoring Systems
package com.company.sca.monitoring;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class SCAMetrics {
private final Counter scanCounter;
private final Counter vulnerabilityCounter;
private final Timer scanTimer;
private final Counter policyViolationCounter;
public SCAMetrics(MeterRegistry registry) {
this.scanCounter = Counter.builder("sca.scan.total")
.description("Total number of SCA scans performed")
.register(registry);
this.vulnerabilityCounter = Counter.builder("sca.vulnerabilities.total")
.description("Total vulnerabilities found")
.tag("severity", "critical")
.register(registry);
this.scanTimer = Timer.builder("sca.scan.duration")
.description("Time taken for SCA scans")
.register(registry);
this.policyViolationCounter = Counter.builder("sca.policy.violations")
.description("Policy violations detected")
.register(registry);
}
public void recordScan(long durationMs, int vulnerabilityCount, int criticalCount) {
scanCounter.increment();
scanTimer.record(durationMs, TimeUnit.MILLISECONDS);
vulnerabilityCounter.increment(vulnerabilityCount);
if (criticalCount > 0) {
// You could have separate counters for different severity levels
}
}
public void recordPolicyViolation() {
policyViolationCounter.increment();
}
}
@Component
public class AlertManager {
private final NotificationService notificationService;
private final SCAMetrics metrics;
public AlertManager(NotificationService notificationService, SCAMetrics metrics) {
this.notificationService = notificationService;
this.metrics = metrics;
}
public void alertOnCriticalVulnerabilities(ScanResult scanResult) {
long criticalCount = scanResult.getVulnerabilities().stream()
.filter(v -> v.getSeverity() == Severity.CRITICAL)
.count();
if (criticalCount > 0) {
String message = String.format(
"🚨 CRITICAL: %d critical vulnerabilities found in project %s",
criticalCount, scanResult.getContext().getProjectRoot().getFileName());
notificationService.sendAlert("Security Team", message, AlertLevel.CRITICAL);
metrics.recordPolicyViolation();
}
}
public void alertOnLicenseViolations(ComplianceReport complianceReport) {
if (!complianceReport.getViolations().isEmpty()) {
String message = String.format(
"⚠️ LICENSE: %d license violations detected",
complianceReport.getViolations().size());
notificationService.sendAlert("Legal Team", message, AlertLevel.WARNING);
metrics.recordPolicyViolation();
}
}
}
enum AlertLevel {
INFO, WARNING, CRITICAL
}
Best Practices and Conclusion
Implementation Checklist
- Integrate SCA tools early in the development lifecycle
- Configure automatic scanning on every build and PR
- Set appropriate severity thresholds for your organization
- Maintain suppression files for false positives
- Implement security gates in CI/CD pipelines
- Generate comprehensive reports for different stakeholders
- Monitor scan results and set up alerts for critical findings
- Regularly update SCA tools and their databases
- Train development teams on addressing vulnerabilities
- Establish clear policies for dependency usage and security
Key Benefits
- Early vulnerability detection before reaching production
- License compliance assurance
- Automated security governance
- Comprehensive visibility into dependency risks
- Integration with existing DevOps workflows
By implementing these SCA integration patterns, organizations can significantly improve their software security posture while maintaining development velocity and compliance requirements.