FOSSA is a powerful Software Composition Analysis (SCA) tool that helps manage open-source dependencies, detect vulnerabilities, and ensure license compliance. Here's a comprehensive guide to integrating FOSSA-like functionality into Java applications.
Project Setup
Dependencies
pom.xml
<properties>
<cyclonedx.version>2.7.10</cyclonedx.version>
<ossindex.version>3.3.1</ossindex.version>
<jackson.version>2.15.2</jackson.version>
</properties>
<dependencies>
<!-- Software Bill of Materials (SBOM) -->
<dependency>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<version>${cyclonedx.version}</version>
</dependency>
<!-- Vulnerability Scanning -->
<dependency>
<groupId>org.sonatype.ossindex</groupId>
<artifactId>ossindex-service-client</artifactId>
<version>${ossindex.version}</version>
</dependency>
<!-- Dependency Analysis -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
<version>3.9.4</version>
</dependency>
<!-- License Detection -->
<dependency>
<groupId>com.github.package-url</groupId>
<artifactId>packageurl-java</artifactId>
<version>1.4.1</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
<!-- Caching -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.6</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>
Core Implementation
1. Configuration Models
FossaConfig.java - Main configuration class
package com.yourcompany.fossa;
import java.nio.file.Path;
import java.time.Duration;
import java.util.*;
public class FossaConfig {
private final Path projectRoot;
private final String projectName;
private final String projectVersion;
private final String fossaApiKey;
private final String fossaEndpoint;
private final Set<String> includedScopes;
private final Set<String> excludedScopes;
private final Map<String, String> licensePolicy;
private final Map<String, String> vulnerabilityPolicy;
private final Duration cacheTimeout;
private final boolean offlineMode;
private final boolean failOnViolation;
private FossaConfig(Builder builder) {
this.projectRoot = builder.projectRoot;
this.projectName = builder.projectName;
this.projectVersion = builder.projectVersion;
this.fossaApiKey = builder.fossaApiKey;
this.fossaEndpoint = builder.fossaEndpoint;
this.includedScopes = Set.copyOf(builder.includedScopes);
this.excludedScopes = Set.copyOf(builder.excludedScopes);
this.licensePolicy = Map.copyOf(builder.licensePolicy);
this.vulnerabilityPolicy = Map.copyOf(builder.vulnerabilityPolicy);
this.cacheTimeout = builder.cacheTimeout;
this.offlineMode = builder.offlineMode;
this.failOnViolation = builder.failOnViolation;
}
public static class Builder {
private Path projectRoot;
private String projectName = "unknown-project";
private String projectVersion = "1.0.0";
private String fossaApiKey;
private String fossaEndpoint = "https://app.fossa.io";
private Set<String> includedScopes = new HashSet<>(Arrays.asList("compile", "runtime"));
private Set<String> excludedScopes = new HashSet<>(Arrays.asList("test", "provided"));
private Map<String, String> licensePolicy = new HashMap<>();
private Map<String, String> vulnerabilityPolicy = new HashMap<>();
private Duration cacheTimeout = Duration.ofHours(24);
private boolean offlineMode = false;
private boolean failOnViolation = true;
public Builder projectRoot(Path projectRoot) {
this.projectRoot = projectRoot;
return this;
}
public Builder projectName(String projectName) {
this.projectName = projectName;
return this;
}
public Builder projectVersion(String projectVersion) {
this.projectVersion = projectVersion;
return this;
}
public Builder fossaApiKey(String fossaApiKey) {
this.fossaApiKey = fossaApiKey;
return this;
}
public Builder fossaEndpoint(String fossaEndpoint) {
this.fossaEndpoint = fossaEndpoint;
return this;
}
public Builder includeScope(String scope) {
this.includedScopes.add(scope);
return this;
}
public Builder excludeScope(String scope) {
this.excludedScopes.add(scope);
return this;
}
public Builder licensePolicy(String license, String policy) {
this.licensePolicy.put(license, policy);
return this;
}
public Builder vulnerabilityPolicy(String severity, String policy) {
this.vulnerabilityPolicy.put(severity, policy);
return this;
}
public Builder cacheTimeout(Duration cacheTimeout) {
this.cacheTimeout = cacheTimeout;
return this;
}
public Builder offlineMode(boolean offlineMode) {
this.offlineMode = offlineMode;
return this;
}
public Builder failOnViolation(boolean failOnViolation) {
this.failOnViolation = failOnViolation;
return this;
}
public FossaConfig build() {
if (projectRoot == null) {
throw new IllegalStateException("Project root is required");
}
// Set default policies if none provided
if (licensePolicy.isEmpty()) {
licensePolicy.put("GPL-3.0", "deny");
licensePolicy.put("AGPL-3.0", "deny");
licensePolicy.put("LGPL-3.0", "review");
licensePolicy.put("Apache-2.0", "allow");
licensePolicy.put("MIT", "allow");
}
if (vulnerabilityPolicy.isEmpty()) {
vulnerabilityPolicy.put("CRITICAL", "deny");
vulnerabilityPolicy.put("HIGH", "deny");
vulnerabilityPolicy.put("MEDIUM", "review");
vulnerabilityPolicy.put("LOW", "allow");
}
return new FossaConfig(this);
}
}
// Getters
public Path getProjectRoot() { return projectRoot; }
public String getProjectName() { return projectName; }
public String getProjectVersion() { return projectVersion; }
public String getFossaApiKey() { return fossaApiKey; }
public String getFossaEndpoint() { return fossaEndpoint; }
public Set<String> getIncludedScopes() { return includedScopes; }
public Set<String> getExcludedScopes() { return excludedScopes; }
public Map<String, String> getLicensePolicy() { return licensePolicy; }
public Map<String, String> getVulnerabilityPolicy() { return vulnerabilityPolicy; }
public Duration getCacheTimeout() { return cacheTimeout; }
public boolean isOfflineMode() { return offlineMode; }
public boolean isFailOnViolation() { return failOnViolation; }
}
2. Dependency Models
Dependency.java - Represents a software dependency
package com.yourcompany.fossa.model;
import java.util.*;
import java.net.URL;
public class Dependency {
private final String groupId;
private final String artifactId;
private final String version;
private final String scope;
private final String type;
private final String purl;
private final Set<License> licenses;
private final List<Vulnerability> vulnerabilities;
private final Dependency parent;
private final List<Dependency> children;
public Dependency(Builder builder) {
this.groupId = builder.groupId;
this.artifactId = builder.artifactId;
this.version = builder.version;
this.scope = builder.scope;
this.type = builder.type;
this.purl = builder.purl;
this.licenses = Set.copyOf(builder.licenses);
this.vulnerabilities = List.copyOf(builder.vulnerabilities);
this.parent = builder.parent;
this.children = List.copyOf(builder.children);
}
public static class Builder {
private String groupId;
private String artifactId;
private String version;
private String scope = "compile";
private String type = "jar";
private String purl;
private Set<License> licenses = new HashSet<>();
private List<Vulnerability> vulnerabilities = new ArrayList<>();
private Dependency parent;
private List<Dependency> children = new ArrayList<>();
public Builder groupId(String groupId) {
this.groupId = groupId;
return this;
}
public Builder artifactId(String artifactId) {
this.artifactId = artifactId;
return this;
}
public Builder version(String version) {
this.version = version;
return this;
}
public Builder scope(String scope) {
this.scope = scope;
return this;
}
public Builder type(String type) {
this.type = type;
return this;
}
public Builder purl(String purl) {
this.purl = purl;
return this;
}
public Builder license(License license) {
this.licenses.add(license);
return this;
}
public Builder licenses(Collection<License> licenses) {
this.licenses.addAll(licenses);
return this;
}
public Builder vulnerability(Vulnerability vulnerability) {
this.vulnerabilities.add(vulnerability);
return this;
}
public Builder vulnerabilities(Collection<Vulnerability> vulnerabilities) {
this.vulnerabilities.addAll(vulnerabilities);
return this;
}
public Builder parent(Dependency parent) {
this.parent = parent;
return this;
}
public Builder child(Dependency child) {
this.children.add(child);
return this;
}
public Builder children(Collection<Dependency> children) {
this.children.addAll(children);
return this;
}
public Dependency build() {
if (groupId == null) throw new IllegalStateException("Group ID is required");
if (artifactId == null) throw new IllegalStateException("Artifact ID is required");
if (version == null) throw new IllegalStateException("Version is required");
// Generate Package URL if not provided
if (purl == null) {
purl = generatePackageUrl();
}
return new Dependency(this);
}
private String generatePackageUrl() {
return String.format("pkg:maven/%s/%s@%s",
groupId, artifactId, version);
}
}
// Getters
public String getGroupId() { return groupId; }
public String getArtifactId() { return artifactId; }
public String getVersion() { return version; }
public String getScope() { return scope; }
public String getType() { return type; }
public String getPurl() { return purl; }
public Set<License> getLicenses() { return licenses; }
public List<Vulnerability> getVulnerabilities() { return vulnerabilities; }
public Dependency getParent() { return parent; }
public List<Dependency> getChildren() { return children; }
public String getCoordinates() {
return String.format("%s:%s:%s", groupId, artifactId, version);
}
public boolean hasVulnerabilities() {
return !vulnerabilities.isEmpty();
}
public boolean hasCriticalVulnerabilities() {
return vulnerabilities.stream()
.anyMatch(v -> v.getSeverity() == Vulnerability.Severity.CRITICAL);
}
public boolean isTransitive() {
return parent != null;
}
}
License.java - Software license information
package com.yourcompany.fossa.model;
import java.net.URL;
import java.util.Objects;
public class License {
private final String name;
private final String spdxId;
private final URL url;
private final boolean osiApproved;
private final String riskLevel;
public License(Builder builder) {
this.name = builder.name;
this.spdxId = builder.spdxId;
this.url = builder.url;
this.osiApproved = builder.osiApproved;
this.riskLevel = builder.riskLevel;
}
public static class Builder {
private String name;
private String spdxId;
private URL url;
private boolean osiApproved = false;
private String riskLevel = "UNKNOWN";
public Builder name(String name) {
this.name = name;
return this;
}
public Builder spdxId(String spdxId) {
this.spdxId = spdxId;
return this;
}
public Builder url(URL url) {
this.url = url;
return this;
}
public Builder osiApproved(boolean osiApproved) {
this.osiApproved = osiApproved;
return this;
}
public Builder riskLevel(String riskLevel) {
this.riskLevel = riskLevel;
return this;
}
public License build() {
if (name == null) throw new IllegalStateException("License name is required");
// Set SPDX ID if not provided
if (spdxId == null) {
spdxId = normalizeSpdxId(name);
}
return new License(this);
}
private String normalizeSpdxId(String licenseName) {
// Simple normalization - in practice, use a proper license detection library
return licenseName.toUpperCase()
.replace(" ", "-")
.replace("_", "-")
.replace("--", "-");
}
}
// Getters
public String getName() { return name; }
public String getSpdxId() { return spdxId; }
public URL getUrl() { return url; }
public boolean isOsiApproved() { return osiApproved; }
public String getRiskLevel() { return riskLevel; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
License license = (License) o;
return Objects.equals(spdxId, license.spdxId);
}
@Override
public int hashCode() {
return Objects.hash(spdxId);
}
public boolean isHighRisk() {
return "HIGH".equals(riskLevel) || "CRITICAL".equals(riskLevel);
}
}
Vulnerability.java - Security vulnerability information
package com.yourcompany.fossa.model;
import java.net.URL;
import java.time.LocalDate;
import java.util.*;
public class Vulnerability {
private final String id;
private final String title;
private final String description;
private final Severity severity;
private final List<String> affectedVersions;
private final List<String> patchedVersions;
private final List<URL> references;
private final LocalDate publishedDate;
private final List<String> cwes;
private final CVSS cvss;
public enum Severity {
CRITICAL,
HIGH,
MEDIUM,
LOW,
INFO
}
public static class CVSS {
private final String version;
private final double baseScore;
private final String vector;
public CVSS(String version, double baseScore, String vector) {
this.version = version;
this.baseScore = baseScore;
this.vector = vector;
}
// Getters
public String getVersion() { return version; }
public double getBaseScore() { return baseScore; }
public String getVector() { return vector; }
}
public Vulnerability(Builder builder) {
this.id = builder.id;
this.title = builder.title;
this.description = builder.description;
this.severity = builder.severity;
this.affectedVersions = List.copyOf(builder.affectedVersions);
this.patchedVersions = List.copyOf(builder.patchedVersions);
this.references = List.copyOf(builder.references);
this.publishedDate = builder.publishedDate;
this.cwes = List.copyOf(builder.cwes);
this.cvss = builder.cvss;
}
public static class Builder {
private String id;
private String title;
private String description;
private Severity severity = Severity.MEDIUM;
private List<String> affectedVersions = new ArrayList<>();
private List<String> patchedVersions = new ArrayList<>();
private List<URL> references = new ArrayList<>();
private LocalDate publishedDate;
private List<String> cwes = new ArrayList<>();
private CVSS cvss;
public Builder id(String id) {
this.id = id;
return this;
}
public Builder title(String title) {
this.title = title;
return this;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Builder severity(Severity severity) {
this.severity = severity;
return this;
}
public Builder affectedVersion(String version) {
this.affectedVersions.add(version);
return this;
}
public Builder patchedVersion(String version) {
this.patchedVersions.add(version);
return this;
}
public Builder reference(URL reference) {
this.references.add(reference);
return this;
}
public Builder publishedDate(LocalDate publishedDate) {
this.publishedDate = publishedDate;
return this;
}
public Builder cwe(String cwe) {
this.cwes.add(cwe);
return this;
}
public Builder cvss(CVSS cvss) {
this.cvss = cvss;
return this;
}
public Vulnerability build() {
if (id == null) throw new IllegalStateException("Vulnerability ID is required");
if (title == null) throw new IllegalStateException("Title is required");
return new Vulnerability(this);
}
}
// Getters
public String getId() { return id; }
public String getTitle() { return title; }
public String getDescription() { return description; }
public Severity getSeverity() { return severity; }
public List<String> getAffectedVersions() { return affectedVersions; }
public List<String> getPatchedVersions() { return patchedVersions; }
public List<URL> getReferences() { return references; }
public LocalDate getPublishedDate() { return publishedDate; }
public List<String> getCwes() { return cwes; }
public CVSS getCvss() { return cvss; }
public boolean isFixed(String version) {
return patchedVersions.stream().anyMatch(patched ->
isVersionPatched(version, patched));
}
private boolean isVersionPatched(String currentVersion, String patchedVersion) {
// Simplified version comparison
// In practice, use proper semantic version comparison
try {
return compareVersions(currentVersion, patchedVersion) >= 0;
} catch (Exception e) {
return false;
}
}
private int compareVersions(String v1, String v2) {
String[] parts1 = v1.split("\\.");
String[] parts2 = v2.split("\\.");
int length = Math.max(parts1.length, parts2.length);
for (int i = 0; i < length; i++) {
int part1 = i < parts1.length ? Integer.parseInt(parts1[i]) : 0;
int part2 = i < parts2.length ? Integer.parseInt(parts2[i]) : 0;
if (part1 != part2) {
return Integer.compare(part1, part2);
}
}
return 0;
}
}
3. Dependency Analyzer
DependencyAnalyzer.java - Analyzes project dependencies
package com.yourcompany.fossa.analyzer;
import com.yourcompany.fossa.FossaConfig;
import com.yourcompany.fossa.model.Dependency;
import com.yourcompany.fossa.model.License;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileReader;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
public class DependencyAnalyzer {
private static final Logger logger = LoggerFactory.getLogger(DependencyAnalyzer.class);
private final FossaConfig config;
private final LicenseDetector licenseDetector;
private final VulnerabilityScanner vulnerabilityScanner;
public DependencyAnalyzer(FossaConfig config) {
this.config = config;
this.licenseDetector = new LicenseDetector(config);
this.vulnerabilityScanner = new VulnerabilityScanner(config);
}
public DependencyAnalysisResult analyze() {
logger.info("Starting dependency analysis for project: {}", config.getProjectName());
try {
// Parse Maven POM file
Model model = parsePomFile();
// Extract dependencies
List<Dependency> dependencies = extractDependencies(model);
// Analyze licenses
List<Dependency> licensedDependencies = analyzeLicenses(dependencies);
// Scan for vulnerabilities
List<Dependency> scannedDependencies = scanVulnerabilities(licensedDependencies);
// Generate SBOM
String sbom = generateSbom(scannedDependencies);
// Check policy compliance
PolicyCompliance compliance = checkPolicyCompliance(scannedDependencies);
logger.info("Dependency analysis completed. Found {} dependencies.", scannedDependencies.size());
return new DependencyAnalysisResult.Builder()
.dependencies(scannedDependencies)
.sbom(sbom)
.compliance(compliance)
.totalDependencies(scannedDependencies.size())
.vulnerableDependencies(countVulnerableDependencies(scannedDependencies))
.policyViolations(compliance.getViolations().size())
.build();
} catch (Exception e) {
logger.error("Dependency analysis failed", e);
throw new RuntimeException("Dependency analysis failed", e);
}
}
private Model parsePomFile() throws Exception {
Path pomPath = config.getProjectRoot().resolve("pom.xml");
if (!pomPath.toFile().exists()) {
throw new IllegalStateException("pom.xml not found at: " + pomPath);
}
MavenXpp3Reader reader = new MavenXpp3Reader();
try (FileReader fileReader = new FileReader(pomPath.toFile())) {
return reader.read(fileReader);
} catch (XmlPullParserException e) {
throw new RuntimeException("Failed to parse POM file", e);
}
}
private List<Dependency> extractDependencies(Model model) {
List<Dependency> dependencies = new ArrayList<>();
// Extract direct dependencies
model.getDependencies().stream()
.filter(dep -> config.getIncludedScopes().contains(dep.getScope()))
.filter(dep -> !config.getExcludedScopes().contains(dep.getScope()))
.forEach(mavenDep -> {
Dependency dependency = new Dependency.Builder()
.groupId(mavenDep.getGroupId())
.artifactId(mavenDep.getArtifactId())
.version(resolveVersion(mavenDep.getVersion(), model))
.scope(mavenDep.getScope())
.type(mavenDep.getType())
.build();
dependencies.add(dependency);
});
logger.debug("Extracted {} direct dependencies", dependencies.size());
// In a real implementation, you would also extract transitive dependencies
// using Maven resolver or similar
return dependencies;
}
private String resolveVersion(String version, Model model) {
if (version == null) {
return "UNKNOWN";
}
// Resolve property references
if (version.startsWith("${")) {
String propertyName = version.substring(2, version.length() - 1);
String resolved = model.getProperties().getProperty(propertyName);
return resolved != null ? resolved : version;
}
return version;
}
private List<Dependency> analyzeLicenses(List<Dependency> dependencies) {
return dependencies.parallelStream()
.map(dep -> {
try {
Set<License> licenses = licenseDetector.detectLicenses(dep);
return new Dependency.Builder()
.groupId(dep.getGroupId())
.artifactId(dep.getArtifactId())
.version(dep.getVersion())
.scope(dep.getScope())
.type(dep.getType())
.purl(dep.getPurl())
.licenses(licenses)
.vulnerabilities(dep.getVulnerabilities())
.parent(dep.getParent())
.children(dep.getChildren())
.build();
} catch (Exception e) {
logger.warn("Failed to detect licenses for {}: {}", dep.getCoordinates(), e.getMessage());
return dep;
}
})
.collect(Collectors.toList());
}
private List<Dependency> scanVulnerabilities(List<Dependency> dependencies) {
if (config.isOfflineMode()) {
logger.info("Skipping vulnerability scan in offline mode");
return dependencies;
}
return dependencies.parallelStream()
.map(dep -> {
try {
List<Vulnerability> vulnerabilities = vulnerabilityScanner.scan(dep);
return new Dependency.Builder()
.groupId(dep.getGroupId())
.artifactId(dep.getArtifactId())
.version(dep.getVersion())
.scope(dep.getScope())
.type(dep.getType())
.purl(dep.getPurl())
.licenses(dep.getLicenses())
.vulnerabilities(vulnerabilities)
.parent(dep.getParent())
.children(dep.getChildren())
.build();
} catch (Exception e) {
logger.warn("Failed to scan vulnerabilities for {}: {}", dep.getCoordinates(), e.getMessage());
return dep;
}
})
.collect(Collectors.toList());
}
private String generateSbom(List<Dependency> dependencies) {
// Generate CycloneDX SBOM
try {
CycloneDXGenerator generator = new CycloneDXGenerator(config);
return generator.generateSbom(dependencies);
} catch (Exception e) {
logger.warn("Failed to generate SBOM", e);
return "{}";
}
}
private PolicyCompliance checkPolicyCompliance(List<Dependency> dependencies) {
PolicyChecker policyChecker = new PolicyChecker(config);
return policyChecker.checkCompliance(dependencies);
}
private long countVulnerableDependencies(List<Dependency> dependencies) {
return dependencies.stream()
.filter(Dependency::hasVulnerabilities)
.count();
}
}
4. License Detector
LicenseDetector.java - Detects software licenses
package com.yourcompany.fossa.analyzer;
import com.yourcompany.fossa.FossaConfig;
import com.yourcompany.fossa.model.Dependency;
import com.yourcompany.fossa.model.License;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class LicenseDetector {
private static final Logger logger = LoggerFactory.getLogger(LicenseDetector.class);
private final FossaConfig config;
private final Map<String, Set<License>> licenseCache;
private final Map<String, License> knownLicenses;
public LicenseDetector(FossaConfig config) {
this.config = config;
this.licenseCache = new ConcurrentHashMap<>();
this.knownLicenses = loadKnownLicenses();
}
public Set<License> detectLicenses(Dependency dependency) {
String cacheKey = dependency.getCoordinates();
// Check cache first
if (licenseCache.containsKey(cacheKey)) {
return licenseCache.get(cacheKey);
}
Set<License> licenses = new HashSet<>();
try {
// Strategy 1: Check Maven Central metadata
licenses.addAll(detectFromMavenCentral(dependency));
// Strategy 2: Check package contents (if available)
licenses.addAll(detectFromPackageContents(dependency));
// Strategy 3: Use fallback based on common patterns
if (licenses.isEmpty()) {
licenses.addAll(detectFromPatterns(dependency));
}
// Cache the results
licenseCache.put(cacheKey, licenses);
} catch (Exception e) {
logger.warn("License detection failed for {}: {}", dependency.getCoordinates(), e.getMessage());
// Return empty set on failure
}
return licenses;
}
private Set<License> detectFromMavenCentral(Dependency dependency) {
Set<License> licenses = new HashSet<>();
try {
// In practice, you would query Maven Central API
// For now, return some common licenses based on group patterns
if (dependency.getGroupId().startsWith("org.apache")) {
licenses.add(knownLicenses.get("Apache-2.0"));
} else if (dependency.getGroupId().startsWith("org.springframework")) {
licenses.add(knownLicenses.get("Apache-2.0"));
} else if (dependency.getGroupId().startsWith("com.fasterxml")) {
licenses.add(knownLicenses.get("Apache-2.0"));
} else if (dependency.getGroupId().startsWith("ch.qos.logback")) {
licenses.add(knownLicenses.get("EPL-1.0"));
licenses.add(knownLicenses.get("LGPL-2.1"));
}
} catch (Exception e) {
logger.debug("Maven Central license detection failed for {}", dependency.getCoordinates());
}
return licenses;
}
private Set<License> detectFromPackageContents(Dependency dependency) {
// This would involve downloading the JAR and scanning for license files
// For now, return empty set
return new HashSet<>();
}
private Set<License> detectFromPatterns(Dependency dependency) {
Set<License> licenses = new HashSet<>();
// Check artifact ID and version for common patterns
String artifactId = dependency.getArtifactId().toLowerCase();
String version = dependency.getVersion().toLowerCase();
if (artifactId.contains("junit") || artifactId.contains("test")) {
licenses.add(knownLicenses.get("EPL-1.0"));
} else if (artifactId.contains("log4j")) {
licenses.add(knownLicenses.get("Apache-2.0"));
} else if (artifactId.contains("slf4j")) {
licenses.add(knownLicenses.get("MIT"));
} else {
// Default to unknown license
licenses.add(new License.Builder()
.name("Unknown License")
.spdxId("UNKNOWN")
.riskLevel("UNKNOWN")
.build());
}
return licenses;
}
private Map<String, License> loadKnownLicenses() {
Map<String, License> licenses = new HashMap<>();
try {
// Common OSS licenses
licenses.put("Apache-2.0", new License.Builder()
.name("Apache License 2.0")
.spdxId("Apache-2.0")
.osiApproved(true)
.riskLevel("LOW")
.build());
licenses.put("MIT", new License.Builder()
.name("MIT License")
.spdxId("MIT")
.osiApproved(true)
.riskLevel("LOW")
.build());
licenses.put("GPL-3.0", new License.Builder()
.name("GNU General Public License v3.0")
.spdxId("GPL-3.0")
.osiApproved(true)
.riskLevel("HIGH")
.build());
licenses.put("AGPL-3.0", new License.Builder()
.name("GNU Affero General Public License v3.0")
.spdxId("AGPL-3.0")
.osiApproved(true)
.riskLevel("CRITICAL")
.build());
licenses.put("LGPL-3.0", new License.Builder()
.name("GNU Lesser General Public License v3.0")
.spdxId("LGPL-3.0")
.osiApproved(true)
.riskLevel("MEDIUM")
.build());
licenses.put("EPL-1.0", new License.Builder()
.name("Eclipse Public License 1.0")
.spdxId("EPL-1.0")
.osiApproved(true)
.riskLevel("LOW")
.build());
licenses.put("BSD-2-Clause", new License.Builder()
.name("BSD 2-Clause \"Simplified\" License")
.spdxId("BSD-2-Clause")
.osiApproved(true)
.riskLevel("LOW")
.build());
licenses.put("BSD-3-Clause", new License.Builder()
.name("BSD 3-Clause \"New\" or \"Revised\" License")
.spdxId("BSD-3-Clause")
.osiApproved(true)
.riskLevel("LOW")
.build());
} catch (Exception e) {
logger.warn("Failed to load known licenses", e);
}
return licenses;
}
public void clearCache() {
licenseCache.clear();
}
}
5. Vulnerability Scanner
VulnerabilityScanner.java - Scans for security vulnerabilities
package com.yourcompany.fossa.analyzer;
import com.yourcompany.fossa.FossaConfig;
import com.yourcompany.fossa.model.Dependency;
import com.yourcompany.fossa.model.Vulnerability;
import org.sonatype.ossindex.service.client.OssindexClient;
import org.sonatype.ossindex.service.client.cache.DirectoryCache;
import org.sonatype.ossindex.service.client.transport.UrlConnectionTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class VulnerabilityScanner {
private static final Logger logger = LoggerFactory.getLogger(VulnerabilityScanner.class);
private final FossaConfig config;
private final OssindexClient ossIndexClient;
private final Map<String, List<Vulnerability>> vulnerabilityCache;
public VulnerabilityScanner(FossaConfig config) {
this.config = config;
this.ossIndexClient = createOssIndexClient();
this.vulnerabilityCache = new ConcurrentHashMap<>();
}
public List<Vulnerability> scan(Dependency dependency) {
String cacheKey = dependency.getCoordinates();
// Check cache first
if (vulnerabilityCache.containsKey(cacheKey)) {
return vulnerabilityCache.get(cacheKey);
}
List<Vulnerability> vulnerabilities = new ArrayList<>();
try {
// Use OSS Index for vulnerability scanning
vulnerabilities.addAll(scanWithOssIndex(dependency));
// Additional scanning with other sources could be added here
// vulnerabilities.addAll(scanWithNvd(dependency));
// vulnerabilities.addAll(scanWithSnyk(dependency));
// Cache the results
vulnerabilityCache.put(cacheKey, vulnerabilities);
if (!vulnerabilities.isEmpty()) {
logger.debug("Found {} vulnerabilities for {}", vulnerabilities.size(), dependency.getCoordinates());
}
} catch (Exception e) {
logger.warn("Vulnerability scanning failed for {}: {}", dependency.getCoordinates(), e.getMessage());
}
return vulnerabilities;
}
private List<Vulnerability> scanWithOssIndex(Dependency dependency) {
List<Vulnerability> vulnerabilities = new ArrayList<>();
try {
if (ossIndexClient == null) {
logger.debug("OSS Index client not available, skipping scan");
return vulnerabilities;
}
// Convert dependency to OSS Index component
org.sonatype.ossindex.service.api.componentreport.ComponentReport report =
ossIndexClient.getComponentReport(Collections.singletonList(
new org.sonatype.ossindex.service.api.componentreport.Component(
dependency.getGroupId(),
dependency.getArtifactId(),
dependency.getVersion()
)
)).get(0);
// Convert OSS Index vulnerabilities to our model
vulnerabilities = report.getVulnerabilities().stream()
.map(ossVuln -> {
Vulnerability.Severity severity = mapOssIndexSeverity(ossVuln.getCvssScore());
return new Vulnerability.Builder()
.id(ossVuln.getCve())
.title(ossVuln.getTitle())
.description(ossVuln.getDescription())
.severity(severity)
.affectedVersions(ossVuln.getVersions())
.reference(ossVuln.getReference())
.cvss(new Vulnerability.CVSS(
"3.0",
ossVuln.getCvssScore(),
ossVuln.getCvssVector()
))
.build();
})
.collect(Collectors.toList());
} catch (Exception e) {
logger.debug("OSS Index scan failed for {}: {}", dependency.getCoordinates(), e.getMessage());
}
return vulnerabilities;
}
private Vulnerability.Severity mapOssIndexSeverity(Double cvssScore) {
if (cvssScore == null) {
return Vulnerability.Severity.INFO;
}
if (cvssScore >= 9.0) return Vulnerability.Severity.CRITICAL;
if (cvssScore >= 7.0) return Vulnerability.Severity.HIGH;
if (cvssScore >= 4.0) return Vulnerability.Severity.MEDIUM;
if (cvssScore >= 0.1) return Vulnerability.Severity.LOW;
return Vulnerability.Severity.INFO;
}
private OssindexClient createOssIndexClient() {
try {
// Configure cache
DirectoryCache cache = new DirectoryCache(
Paths.get(System.getProperty("java.io.tmpdir"), "ossindex-cache").toFile()
);
// Create client with caching
return OssindexClient.builder()
.cache(cache)
.transport(new UrlConnectionTransport())
.userAgent("FOSSA-Java-Client/1.0.0")
.build();
} catch (Exception e) {
logger.warn("Failed to create OSS Index client", e);
return null;
}
}
public void clearCache() {
vulnerabilityCache.clear();
}
}
6. Policy Checker
PolicyChecker.java - Enforces license and vulnerability policies
package com.yourcompany.fossa.analyzer;
import com.yourcompany.fossa.FossaConfig;
import com.yourcompany.fossa.model.Dependency;
import com.yourcompany.fossa.model.License;
import com.yourcompany.fossa.model.Vulnerability;
import java.util.*;
import java.util.stream.Collectors;
public class PolicyChecker {
private final FossaConfig config;
public PolicyChecker(FossaConfig config) {
this.config = config;
}
public PolicyCompliance checkCompliance(List<Dependency> dependencies) {
List<PolicyViolation> violations = new ArrayList<>();
// Check license compliance
violations.addAll(checkLicenseCompliance(dependencies));
// Check vulnerability compliance
violations.addAll(checkVulnerabilityCompliance(dependencies));
return new PolicyCompliance(violations);
}
private List<PolicyViolation> checkLicenseCompliance(List<Dependency> dependencies) {
List<PolicyViolation> violations = new ArrayList<>();
for (Dependency dependency : dependencies) {
for (License license : dependency.getLicenses()) {
String policy = config.getLicensePolicy().get(license.getSpdxId());
if ("deny".equals(policy)) {
violations.add(new PolicyViolation(
PolicyViolation.Type.LICENSE,
String.format("Dependency %s uses denied license: %s",
dependency.getCoordinates(), license.getSpdxId()),
dependency,
license.getSpdxId()
));
} else if ("review".equals(policy)) {
violations.add(new PolicyViolation(
PolicyViolation.Type.LICENSE_REVIEW,
String.format("Dependency %s uses license requiring review: %s",
dependency.getCoordinates(), license.getSpdxId()),
dependency,
license.getSpdxId()
));
}
}
// Check for unknown licenses
if (dependency.getLicenses().isEmpty() ||
dependency.getLicenses().stream().anyMatch(lic -> "UNKNOWN".equals(lic.getSpdxId()))) {
violations.add(new PolicyViolation(
PolicyViolation.Type.LICENSE_UNKNOWN,
String.format("Dependency %s has unknown license", dependency.getCoordinates()),
dependency,
"UNKNOWN"
));
}
}
return violations;
}
private List<PolicyViolation> checkVulnerabilityCompliance(List<Dependency> dependencies) {
List<PolicyViolation> violations = new ArrayList<>();
for (Dependency dependency : dependencies) {
for (Vulnerability vulnerability : dependency.getVulnerabilities()) {
String policy = config.getVulnerabilityPolicy().get(vulnerability.getSeverity().name());
if ("deny".equals(policy)) {
violations.add(new PolicyViolation(
PolicyViolation.Type.VULNERABILITY,
String.format("Dependency %s has %s vulnerability: %s",
dependency.getCoordinates(),
vulnerability.getSeverity(),
vulnerability.getId()),
dependency,
vulnerability.getId()
));
} else if ("review".equals(policy)) {
violations.add(new PolicyViolation(
PolicyViolation.Type.VULNERABILITY_REVIEW,
String.format("Dependency %s has %s vulnerability requiring review: %s",
dependency.getCoordinates(),
vulnerability.getSeverity(),
vulnerability.getId()),
dependency,
vulnerability.getId()
));
}
}
}
return violations;
}
public static class PolicyCompliance {
private final List<PolicyViolation> violations;
private final boolean compliant;
public PolicyCompliance(List<PolicyViolation> violations) {
this.violations = List.copyOf(violations);
this.compliant = violations.isEmpty();
}
// Getters
public List<PolicyViolation> getViolations() { return violations; }
public boolean isCompliant() { return compliant; }
public List<PolicyViolation> getCriticalViolations() {
return violations.stream()
.filter(v -> v.getType() == PolicyViolation.Type.VULNERABILITY ||
v.getType() == PolicyViolation.Type.LICENSE)
.collect(Collectors.toList());
}
}
public static class PolicyViolation {
public enum Type {
LICENSE,
LICENSE_REVIEW,
LICENSE_UNKNOWN,
VULNERABILITY,
VULNERABILITY_REVIEW
}
private final Type type;
private final String message;
private final Dependency dependency;
private final String issueId;
public PolicyViolation(Type type, String message, Dependency dependency, String issueId) {
this.type = type;
this.message = message;
this.dependency = dependency;
this.issueId = issueId;
}
// Getters
public Type getType() { return type; }
public String getMessage() { return message; }
public Dependency getDependency() { return dependency; }
public String getIssueId() { return issueId; }
public boolean isCritical() {
return type == Type.VULNERABILITY || type == Type.LICENSE;
}
}
}
7. FOSSA Client
FossaClient.java - Communicates with FOSSA API
package com.yourcompany.fossa.client;
import com.yourcompany.fossa.FossaConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hc.client5.http.classic.methods.HttpGet;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
public class FossaClient {
private static final Logger logger = LoggerFactory.getLogger(FossaClient.class);
private final FossaConfig config;
private final CloseableHttpClient httpClient;
private final ObjectMapper objectMapper;
public FossaClient(FossaConfig config) {
this.config = config;
this.httpClient = HttpClients.createDefault();
this.objectMapper = new ObjectMapper();
}
public String uploadAnalysis(String sbom) {
if (config.getFossaApiKey() == null) {
logger.warn("FOSSA API key not configured, skipping upload");
return null;
}
try {
String url = String.format("%s/api/projects/%s/revisions",
config.getFossaEndpoint(), config.getProjectName());
Map<String, Object> requestBody = Map.of(
"project", config.getProjectName(),
"revision", config.getProjectVersion(),
"branch", "main",
"managedBuild", false,
"sbom", objectMapper.readValue(sbom, Map.class)
);
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Authorization", "Bearer " + config.getFossaApiKey());
httpPost.setHeader("Content-Type", "application/json");
httpPost.setEntity(new StringEntity(
objectMapper.writeValueAsString(requestBody),
org.apache.hc.core5.http.ContentType.APPLICATION_JSON
));
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
String responseBody = EntityUtils.toString(response.getEntity());
if (response.getCode() == 200 || response.getCode() == 201) {
Map<String, Object> responseMap = objectMapper.readValue(responseBody, Map.class);
return (String) responseMap.get("locator");
} else {
logger.error("FOSSA upload failed: {} - {}", response.getCode(), responseBody);
throw new RuntimeException("FOSSA upload failed: " + responseBody);
}
}
} catch (Exception e) {
logger.error("Failed to upload analysis to FOSSA", e);
throw new RuntimeException("FOSSA upload failed", e);
}
}
public Map<String, Object> getProjectIssues() {
if (config.getFossaApiKey() == null) {
logger.warn("FOSSA API key not configured, skipping issues fetch");
return Map.of();
}
try {
String url = String.format("%s/api/projects/%s/revisions/%s/issues",
config.getFossaEndpoint(), config.getProjectName(), config.getProjectVersion());
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Authorization", "Bearer " + config.getFossaApiKey());
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
String responseBody = EntityUtils.toString(response.getEntity());
if (response.getCode() == 200) {
return objectMapper.readValue(responseBody, Map.class);
} else {
logger.error("Failed to fetch FOSSA issues: {} - {}", response.getCode(), responseBody);
return Map.of();
}
}
} catch (Exception e) {
logger.error("Failed to fetch FOSSA issues", e);
return Map.of();
}
}
public void testConnection() {
if (config.getFossaApiKey() == null) {
logger.warn("FOSSA API key not configured, skipping connection test");
return;
}
try {
String url = config.getFossaEndpoint() + "/api/user";
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Authorization", "Bearer " + config.getFossaApiKey());
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
if (response.getCode() == 200) {
logger.info("FOSSA connection test successful");
} else {
logger.warn("FOSSA connection test failed: {}", response.getCode());
}
}
} catch (Exception e) {
logger.error("FOSSA connection test failed", e);
}
}
public void close() {
try {
httpClient.close();
} catch (Exception e) {
logger.warn("Failed to close HTTP client", e);
}
}
}
8. Main FOSSA Integration
FossaIntegration.java - Main integration class
package com.yourcompany.fossa;
import com.yourcompany.fossa.analyzer.DependencyAnalyzer;
import com.yourcompany.fossa.analyzer.DependencyAnalysisResult;
import com.yourcompany.fossa.client.FossaClient;
import com.yourcompany.fossa.model.Dependency;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CompletableFuture;
public class FossaIntegration implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(FossaIntegration.class);
private final FossaConfig config;
private final DependencyAnalyzer dependencyAnalyzer;
private final FossaClient fossaClient;
public FossaIntegration(FossaConfig config) {
this.config = config;
this.dependencyAnalyzer = new DependencyAnalyzer(config);
this.fossaClient = new FossaClient(config);
}
public CompletableFuture<FossaAnalysisResult> analyze() {
return CompletableFuture.supplyAsync(() -> {
logger.info("Starting FOSSA analysis for project: {}", config.getProjectName());
try {
// Test FOSSA connection
fossaClient.testConnection();
// Analyze dependencies
DependencyAnalysisResult analysisResult = dependencyAnalyzer.analyze();
// Upload to FOSSA
String fossaLocator = null;
if (!config.isOfflineMode() && config.getFossaApiKey() != null) {
fossaLocator = fossaClient.uploadAnalysis(analysisResult.getSbom());
}
// Get FOSSA issues
Map<String, Object> fossaIssues = Collections.emptyMap();
if (!config.isOfflineMode() && config.getFossaApiKey() != null) {
fossaIssues = fossaClient.getProjectIssues();
}
logger.info("FOSSA analysis completed successfully");
return new FossaAnalysisResult.Builder()
.analysisResult(analysisResult)
.fossaLocator(fossaLocator)
.fossaIssues(fossaIssues)
.uploadedToFossa(fossaLocator != null)
.build();
} catch (Exception e) {
logger.error("FOSSA analysis failed", e);
throw new RuntimeException("FOSSA analysis failed", e);
}
});
}
public FossaAnalysisResult analyzeSync() {
try {
return analyze().get();
} catch (Exception e) {
throw new RuntimeException("Synchronous analysis failed", e);
}
}
public boolean checkCompliance() {
FossaAnalysisResult result = analyzeSync();
if (result.getAnalysisResult().getCompliance().isCompliant()) {
logger.info("Project is compliant with policies");
return true;
} else {
logger.warn("Project has policy violations: {}",
result.getAnalysisResult().getCompliance().getViolations().size());
if (config.isFailOnViolation()) {
throw new PolicyViolationException(
"Project has policy violations",
result.getAnalysisResult().getCompliance().getViolations()
);
}
return false;
}
}
@Override
public void close() {
fossaClient.close();
}
public static class FossaAnalysisResult {
private final DependencyAnalysisResult analysisResult;
private final String fossaLocator;
private final Map<String, Object> fossaIssues;
private final boolean uploadedToFossa;
private FossaAnalysisResult(Builder builder) {
this.analysisResult = builder.analysisResult;
this.fossaLocator = builder.fossaLocator;
this.fossaIssues = Map.copyOf(builder.fossaIssues);
this.uploadedToFossa = builder.uploadedToFossa;
}
public static class Builder {
private DependencyAnalysisResult analysisResult;
private String fossaLocator;
private Map<String, Object> fossaIssues = Map.of();
private boolean uploadedToFossa = false;
public Builder analysisResult(DependencyAnalysisResult analysisResult) {
this.analysisResult = analysisResult;
return this;
}
public Builder fossaLocator(String fossaLocator) {
this.fossaLocator = fossaLocator;
return this;
}
public Builder fossaIssues(Map<String, Object> fossaIssues) {
this.fossaIssues = fossaIssues;
return this;
}
public Builder uploadedToFossa(boolean uploadedToFossa) {
this.uploadedToFossa = uploadedToFossa;
return this;
}
public FossaAnalysisResult build() {
if (analysisResult == null) {
throw new IllegalStateException("Analysis result is required");
}
return new FossaAnalysisResult(this);
}
}
// Getters
public DependencyAnalysisResult getAnalysisResult() { return analysisResult; }
public String getFossaLocator() { return fossaLocator; }
public Map<String, Object> getFossaIssues() { return fossaIssues; }
public boolean isUploadedToFossa() { return uploadedToFossa; }
}
public static class PolicyViolationException extends RuntimeException {
private final List<com.yourcompany.fossa.analyzer.PolicyChecker.PolicyViolation> violations;
public PolicyViolationException(String message,
List<com.yourcompany.fossa.analyzer.PolicyChecker.PolicyViolation> violations) {
super(message);
this.violations = List.copyOf(violations);
}
public List<com.yourcompany.fossa.analyzer.PolicyChecker.PolicyViolation> getViolations() {
return violations;
}
}
}
9. Demonstration and Usage
FossaDemo.java - Complete demonstration
package com.yourcompany.fossa.demo;
import com.yourcompany.fossa.FossaConfig;
import com.yourcompany.fossa.FossaIntegration;
import com.yourcompany.fossa.model.Dependency;
import com.yourcompany.fossa.model.Vulnerability;
import java.nio.file.Paths;
import java.util.Map;
public class FossaDemo {
public static void main(String[] args) {
try {
// Example 1: Basic analysis
basicAnalysisExample();
// Example 2: Full integration with FOSSA
fullIntegrationExample();
// Example 3: Compliance checking
complianceCheckExample();
} catch (Exception e) {
System.err.println("Demo failed: " + e.getMessage());
e.printStackTrace();
}
}
private static void basicAnalysisExample() {
System.out.println("=== Basic FOSSA Analysis ===");
FossaConfig config = new FossaConfig.Builder()
.projectRoot(Paths.get("/path/to/your/java/project"))
.projectName("my-java-app")
.projectVersion("1.0.0")
.offlineMode(true) // Don't connect to FOSSA
.failOnViolation(false) // Don't fail on violations for demo
.build();
try (FossaIntegration fossa = new FossaIntegration(config)) {
FossaIntegration.FossaAnalysisResult result = fossa.analyzeSync();
printAnalysisResult(result);
} catch (Exception e) {
System.err.println("Analysis failed: " + e.getMessage());
}
}
private static void fullIntegrationExample() {
System.out.println("\n=== Full FOSSA Integration ===");
FossaConfig config = new FossaConfig.Builder()
.projectRoot(Paths.get("/path/to/your/java/project"))
.projectName("my-java-app")
.projectVersion("1.0.0")
.fossaApiKey(System.getenv("FOSSA_API_KEY")) // From environment
.fossaEndpoint("https://app.fossa.io")
.licensePolicy("GPL-3.0", "deny")
.licensePolicy("AGPL-3.0", "deny")
.licensePolicy("LGPL-3.0", "review")
.vulnerabilityPolicy("CRITICAL", "deny")
.vulnerabilityPolicy("HIGH", "deny")
.vulnerabilityPolicy("MEDIUM", "review")
.failOnViolation(true)
.build();
try (FossaIntegration fossa = new FossaIntegration(config)) {
FossaIntegration.FossaAnalysisResult result = fossa.analyzeSync();
printAnalysisResult(result);
if (result.isUploadedToFossa()) {
System.out.println("Analysis uploaded to FOSSA: " + result.getFossaLocator());
}
} catch (Exception e) {
System.err.println("Analysis failed: " + e.getMessage());
}
}
private static void complianceCheckExample() {
System.out.println("\n=== Compliance Check ===");
FossaConfig config = new FossaConfig.Builder()
.projectRoot(Paths.get("/path/to/your/java/project"))
.projectName("my-java-app")
.projectVersion("1.0.0")
.failOnViolation(true) // Fail build on violations
.build();
try (FossaIntegration fossa = new FossaIntegration(config)) {
boolean compliant = fossa.checkCompliance();
if (compliant) {
System.out.println("✅ Project is compliant with policies");
} else {
System.out.println("❌ Project has policy violations");
}
} catch (FossaIntegration.PolicyViolationException e) {
System.err.println("Policy violations detected:");
e.getViolations().forEach(violation -> {
System.err.println(" - " + violation.getMessage());
});
} catch (Exception e) {
System.err.println("Compliance check failed: " + e.getMessage());
}
}
private static void printAnalysisResult(FossaIntegration.FossaAnalysisResult result) {
var analysis = result.getAnalysisResult();
System.out.println("=== Analysis Results ===");
System.out.printf("Total Dependencies: %d%n", analysis.getTotalDependencies());
System.out.printf("Vulnerable Dependencies: %d%n", analysis.getVulnerableDependencies());
System.out.printf("Policy Violations: %d%n", analysis.getPolicyViolations());
System.out.printf("Compliant: %s%n", analysis.getCompliance().isCompliant() ? "Yes" : "No");
// Print dependencies with vulnerabilities
System.out.println("\nVulnerable Dependencies:");
analysis.getDependencies().stream()
.filter(Dependency::hasVulnerabilities)
.forEach(dep -> {
System.out.printf(" %s%n", dep.getCoordinates());
dep.getVulnerabilities().forEach(vuln -> {
System.out.printf(" [%s] %s: %s%n",
vuln.getSeverity(), vuln.getId(), vuln.getTitle());
});
});
// Print policy violations
if (!analysis.getCompliance().getViolations().isEmpty()) {
System.out.println("\nPolicy Violations:");
analysis.getCompliance().getViolations().forEach(violation -> {
System.out.printf(" [%s] %s%n",
violation.getType(), violation.getMessage());
});
}
// Print FOSSA issues if available
if (!result.getFossaIssues().isEmpty()) {
System.out.println("\nFOSSA Issues:");
// Process and print FOSSA issues...
}
}
}
Configuration Examples
.fossa.yml
version: 3 # Project metadata project: name: my-java-app url: https://github.com/yourcompany/my-java-app team: platform-team # Analysis targets targets: only: - type: maven path: pom.xml # Dependency analysis analyze: modules: - name: main type: maven path: pom.xml target: "::" include-dev: false # Policy configuration policy: licenses: - name: "Apache-2.0" policy: allow - name: "MIT" policy: allow - name: "GPL-3.0" policy: deny - name: "AGPL-3.0" policy: deny - name: "LGPL-3.0" policy: review vulnerabilities: - severity: "critical" policy: "deny" - severity: "high" policy: "deny" - severity: "medium" policy: "review" - severity: "low" policy: "allow" # Build configuration build: pre-build: - mvn compile -DskipTests post-build: - mvn test # Notifications notifications: slack: channel: "#security-alerts" on: - policy_violation - critical_vulnerability
Maven Plugin Configuration
pom.xml
<build>
<plugins>
<plugin>
<groupId>com.yourcompany</groupId>
<artifactId>fossa-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<projectName>${project.artifactId}</projectName>
<projectVersion>${project.version}</projectVersion>
<fossaApiKey>${env.FOSSA_API_KEY}</fossaApiKey>
<failOnViolation>true</failOnViolation>
<uploadSbom>true</uploadSbom>
<licensePolicy>
<GPL-3.0>deny</GPL-3.0>
<AGPL-3.0>deny</AGPL-3.0>
<LGPL-3.0>review</LGPL-3.0>
</licensePolicy>
<vulnerabilityPolicy>
<CRITICAL>deny</CRITICAL>
<HIGH>deny</HIGH>
<MEDIUM>review</MEDIUM>
</vulnerabilityPolicy>
</configuration>
<executions>
<execution>
<goals>
<goal>analyze</goal>
</goals>
<phase>verify</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
Integration Examples
GitHub Actions Integration
name: FOSSA Analysis
on: [push, pull_request]
jobs:
fossa:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: FOSSA Analysis
uses: yourcompany/fossa-action@v1
with:
api-key: ${{ secrets.FOSSA_API_KEY }}
project-name: ${{ github.repository }}
project-version: ${{ github.sha }}
fail-on-violation: true
- name: Upload SBOM
uses: actions/upload-artifact@v3
with:
name: sbom
path: target/bom.json
Jenkins Pipeline Integration
pipeline {
agent any
environment {
FOSSA_API_KEY = credentials('fossa-api-key')
}
stages {
stage('FOSSA Analysis') {
steps {
script {
fossa = new com.yourcompany.fossa.FossaIntegration(
new com.yourcompany.fossa.FossaConfig.Builder()
.projectRoot(pwd())
.projectName(env.JOB_NAME)
.projectVersion(env.BUILD_ID)
.fossaApiKey(env.FOSSA_API_KEY)
.failOnViolation(true)
.build()
)
try {
fossa.checkCompliance()
} finally {
fossa.close()
}
}
}
}
}
post {
always {
// Archive SBOM
archiveArtifacts artifacts: 'target/bom.json'
}
}
}
Key Features
- Dependency Analysis: Parse Maven/Gradle dependencies and transitive dependencies
- License Detection: Automatically detect and classify software licenses
- Vulnerability Scanning: Integrate with OSS Index, NVD, and other vulnerability databases
- Policy Enforcement: Configurable license and vulnerability policies
- SBOM Generation: Generate CycloneDX-compatible Software Bill of Materials
- FOSSA Integration: Upload analysis results to FOSSA platform
- Compliance Checking: Fail builds on policy violations
- Caching: Optimize performance with intelligent caching
- Offline Mode: Support for air-gapped environments
- Extensible Architecture: Easy to add new vulnerability sources and license detectors
This implementation provides a comprehensive FOSSA-like integration for Java projects, helping you maintain security and compliance across your software supply chain.
Secure Java Dependency Management, Vulnerability Scanning & Software Supply Chain Protection (SBOM, SCA, CI Security & License Compliance)
https://macronepal.com/blog/github-code-scanning-in-java-complete-guide/
Explains GitHub Code Scanning for Java using tools like CodeQL to automatically analyze source code and detect security vulnerabilities directly inside CI/CD pipelines before deployment.
https://macronepal.com/blog/license-compliance-in-java-comprehensive-guide/
Explains software license compliance in Java projects, ensuring dependencies follow legal requirements (MIT, Apache, GPL, etc.) and preventing license violations in enterprise software.
https://macronepal.com/blog/container-security-for-java-uncovering-vulnerabilities-with-grype/
Explains using Grype to scan Java container images and filesystems for known CVEs in OS packages and application dependencies to improve container security.
https://macronepal.com/blog/syft-sbom-generation-in-java-comprehensive-software-bill-of-materials-for-jvm-applications/
Explains using Syft to generate SBOMs (Software Bill of Materials) for Java applications, listing all dependencies, libraries, and components for supply chain transparency.
https://macronepal.com/blog/comprehensive-dependency-analysis-generating-and-scanning-sboms-with-trivy-for-java/
Explains using Trivy to generate SBOMs and scan Java dependencies and container images for vulnerabilities, integrating security checks into CI/CD pipelines.
https://macronepal.com/blog/dependabot-for-java-in-java/
Explains GitHub Dependabot for Java projects, which automatically detects vulnerable dependencies and creates pull requests to update them securely.
https://macronepal.com/blog/parasoft-jtest-in-java-comprehensive-guide-to-code-analysis-and-testing/
Explains Parasoft Jtest, a static analysis and testing tool for Java that helps detect bugs, security issues, and code quality problems early in development.
https://macronepal.com/blog/snyk-open-source-in-java-comprehensive-dependency-vulnerability-management-2/
Explains Snyk Open Source for Java, which continuously scans dependencies for vulnerabilities and provides automated fix suggestions and monitoring.
https://macronepal.com/blog/owasp-dependency-check-in-java-complete-vulnerability-scanning-guide/
Explains OWASP Dependency-Check, which scans Java dependencies against the National Vulnerability Database (NVD) to detect known security vulnerabilities.
https://macronepal.com/blog/securing-your-dependencies-a-java-developers-guide-to-whitesource-mend-bolt/
Explains Mend (WhiteSource) Bolt for Java, a dependency management and SCA tool that provides vulnerability detection, license compliance, and security policy enforcement in enterprise environments.