Introduction to OSS Index
OSS Index is a free service by Sonatype that provides vulnerability information for open-source software components. It helps identify security vulnerabilities in your project dependencies, similar to services like Snyk and GitHub Security Advisories.
Table of Contents
- OSS Index Integration Approaches
- Maven Plugin Implementation
- Gradle Plugin Implementation
- REST API Client
- Dependency Analysis
- CI/CD Integration
- Enterprise Vulnerability Scanner
OSS Index Integration Approaches
1. REST API Integration
2. Maven Plugin
3. Gradle Plugin
4. Standalone Scanner
Maven Dependencies
<dependencies> <!-- REST Client --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <!-- Maven Integration --> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-core</artifactId> <version>3.9.5</version> </dependency> <!-- Gradle Tooling API --> <dependency> <groupId>org.gradle</groupId> <artifactId>gradle-tooling-api</artifactId> <version>8.4</version> </dependency> <!-- Cache Implementation --> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>3.1.8</version> </dependency> <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.9</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>2.0.9</version> </dependency> </dependencies>
REST API Client Implementation
Core OSS Index Client
package com.security.ossindex;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class OSSIndexClient {
private static final String OSS_INDEX_API_URL = "https://ossindex.sonatype.org/api/v3/component-report";
private static final int BATCH_SIZE = 128; // OSS Index batch limit
private final CloseableHttpClient httpClient;
private final ObjectMapper objectMapper;
private final String userAgent;
private final RateLimiter rateLimiter;
public OSSIndexClient() {
this(HttpClients.createDefault(), "Java-OSS-Index-Client/1.0");
}
public OSSIndexClient(CloseableHttpClient httpClient, String userAgent) {
this.httpClient = httpClient;
this.objectMapper = new ObjectMapper();
this.userAgent = userAgent;
this.rateLimiter = new RateLimiter(100, TimeUnit.MINUTES); // 100 requests per minute
}
public List<ComponentReport> getVulnerabilityReports(List<Component> components)
throws OSSIndexException {
List<ComponentReport> allReports = new ArrayList<>();
// Process components in batches
for (int i = 0; i < components.size(); i += BATCH_SIZE) {
List<Component> batch = components.subList(i,
Math.min(i + BATCH_SIZE, components.size()));
List<ComponentReport> batchReports = getBatchVulnerabilityReports(batch);
allReports.addAll(batchReports);
// Respect rate limits
rateLimiter.acquire();
}
return allReports;
}
private List<ComponentReport> getBatchVulnerabilityReports(List<Component> components)
throws OSSIndexException {
try {
HttpPost request = new HttpPost(OSS_INDEX_API_URL);
request.setHeader("User-Agent", userAgent);
request.setHeader("Content-Type", "application/json");
request.setHeader("Accept", "application/json");
// Build request payload
List<Map<String, String>> coordinates = new ArrayList<>();
for (Component component : components) {
coordinates.add(Map.of(
"coordinates", component.getCoordinates()
));
}
String requestBody = objectMapper.writeValueAsString(coordinates);
request.setEntity(new StringEntity(requestBody));
try (CloseableHttpResponse response = httpClient.execute(request)) {
int statusCode = response.getCode();
String responseBody = EntityUtils.toString(response.getEntity());
if (statusCode == 200) {
ComponentReport[] reports = objectMapper.readValue(
responseBody, ComponentReport[].class);
return Arrays.asList(reports);
} else if (statusCode == 429) {
throw new RateLimitExceededException("Rate limit exceeded");
} else {
throw new OSSIndexException(
"OSS Index API returned status: " + statusCode + " - " + responseBody);
}
}
} catch (RateLimitExceededException e) {
throw e;
} catch (Exception e) {
throw new OSSIndexException("Failed to get vulnerability reports", e);
}
}
public ComponentReport getVulnerabilityReport(Component component)
throws OSSIndexException {
List<ComponentReport> reports = getVulnerabilityReports(List.of(component));
return reports.isEmpty() ? null : reports.get(0);
}
public void close() throws Exception {
httpClient.close();
}
// Data classes
public static class Component {
private final String groupId;
private final String artifactId;
private final String version;
private final String packaging;
public Component(String groupId, String artifactId, String version) {
this(groupId, artifactId, version, "jar");
}
public Component(String groupId, String artifactId, String version, String packaging) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
this.packaging = packaging;
}
public String getCoordinates() {
return String.format("pkg:maven/%s/%s@%s?type=%s",
groupId, artifactId, version, packaging);
}
// Getters
public String getGroupId() { return groupId; }
public String getArtifactId() { return artifactId; }
public String getVersion() { return version; }
public String getPackaging() { return packaging; }
@Override
public String toString() {
return groupId + ":" + artifactId + ":" + version;
}
}
public static class ComponentReport {
private String coordinates;
private String description;
private String reference;
private List<Vulnerability> vulnerabilities;
private double cvssScore;
// Getters and setters
public String getCoordinates() { return coordinates; }
public void setCoordinates(String coordinates) { this.coordinates = coordinates; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getReference() { return reference; }
public void setReference(String reference) { this.reference = reference; }
public List<Vulnerability> getVulnerabilities() { return vulnerabilities; }
public void setVulnerabilities(List<Vulnerability> vulnerabilities) { this.vulnerabilities = vulnerabilities; }
public double getCvssScore() { return cvssScore; }
public void setCvssScore(double cvssScore) { this.cvssScore = cvssScore; }
public boolean hasVulnerabilities() {
return vulnerabilities != null && !vulnerabilities.isEmpty();
}
public List<Vulnerability> getCriticalVulnerabilities() {
return vulnerabilities != null ?
vulnerabilities.stream()
.filter(v -> v.getCvssScore() >= 9.0)
.toList() :
Collections.emptyList();
}
public List<Vulnerability> getHighVulnerabilities() {
return vulnerabilities != null ?
vulnerabilities.stream()
.filter(v -> v.getCvssScore() >= 7.0 && v.getCvssScore() < 9.0)
.toList() :
Collections.emptyList();
}
}
public static class Vulnerability {
private String id;
private String title;
private String description;
private double cvssScore;
private String cvssVector;
private String cwe;
private String cve;
private String reference;
private List<String> externalReferences;
private Date published;
private Date updated;
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public double getCvssScore() { return cvssScore; }
public void setCvssScore(double cvssScore) { this.cvssScore = cvssScore; }
public String getCvssVector() { return cvssVector; }
public void setCvssVector(String cvssVector) { this.cvssVector = cvssVector; }
public String getCwe() { return cwe; }
public void setCwe(String cwe) { this.cwe = cwe; }
public String getCve() { return cve; }
public void setCve(String cve) { this.cve = cve; }
public String getReference() { return reference; }
public void setReference(String reference) { this.reference = reference; }
public List<String> getExternalReferences() { return externalReferences; }
public void setExternalReferences(List<String> externalReferences) { this.externalReferences = externalReferences; }
public Date getPublished() { return published; }
public void setPublished(Date published) { this.published = published; }
public Date getUpdated() { return updated; }
public void setUpdated(Date updated) { this.updated = updated; }
public String getSeverity() {
if (cvssScore >= 9.0) return "CRITICAL";
if (cvssScore >= 7.0) return "HIGH";
if (cvssScore >= 4.0) return "MEDIUM";
return "LOW";
}
@Override
public String toString() {
return String.format("%s (CVSS: %.1f, %s)", id, cvssScore, getSeverity());
}
}
public static class OSSIndexException extends Exception {
public OSSIndexException(String message) {
super(message);
}
public OSSIndexException(String message, Throwable cause) {
super(message, cause);
}
}
public static class RateLimitExceededException extends OSSIndexException {
public RateLimitExceededException(String message) {
super(message);
}
}
// Simple rate limiter
private static class RateLimiter {
private final int permits;
private final long periodMillis;
private long lastRefillTime;
private int availablePermits;
public RateLimiter(int permits, TimeUnit timeUnit) {
this.permits = permits;
this.periodMillis = timeUnit.toMillis(1);
this.availablePermits = permits;
this.lastRefillTime = System.currentTimeMillis();
}
public synchronized void acquire() throws InterruptedException {
refill();
while (availablePermits <= 0) {
long waitTime = periodMillis - (System.currentTimeMillis() - lastRefillTime);
if (waitTime > 0) {
Thread.sleep(waitTime);
}
refill();
}
availablePermits--;
}
private void refill() {
long currentTime = System.currentTimeMillis();
long timeSinceLastRefill = currentTime - lastRefillTime;
if (timeSinceLastRefill >= periodMillis) {
availablePermits = permits;
lastRefillTime = currentTime;
}
}
}
}
Maven Plugin Implementation
Custom Maven Plugin for Vulnerability Scanning
package com.security.ossindex.maven;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.artifact.Artifact;
import java.util.*;
import java.util.stream.Collectors;
@Mojo(
name = "vulnerability-scan",
requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
threadSafe = true
)
public class VulnerabilityScanMojo extends AbstractMojo {
@Parameter(defaultValue = "${project}", required = true, readonly = true)
private MavenProject project;
@Parameter(property = "failOnCritical", defaultValue = "true")
private boolean failOnCritical;
@Parameter(property = "failOnHigh", defaultValue = "false")
private boolean failOnHigh;
@Parameter(property = "includeScopes", defaultValue = "compile,runtime")
private String includeScopes;
@Parameter(property = "excludeScopes", defaultValue = "test,system,provided")
private String excludeScopes;
@Parameter(property = "outputFormat", defaultValue = "console")
private String outputFormat;
@Parameter(property = "reportFile", defaultValue = "target/vulnerability-report.json")
private String reportFile;
@Parameter(property = "cacheResults", defaultValue = "true")
private boolean cacheResults;
private OSSIndexClient client;
private VulnerabilityCache cache;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info("Starting OSS Index vulnerability scan...");
try {
initializeClient();
List<OSSIndexClient.Component> components = collectDependencies();
getLog().info("Found " + components.size() + " dependencies to scan");
ScanResult result = scanDependencies(components);
generateReport(result);
if (shouldFailBuild(result)) {
throw new MojoFailureException(
"Vulnerabilities found that exceed threshold: " +
getFailureSummary(result));
}
} catch (OSSIndexClient.OSSIndexException e) {
throw new MojoExecutionException("Failed to scan vulnerabilities", e);
} finally {
try {
if (client != null) {
client.close();
}
} catch (Exception e) {
getLog().warn("Failed to close OSS Index client", e);
}
}
}
private void initializeClient() {
this.client = new OSSIndexClient();
this.cache = new VulnerabilityCache();
}
private List<OSSIndexClient.Component> collectDependencies() {
Set<String> includedScopes = parseScopes(includeScopes);
Set<String> excludedScopes = parseScopes(excludeScopes);
return project.getArtifacts().stream()
.filter(artifact -> includedScopes.contains(artifact.getScope()))
.filter(artifact -> !excludedScopes.contains(artifact.getScope()))
.map(this::convertToComponent)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private OSSIndexClient.Component convertToComponent(Artifact artifact) {
try {
return new OSSIndexClient.Component(
artifact.getGroupId(),
artifact.getArtifactId(),
artifact.getVersion(),
artifact.getType()
);
} catch (Exception e) {
getLog().warn("Failed to convert artifact: " + artifact, e);
return null;
}
}
private Set<String> parseScopes(String scopes) {
return Arrays.stream(scopes.split(","))
.map(String::trim)
.collect(Collectors.toSet());
}
private ScanResult scanDependencies(List<OSSIndexClient.Component> components)
throws OSSIndexClient.OSSIndexException {
ScanResult result = new ScanResult();
for (OSSIndexClient.Component component : components) {
OSSIndexClient.ComponentReport report = null;
// Check cache first
if (cacheResults) {
report = cache.get(component.getCoordinates());
}
// Fetch from OSS Index if not cached
if (report == null) {
try {
report = client.getVulnerabilityReport(component);
if (cacheResults && report != null) {
cache.put(component.getCoordinates(), report);
}
} catch (OSSIndexClient.RateLimitExceededException e) {
getLog().warn("Rate limit exceeded, waiting before continuing...");
try {
Thread.sleep(60000); // Wait 1 minute
report = client.getVulnerabilityReport(component);
} catch (Exception ex) {
getLog().error("Failed to get report after rate limit wait", ex);
}
}
}
if (report != null && report.hasVulnerabilities()) {
result.addVulnerableComponent(component, report);
logVulnerabilities(component, report);
}
}
return result;
}
private void logVulnerabilities(OSSIndexClient.Component component,
OSSIndexClient.ComponentReport report) {
getLog().warn("Vulnerabilities found in " + component + ":");
for (OSSIndexClient.Vulnerability vuln : report.getVulnerabilities()) {
getLog().warn(String.format(" %s - %s (CVSS: %.1f, %s)",
vuln.getId(), vuln.getTitle(), vuln.getCvssScore(), vuln.getSeverity()));
if (getLog().isDebugEnabled()) {
getLog().debug(" Description: " +
(vuln.getDescription() != null ?
vuln.getDescription().substring(0, Math.min(100, vuln.getDescription().length())) : "N/A"));
getLog().debug(" Reference: " + vuln.getReference());
}
}
}
private void generateReport(ScanResult result) {
// Generate report based on output format
switch (outputFormat.toLowerCase()) {
case "json":
generateJsonReport(result);
break;
case "html":
generateHtmlReport(result);
break;
case "console":
default:
generateConsoleReport(result);
}
}
private void generateConsoleReport(ScanResult result) {
getLog().info("=== Vulnerability Scan Report ===");
getLog().info("Scanned components: " + result.getTotalComponents());
getLog().info("Vulnerable components: " + result.getVulnerableComponents().size());
getLog().info("Total vulnerabilities: " + result.getTotalVulnerabilities());
getLog().info("Critical vulnerabilities: " + result.getCriticalCount());
getLog().info("High vulnerabilities: " + result.getHighCount());
getLog().info("Medium vulnerabilities: " + result.getMediumCount());
getLog().info("Low vulnerabilities: " + result.getLowCount());
if (!result.getVulnerableComponents().isEmpty()) {
getLog().info("");
getLog().info("Vulnerable Components:");
result.getVulnerableComponents().forEach((component, report) -> {
getLog().info(" " + component + ": " +
report.getVulnerabilities().size() + " vulnerabilities");
});
}
}
private void generateJsonReport(ScanResult result) {
try {
// Implementation for JSON report generation
// Would use Jackson to write result to reportFile
getLog().info("JSON report generated at: " + reportFile);
} catch (Exception e) {
getLog().error("Failed to generate JSON report", e);
}
}
private void generateHtmlReport(ScanResult result) {
try {
// Implementation for HTML report generation
getLog().info("HTML report generated at: " + reportFile);
} catch (Exception e) {
getLog().error("Failed to generate HTML report", e);
}
}
private boolean shouldFailBuild(ScanResult result) {
if (failOnCritical && result.getCriticalCount() > 0) {
return true;
}
if (failOnHigh && result.getHighCount() > 0) {
return true;
}
return false;
}
private String getFailureSummary(ScanResult result) {
StringBuilder summary = new StringBuilder();
if (result.getCriticalCount() > 0) {
summary.append(result.getCriticalCount()).append(" critical");
}
if (result.getHighCount() > 0) {
if (summary.length() > 0) summary.append(", ");
summary.append(result.getHighCount()).append(" high");
}
summary.append(" vulnerabilities found");
return summary.toString();
}
// Scan result container
private static class ScanResult {
private final Map<OSSIndexClient.Component, OSSIndexClient.ComponentReport> vulnerableComponents;
private int totalComponents;
public ScanResult() {
this.vulnerableComponents = new HashMap<>();
}
public void addVulnerableComponent(OSSIndexClient.Component component,
OSSIndexClient.ComponentReport report) {
vulnerableComponents.put(component, report);
}
public void setTotalComponents(int totalComponents) {
this.totalComponents = totalComponents;
}
// Getters
public Map<OSSIndexClient.Component, OSSIndexClient.ComponentReport> getVulnerableComponents() {
return vulnerableComponents;
}
public int getTotalComponents() {
return totalComponents;
}
public int getTotalVulnerabilities() {
return vulnerableComponents.values().stream()
.mapToInt(report -> report.getVulnerabilities().size())
.sum();
}
public int getCriticalCount() {
return vulnerableComponents.values().stream()
.mapToInt(report -> report.getCriticalVulnerabilities().size())
.sum();
}
public int getHighCount() {
return vulnerableComponents.values().stream()
.mapToInt(report -> report.getHighVulnerabilities().size())
.sum();
}
public int getMediumCount() {
return vulnerableComponents.values().stream()
.mapToInt(report -> (int) report.getVulnerabilities().stream()
.filter(v -> v.getCvssScore() >= 4.0 && v.getCvssScore() < 7.0)
.count())
.sum();
}
public int getLowCount() {
return vulnerableComponents.values().stream()
.mapToInt(report -> (int) report.getVulnerabilities().stream()
.filter(v -> v.getCvssScore() < 4.0)
.count())
.sum();
}
}
}
Gradle Plugin Implementation
Custom Gradle Plugin for Vulnerability Scanning
package com.security.ossindex.gradle;
import org.gradle.api.DefaultTask;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.artifacts.ResolvedConfiguration;
import org.gradle.api.artifacts.ResolvedDependency;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
import java.util.*;
import java.util.stream.Collectors;
public class VulnerabilityScanTask extends DefaultTask {
private boolean failOnCritical = true;
private boolean failOnHigh = false;
private List<String> includeConfigurations = Arrays.asList("compileClasspath", "runtimeClasspath");
private List<String> excludeConfigurations = Arrays.asList("testCompileClasspath", "testRuntimeClasspath");
private String outputFormat = "console";
private String reportFile = "build/reports/vulnerability-scan/report.json";
private boolean cacheResults = true;
private OSSIndexClient client;
private VulnerabilityCache cache;
@TaskAction
public void scanVulnerabilities() {
getLogger().info("Starting OSS Index vulnerability scan...");
try {
initializeClient();
List<OSSIndexClient.Component> components = collectDependencies();
getLogger().info("Found {} dependencies to scan", components.size());
ScanResult result = scanDependencies(components);
generateReport(result);
if (shouldFailBuild(result)) {
throw new RuntimeException(
"Vulnerabilities found that exceed threshold: " +
getFailureSummary(result));
}
} catch (OSSIndexClient.OSSIndexException e) {
throw new RuntimeException("Failed to scan vulnerabilities", e);
} finally {
try {
if (client != null) {
client.close();
}
} catch (Exception e) {
getLogger().warn("Failed to close OSS Index client", e);
}
}
}
private void initializeClient() {
this.client = new OSSIndexClient();
this.cache = new VulnerabilityCache();
}
private List<OSSIndexClient.Component> collectDependencies() {
Set<Configuration> configurations = getProject().getConfigurations().stream()
.filter(config -> includeConfigurations.contains(config.getName()))
.filter(config -> !excludeConfigurations.contains(config.getName()))
.collect(Collectors.toSet());
List<OSSIndexClient.Component> components = new ArrayList<>();
for (Configuration config : configurations) {
try {
ResolvedConfiguration resolvedConfig = config.getResolvedConfiguration();
Set<ResolvedArtifact> artifacts = resolvedConfig.getResolvedArtifacts();
for (ResolvedArtifact artifact : artifacts) {
OSSIndexClient.Component component = convertToComponent(artifact);
if (component != null) {
components.add(component);
}
}
} catch (Exception e) {
getLogger().warn("Failed to resolve configuration: " + config.getName(), e);
}
}
return components;
}
private OSSIndexClient.Component convertToComponent(ResolvedArtifact artifact) {
try {
return new OSSIndexClient.Component(
artifact.getModuleVersion().getId().getGroup(),
artifact.getModuleVersion().getId().getName(),
artifact.getModuleVersion().getId().getVersion(),
artifact.getExtension()
);
} catch (Exception e) {
getLogger().warn("Failed to convert artifact: " + artifact, e);
return null;
}
}
private ScanResult scanDependencies(List<OSSIndexClient.Component> components)
throws OSSIndexClient.OSSIndexException {
ScanResult result = new ScanResult();
result.setTotalComponents(components.size());
for (OSSIndexClient.Component component : components) {
OSSIndexClient.ComponentReport report = null;
// Check cache first
if (cacheResults) {
report = cache.get(component.getCoordinates());
}
// Fetch from OSS Index if not cached
if (report == null) {
try {
report = client.getVulnerabilityReport(component);
if (cacheResults && report != null) {
cache.put(component.getCoordinates(), report);
}
} catch (OSSIndexClient.RateLimitExceededException e) {
getLogger().warn("Rate limit exceeded, waiting before continuing...");
try {
Thread.sleep(60000); // Wait 1 minute
report = client.getVulnerabilityReport(component);
} catch (Exception ex) {
getLogger().error("Failed to get report after rate limit wait", ex);
}
}
}
if (report != null && report.hasVulnerabilities()) {
result.addVulnerableComponent(component, report);
logVulnerabilities(component, report);
}
}
return result;
}
private void logVulnerabilities(OSSIndexClient.Component component,
OSSIndexClient.ComponentReport report) {
getLogger().warn("Vulnerabilities found in {}:", component);
for (OSSIndexClient.Vulnerability vuln : report.getVulnerabilities()) {
getLogger().warn(" {} - {} (CVSS: {:.1f}, {})",
vuln.getId(), vuln.getTitle(), vuln.getCvssScore(), vuln.getSeverity());
}
}
private void generateReport(ScanResult result) {
// Similar to Maven implementation
switch (outputFormat.toLowerCase()) {
case "json":
generateJsonReport(result);
break;
case "html":
generateHtmlReport(result);
break;
case "console":
default:
generateConsoleReport(result);
}
}
private void generateConsoleReport(ScanResult result) {
getLogger().info("=== Vulnerability Scan Report ===");
getLogger().info("Scanned components: {}", result.getTotalComponents());
getLogger().info("Vulnerable components: {}", result.getVulnerableComponents().size());
getLogger().info("Total vulnerabilities: {}", result.getTotalVulnerabilities());
getLogger().info("Critical vulnerabilities: {}", result.getCriticalCount());
getLogger().info("High vulnerabilities: {}", result.getHighCount());
getLogger().info("Medium vulnerabilities: {}", result.getMediumCount());
getLogger().info("Low vulnerabilities: {}", result.getLowCount());
}
private void generateJsonReport(ScanResult result) {
// JSON report implementation
}
private void generateHtmlReport(ScanResult result) {
// HTML report implementation
}
private boolean shouldFailBuild(ScanResult result) {
if (failOnCritical && result.getCriticalCount() > 0) {
return true;
}
if (failOnHigh && result.getHighCount() > 0) {
return true;
}
return false;
}
private String getFailureSummary(ScanResult result) {
StringBuilder summary = new StringBuilder();
if (result.getCriticalCount() > 0) {
summary.append(result.getCriticalCount()).append(" critical");
}
if (result.getHighCount() > 0) {
if (summary.length() > 0) summary.append(", ");
summary.append(result.getHighCount()).append(" high");
}
summary.append(" vulnerabilities found");
return summary.toString();
}
// Getters and setters for Gradle properties
@Input
public boolean getFailOnCritical() { return failOnCritical; }
public void setFailOnCritical(boolean failOnCritical) { this.failOnCritical = failOnCritical; }
@Input
public boolean getFailOnHigh() { return failOnHigh; }
public void setFailOnHigh(boolean failOnHigh) { this.failOnHigh = failOnHigh; }
@Input
public List<String> getIncludeConfigurations() { return includeConfigurations; }
public void setIncludeConfigurations(List<String> includeConfigurations) { this.includeConfigurations = includeConfigurations; }
@Input
public List<String> getExcludeConfigurations() { return excludeConfigurations; }
public void setExcludeConfigurations(List<String> excludeConfigurations) { this.excludeConfigurations = excludeConfigurations; }
@Input
public String getOutputFormat() { return outputFormat; }
public void setOutputFormat(String outputFormat) { this.outputFormat = outputFormat; }
@Input
@Optional
public String getReportFile() { return reportFile; }
public void setReportFile(String reportFile) { this.reportFile = reportFile; }
@Input
public boolean getCacheResults() { return cacheResults; }
public void setCacheResults(boolean cacheResults) { this.cacheResults = cacheResults; }
}
Vulnerability Cache Implementation
Efficient Caching for OSS Index Results
package com.security.ossindex.cache;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.security.ossindex.OSSIndexClient;
import java.util.concurrent.TimeUnit;
public class VulnerabilityCache {
private final Cache<String, OSSIndexClient.ComponentReport> cache;
public VulnerabilityCache() {
this.cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(24, TimeUnit.HOURS) // Cache for 24 hours
.build();
}
public VulnerabilityCache(int maxSize, int expireHours) {
this.cache = Caffeine.newBuilder()
.maximumSize(maxSize)
.expireAfterWrite(expireHours, TimeUnit.HOURS)
.build();
}
public OSSIndexClient.ComponentReport get(String coordinates) {
return cache.getIfPresent(coordinates);
}
public void put(String coordinates, OSSIndexClient.ComponentReport report) {
cache.put(coordinates, report);
}
public void invalidate(String coordinates) {
cache.invalidate(coordinates);
}
public void clear() {
cache.invalidateAll();
}
public long size() {
return cache.estimatedSize();
}
}
Enterprise Vulnerability Scanner
Comprehensive Vulnerability Management
package com.security.ossindex.enterprise;
import com.security.ossindex.OSSIndexClient;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
public class EnterpriseVulnerabilityScanner {
private final OSSIndexClient ossIndexClient;
private final VulnerabilityCache cache;
private final ObjectMapper objectMapper;
private final ExecutorService executorService;
private final String reportsDirectory;
public EnterpriseVulnerabilityScanner() {
this.ossIndexClient = new OSSIndexClient();
this.cache = new VulnerabilityCache();
this.objectMapper = new ObjectMapper();
this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
this.executorService = Executors.newFixedThreadPool(10);
this.reportsDirectory = "./vulnerability-reports";
createReportsDirectory();
}
public ProjectScanResult scanProject(Project project) throws Exception {
ProjectScanResult result = new ProjectScanResult(project);
result.setScanStartTime(LocalDateTime.now());
try {
List<OSSIndexClient.Component> components = project.collectDependencies();
result.setTotalDependencies(components.size());
List<OSSIndexClient.ComponentReport> reports =
ossIndexClient.getVulnerabilityReports(components);
for (OSSIndexClient.ComponentReport report : reports) {
if (report.hasVulnerabilities()) {
result.addVulnerableComponent(report);
// Cache the result
cache.put(report.getCoordinates(), report);
}
}
result.setScanEndTime(LocalDateTime.now());
generateProjectReport(result);
} catch (Exception e) {
result.setError(e.getMessage());
result.setScanEndTime(LocalDateTime.now());
throw e;
}
return result;
}
public OrganizationScanResult scanOrganization(List<Project> projects) {
OrganizationScanResult orgResult = new OrganizationScanResult();
orgResult.setScanStartTime(LocalDateTime.now());
List<Future<ProjectScanResult>> futures = new ArrayList<>();
for (Project project : projects) {
Future<ProjectScanResult> future = executorService.submit(() -> {
try {
return scanProject(project);
} catch (Exception e) {
ProjectScanResult errorResult = new ProjectScanResult(project);
errorResult.setError(e.getMessage());
return errorResult;
}
});
futures.add(future);
}
for (Future<ProjectScanResult> future : futures) {
try {
ProjectScanResult projectResult = future.get(30, TimeUnit.MINUTES);
orgResult.addProjectResult(projectResult);
} catch (TimeoutException e) {
getLogger().error("Project scan timed out", e);
} catch (Exception e) {
getLogger().error("Project scan failed", e);
}
}
orgResult.setScanEndTime(LocalDateTime.now());
generateOrganizationReport(orgResult);
return orgResult;
}
public List<VulnerabilityTrend> analyzeTrends(OrganizationScanResult currentScan,
OrganizationScanResult previousScan) {
List<VulnerabilityTrend> trends = new ArrayList<>();
// Analyze new vulnerabilities
Set<String> currentVulnerabilities = currentScan.getAllVulnerabilities().stream()
.map(OSSIndexClient.Vulnerability::getId)
.collect(Collectors.toSet());
Set<String> previousVulnerabilities = previousScan.getAllVulnerabilities().stream()
.map(OSSIndexClient.Vulnerability::getId)
.collect(Collectors.toSet());
// Find new vulnerabilities
currentVulnerabilities.stream()
.filter(vulnId -> !previousVulnerabilities.contains(vulnId))
.forEach(vulnId -> {
VulnerabilityTrend trend = new VulnerabilityTrend();
trend.setVulnerabilityId(vulnId);
trend.setTrendType(TrendType.NEW);
trend.setSeverityChange("NEW");
trends.add(trend);
});
// Find fixed vulnerabilities
previousVulnerabilities.stream()
.filter(vulnId -> !currentVulnerabilities.contains(vulnId))
.forEach(vulnId -> {
VulnerabilityTrend trend = new VulnerabilityTrend();
trend.setVulnerabilityId(vulnId);
trend.setTrendType(TrendType.FIXED);
trend.setSeverityChange("FIXED");
trends.add(trend);
});
return trends;
}
public RemediationPlan generateRemediationPlan(ProjectScanResult scanResult) {
RemediationPlan plan = new RemediationPlan();
plan.setGeneratedDate(LocalDateTime.now());
// Group vulnerabilities by severity and component
Map<String, List<OSSIndexClient.Vulnerability>> vulnerabilitiesByComponent =
scanResult.getVulnerableComponents().stream()
.collect(Collectors.toMap(
OSSIndexClient.ComponentReport::getCoordinates,
OSSIndexClient.ComponentReport::getVulnerabilities
));
// Prioritize critical and high vulnerabilities
List<RemediationAction> actions = new ArrayList<>();
for (Map.Entry<String, List<OSSIndexClient.Vulnerability>> entry :
vulnerabilitiesByComponent.entrySet()) {
String component = entry.getKey();
List<OSSIndexClient.Vulnerability> vulnerabilities = entry.getValue();
// Find the highest severity vulnerability
Optional<OSSIndexClient.Vulnerability> highestSeverity = vulnerabilities.stream()
.max(Comparator.comparingDouble(OSSIndexClient.Vulnerability::getCvssScore));
if (highestSeverity.isPresent()) {
RemediationAction action = new RemediationAction();
action.setComponent(component);
action.setHighestSeverity(highestSeverity.get().getSeverity());
action.setVulnerabilityCount(vulnerabilities.size());
action.setRecommendedAction(generateRecommendedAction(component, vulnerabilities));
action.setPriority(calculatePriority(highestSeverity.get()));
actions.add(action);
}
}
// Sort by priority
actions.sort(Comparator.comparingInt(RemediationAction::getPriority).reversed());
plan.setActions(actions);
return plan;
}
private String generateRecommendedAction(String component,
List<OSSIndexClient.Vulnerability> vulnerabilities) {
// Parse component coordinates
// pkg:maven/groupId/artifactId@version
String[] parts = component.split("/");
if (parts.length >= 3) {
String groupId = parts[1];
String artifactVersion = parts[2];
String[] artifactParts = artifactVersion.split("@");
String artifactId = artifactParts[0];
String version = artifactParts.length > 1 ? artifactParts[1].split("\\?")[0] : "unknown";
return String.format("Upgrade %s:%s from version %s to a secure version",
groupId, artifactId, version);
}
return "Review component for available security updates";
}
private int calculatePriority(OSSIndexClient.Vulnerability vulnerability) {
switch (vulnerability.getSeverity()) {
case "CRITICAL": return 100;
case "HIGH": return 75;
case "MEDIUM": return 50;
case "LOW": return 25;
default: return 0;
}
}
private void generateProjectReport(ProjectScanResult result) {
try {
String timestamp = LocalDateTime.now().format(
java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
String filename = String.format("project-scan-%s-%s.json",
result.getProject().getName(), timestamp);
Path reportPath = Paths.get(reportsDirectory, "projects", filename);
Files.createDirectories(reportPath.getParent());
objectMapper.writeValue(reportPath.toFile(), result);
result.setReportPath(reportPath.toString());
} catch (Exception e) {
getLogger().error("Failed to generate project report", e);
}
}
private void generateOrganizationReport(OrganizationScanResult result) {
try {
String timestamp = LocalDateTime.now().format(
java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
String filename = String.format("organization-scan-%s.json", timestamp);
Path reportPath = Paths.get(reportsDirectory, "organization", filename);
Files.createDirectories(reportPath.getParent());
objectMapper.writeValue(reportPath.toFile(), result);
result.setReportPath(reportPath.toString());
} catch (Exception e) {
getLogger().error("Failed to generate organization report", e);
}
}
private void createReportsDirectory() {
try {
Files.createDirectories(Paths.get(reportsDirectory, "projects"));
Files.createDirectories(Paths.get(reportsDirectory, "organization"));
Files.createDirectories(Paths.get(reportsDirectory, "remediation"));
} catch (Exception e) {
throw new RuntimeException("Failed to create reports directory", e);
}
}
public void shutdown() {
executorService.shutdown();
try {
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
try {
ossIndexClient.close();
} catch (Exception e) {
getLogger().error("Failed to close OSS Index client", e);
}
}
private org.slf4j.Logger getLogger() {
return org.slf4j.LoggerFactory.getLogger(EnterpriseVulnerabilityScanner.class);
}
// Data classes
public static class Project {
private String name;
private String version;
private String repositoryUrl;
private String branch;
private Path projectPath;
private BuildTool buildTool;
public List<OSSIndexClient.Component> collectDependencies() throws Exception {
// Implementation would vary based on build tool
switch (buildTool) {
case MAVEN:
return collectMavenDependencies();
case GRADLE:
return collectGradleDependencies();
case SBT:
return collectSbtDependencies();
default:
throw new UnsupportedOperationException("Unsupported build tool: " + buildTool);
}
}
private List<OSSIndexClient.Component> collectMavenDependencies() throws Exception {
// Use Maven dependency plugin or parse pom.xml
return Collections.emptyList();
}
private List<OSSIndexClient.Component> collectGradleDependencies() throws Exception {
// Use Gradle dependency insight or parse build.gradle
return Collections.emptyList();
}
private List<OSSIndexClient.Component> collectSbtDependencies() throws Exception {
// Parse build.sbt or use sbt-dependency-graph
return Collections.emptyList();
}
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public String getRepositoryUrl() { return repositoryUrl; }
public void setRepositoryUrl(String repositoryUrl) { this.repositoryUrl = repositoryUrl; }
public String getBranch() { return branch; }
public void setBranch(String branch) { this.branch = branch; }
public Path getProjectPath() { return projectPath; }
public void setProjectPath(Path projectPath) { this.projectPath = projectPath; }
public BuildTool getBuildTool() { return buildTool; }
public void setBuildTool(BuildTool buildTool) { this.buildTool = buildTool; }
}
public static class ProjectScanResult {
private Project project;
private LocalDateTime scanStartTime;
private LocalDateTime scanEndTime;
private int totalDependencies;
private List<OSSIndexClient.ComponentReport> vulnerableComponents = new ArrayList<>();
private String error;
private String reportPath;
public ProjectScanResult(Project project) {
this.project = project;
}
// Getters and setters
public void addVulnerableComponent(OSSIndexClient.ComponentReport report) {
vulnerableComponents.add(report);
}
public int getTotalVulnerabilities() {
return vulnerableComponents.stream()
.mapToInt(report -> report.getVulnerabilities().size())
.sum();
}
public int getCriticalCount() {
return vulnerableComponents.stream()
.mapToInt(report -> report.getCriticalVulnerabilities().size())
.sum();
}
// ... other getters and setters
}
public static class OrganizationScanResult {
private LocalDateTime scanStartTime;
private LocalDateTime scanEndTime;
private List<ProjectScanResult> projectResults = new ArrayList<>();
private String reportPath;
public List<OSSIndexClient.Vulnerability> getAllVulnerabilities() {
return projectResults.stream()
.flatMap(projectResult -> projectResult.getVulnerableComponents().stream())
.flatMap(component -> component.getVulnerabilities().stream())
.collect(Collectors.toList());
}
// Getters and setters
public void addProjectResult(ProjectScanResult result) {
projectResults.add(result);
}
}
public static class VulnerabilityTrend {
private String vulnerabilityId;
private TrendType trendType;
private String severityChange;
// Getters and setters
}
public static class RemediationPlan {
private LocalDateTime generatedDate;
private List<RemediationAction> actions = new ArrayList<>();
// Getters and setters
}
public static class RemediationAction {
private String component;
private String highestSeverity;
private int vulnerabilityCount;
private String recommendedAction;
private int priority;
// Getters and setters
}
public enum BuildTool {
MAVEN, GRADLE, SBT, UNKNOWN
}
public enum TrendType {
NEW, FIXED, WORSENED, IMPROVED
}
}
Usage Examples
Basic Usage
public class OSSIndexExample {
public static void main(String[] args) {
try {
// Initialize OSS Index client
OSSIndexClient client = new OSSIndexClient();
// Create components to scan
List<OSSIndexClient.Component> components = Arrays.asList(
new OSSIndexClient.Component("org.springframework", "spring-core", "5.3.23"),
new OSSIndexClient.Component("com.fasterxml.jackson.core", "jackson-databind", "2.14.2"),
new OSSIndexClient.Component("org.apache.logging.log4j", "log4j-core", "2.20.0")
);
// Get vulnerability reports
List<OSSIndexClient.ComponentReport> reports = client.getVulnerabilityReports(components);
// Analyze results
for (OSSIndexClient.ComponentReport report : reports) {
if (report.hasVulnerabilities()) {
System.out.println("Vulnerabilities found in " + report.getCoordinates() + ":");
for (OSSIndexClient.Vulnerability vuln : report.getVulnerabilities()) {
System.out.println(" - " + vuln);
}
}
}
client.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Best Practices
- Rate Limiting: Always respect OSS Index rate limits (100 requests per minute)
- Caching: Cache results to avoid unnecessary API calls
- Error Handling: Implement robust error handling for network issues
- Dependency Analysis: Scan all dependencies including transitive ones
- Regular Scanning: Integrate into CI/CD for continuous monitoring
- Remediation Workflow: Establish processes for fixing vulnerabilities
- Reporting: Generate comprehensive reports for different stakeholders
This comprehensive OSS Index integration provides enterprise-grade vulnerability scanning for Java applications, helping to identify and remediate security issues in open-source dependencies.