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.