Introduction
WhiteSource (now Mend) is a comprehensive Software Composition Analysis (SCA) tool that automatically scans open-source components for security vulnerabilities, license compliance issues, and quality problems. This guide covers complete integration strategies for Java projects.
Mend Unified Agent Integration
Maven Configuration
<!-- pom.xml Mend Integration -->
<project>
<properties>
<whitesource.version>21.11.1</whitesource.version>
<whitesource.orgToken>${whitesource.orgToken}</whitesource.orgToken>
<whitesource.product>Your-Product-Name</whitesource.product>
<whitesource.project>${project.artifactId}</whitesource.project>
</properties>
<build>
<plugins>
<!-- Mend Unified Agent Plugin -->
<plugin>
<groupId>org.whitesource</groupId>
<artifactId>whitesource-maven-plugin</artifactId>
<version>${whitesource.version}</version>
<configuration>
<orgToken>${whitesource.orgToken}</orgToken>
<product>${whitesource.product}</product>
<productVersion>${project.version}</productVersion>
<project>${whitesource.project}</project>
<checkPolicies>true</checkPolicies>
<forceCheckAllDependencies>true</forceCheckAllDependencies>
<forceUpdate>true</forceUpdate>
<aggregateModules>true</aggregateModules>
<failOnError>true</failOnError>
<failOnPolicyViolation>true</failOnPolicyViolation>
<includePlugins>false</includePlugins>
<includeSubProjects>true</includeSubProjects>
<scanComment>Scan triggered by Maven build</scanComment>
<configurationDirectory>${project.basedir}</configurationDirectory>
<configurationFilePath>whitesource.config</configurationFilePath>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>update</goal>
<goal>check-policies</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Alternative: Using Maven plugin for basic scanning -->
<plugin>
<groupId>org.whitesource</groupId>
<artifactId>whitesource-maven-plugin</artifactId>
<version>${whitesource.version}</version>
<configuration>
<orgToken>${whitesource.orgToken}</orgToken>
<product>${whitesource.product}</product>
<checkPolicies>true</checkPolicies>
<failOnError>true</failOnError>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>whitesource-scan</id>
<build>
<plugins>
<plugin>
<groupId>org.whitesource</groupId>
<artifactId>whitesource-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>update</goal>
<goal>check-policies</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Gradle Configuration
// build.gradle
plugins {
id 'org.whitesource' version '21.11.1'
}
whitesource {
orgToken = System.getenv('WHITESOURCE_ORG_TOKEN')
product = 'Your-Product-Name'
productVersion = project.version
project = project.name
checkPolicies = true
forceUpdate = true
failOnError = true
failOnPolicyViolation = true
includeSubProjects = true
aggregateModules = true
configurationPath = 'whitesource.config'
}
// Custom task for Mend scanning
task mendScan(dependsOn: whitesourceUpdate) {
doLast {
println "Mend scan completed for project: ${project.name}"
}
}
task mendCheckPolicies(dependsOn: whitesourceCheckPolicies) {
doLast {
println "Mend policy check completed"
}
}
// Task to generate Mend report
task generateMendReport {
doLast {
def reportDir = file("${buildDir}/reports/mend")
reportDir.mkdirs()
def reportFile = new File(reportDir, "mend-scan-summary.txt")
reportFile.text = """
Mend Security Scan Report
=========================
Project: ${project.name}
Version: ${project.version}
Scan Date: ${new Date()}
Configuration:
- Product: ${whitesource.product}
- Check Policies: ${whitesource.checkPolicies}
- Fail on Error: ${whitesource.failOnError}
"""
}
}
// Dependency for the scan process
whitesourceUpdate.finalizedBy generateMendReport
Unified Agent Configuration
whitesource.config
# Mend Unified Agent Configuration apiKey=your-org-api-key productName=Your-Product-Name projectName=your-project-name productToken=your-product-token # Scan Configuration forceUpdate=true forceCheckAllDependencies=true checkPolicies=true appPath=. includes=**/*.jar **/*.war **/*.ear **/*.zip excludes=**/test-classes/** **/test/** **/target/test-classes/** **/target/test/** # Project Configuration projectVersion=1.0.0 projectToken=your-project-token [email protected] # Policy Management failOnError=true failOnPolicyViolation=true forceCheckAllDependencies=true ignoreSourceFiles=true # Logging and Reporting log.level=INFO log.files.count=5 log.files.size=10 log.files.path=./whitesource-logs # Proxy Configuration (if needed) #proxy.host=proxy.company.com #proxy.port=8080 #proxy.user=username #proxy.password=password # Advanced Configuration #updateType=OVERRIDE #updateInventory=false #offline=false #docker.scanImages=false #docker.includes=**/*.tar **/*.tar.gz #docker.excludes=**/*test* **/*latest*
Java-based Configuration Manager
package com.company.mend;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
public class MendConfigManager {
private final Properties config;
private final String configPath;
public MendConfigManager(String configPath) {
this.configPath = configPath;
this.config = new Properties();
loadConfiguration();
}
public void loadConfiguration() {
try (FileInputStream input = new FileInputStream(configPath)) {
config.load(input);
System.out.println("Loaded Mend configuration from: " + configPath);
} catch (IOException e) {
System.err.println("Failed to load Mend configuration: " + e.getMessage());
setDefaultConfiguration();
}
}
public void saveConfiguration() {
try (FileOutputStream output = new FileOutputStream(configPath)) {
config.store(output, "Mend Unified Agent Configuration");
System.out.println("Saved Mend configuration to: " + configPath);
} catch (IOException e) {
System.err.println("Failed to save Mend configuration: " + e.getMessage());
}
}
private void setDefaultConfiguration() {
// Basic configuration
config.setProperty("apiKey", "${env.MEND_API_KEY}");
config.setProperty("productName", "Default-Product");
config.setProperty("projectName", "default-project");
config.setProperty("checkPolicies", "true");
config.setProperty("forceUpdate", "true");
config.setProperty("failOnError", "true");
config.setProperty("appPath", ".");
config.setProperty("includes", "**/*.jar **/*.war **/*.ear");
config.setProperty("excludes", "**/test-classes/** **/test/**");
config.setProperty("log.level", "INFO");
}
public void setProjectConfiguration(String projectName, String projectVersion) {
config.setProperty("projectName", projectName);
config.setProperty("projectVersion", projectVersion);
config.setProperty("projectToken", generateProjectToken(projectName));
}
public void configureForCI() {
config.setProperty("checkPolicies", "true");
config.setProperty("failOnPolicyViolation", "true");
config.setProperty("forceCheckAllDependencies", "true");
config.setProperty("requesterEmail", "[email protected]");
}
public void configureForLocal() {
config.setProperty("checkPolicies", "false");
config.setProperty("failOnPolicyViolation", "false");
config.setProperty("requesterEmail", System.getProperty("user.name") + "@company.com");
}
private String generateProjectToken(String projectName) {
return "project-token-" + projectName.hashCode();
}
public Properties getConfig() {
return new Properties(config);
}
public String getConfigAsString() {
StringBuilder sb = new StringBuilder();
config.forEach((key, value) ->
sb.append(key).append("=").append(value).append("\n"));
return sb.toString();
}
}
CI/CD Pipeline Integration
Jenkins Pipeline
// Jenkinsfile with Mend Integration
pipeline {
agent any
environment {
MEND_ORG_TOKEN = credentials('mend-org-token')
MEND_PRODUCT = 'Your-Product-Name'
MEND_API_URL = 'https://saas.mend.io'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'mvn clean compile -DskipTests'
}
}
stage('Mend Scan') {
steps {
script {
// Download Unified Agent
sh '''
curl -L -o /tmp/whitesource-agent.jar \
"https://github.com/whitesource/fs-agent-distribution/raw/master/standalone/whitesource-fs-agent-21.11.1.jar"
'''
// Run Mend Scan
sh """
java -jar /tmp/whitesource-agent.jar -c whitesource.config \
-apiKey ${MEND_ORG_TOKEN} \
-product ${MEND_PRODUCT} \
-project ${env.JOB_NAME} \
-projectVersion ${env.BUILD_NUMBER} \
-d . \
-wss.url ${MEND_API_URL}
"""
}
}
post {
always {
// Archive Mend reports
archiveArtifacts artifacts: 'whitesource/*.json, whitesource/*.html', allowEmptyArchive: true
mendPublishResults scanFile: 'whitesource/scanResult.json'
}
success {
// Send success notification
emailext (
subject: "Mend Scan PASSED: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: """
Mend security scan completed successfully for ${env.JOB_NAME}
Build: ${env.BUILD_URL}
No policy violations detected.
""",
to: '[email protected]'
)
}
failure {
// Send failure notification
emailext (
subject: "Mend Scan FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: """
Mend security scan failed for ${env.JOB_NAME}
Build: ${env.BUILD_URL}
Check the build logs for policy violation details.
""",
to: '[email protected]'
)
}
}
}
stage('Security Gate') {
steps {
script {
// Check for critical vulnerabilities
def scanReport = readJSON file: 'whitesource/scanResult.json'
def criticalVulns = scanReport.alerts.count { alert ->
alert.severity == 'CRITICAL' && alert.status != 'IGNORED'
}
if (criticalVulns > 0) {
error "Build failed: ${criticalVulns} critical vulnerabilities found"
}
// Check license compliance
def licenseViolations = scanReport.licenseViolations.size()
if (licenseViolations > 0) {
error "Build failed: ${licenseViolations} license violations found"
}
}
}
}
}
post {
always {
// Cleanup
sh 'rm -f /tmp/whitesource-agent.jar'
}
}
}
GitHub Actions Workflow
# .github/workflows/mend-scan.yml
name: Mend Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 2 * * 1' # Weekly on Monday at 2 AM
jobs:
mend-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Build project
run: mvn clean compile -DskipTests
- name: Download Mend Unified Agent
run: |
curl -L -o whitesource-agent.jar \
"https://github.com/whitesource/fs-agent-distribution/raw/master/standalone/whitesource-fs-agent-21.11.1.jar"
- name: Run Mend Scan
run: |
java -jar whitesource-agent.jar -c whitesource.config \
-apiKey ${{ secrets.MEND_ORG_TOKEN }} \
-product "GitHub-${{ github.repository }}" \
-project "${{ github.event.repository.name }}" \
-projectVersion "${{ github.sha }}" \
-d . \
-wss.url "https://saas.mend.io"
env:
MEND_ORG_TOKEN: ${{ secrets.MEND_ORG_TOKEN }}
- name: Upload Mend Report
uses: actions/upload-artifact@v3
with:
name: mend-security-report
path: |
whitesource/scanResult.json
whitesource/whitesource-*.html
retention-days: 30
- name: Check for Critical Vulnerabilities
run: |
python .github/scripts/check_mend_results.py
- name: Notify Security Channel
if: failure()
uses: 8398a7/action-slack@v3
with:
status: failure
text: |
Mend scan failed for ${{ github.repository }}
Check run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
fields: repo,commit,author,action,eventName,ref,workflow
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_SECURITY_WEBHOOK }}
Advanced Mend Integration Framework
Java Service for Mend Operations
package com.company.mend;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Service;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
@Service
public class MendService {
private final ObjectMapper objectMapper;
private final MendConfigManager configManager;
private final String apiBaseUrl;
private final String orgToken;
public MendService(ObjectMapper objectMapper,
MendConfigManager configManager,
@Value("${mend.api.url}") String apiBaseUrl,
@Value("${mend.org.token}") String orgToken) {
this.objectMapper = objectMapper;
this.configManager = configManager;
this.apiBaseUrl = apiBaseUrl;
this.orgToken = orgToken;
}
public ScanResult performScan(ProjectContext context) throws MendException {
try {
// Configure for specific project
configManager.setProjectConfiguration(
context.getProjectName(),
context.getProjectVersion()
);
// Run Mend scan
Process process = runUnifiedAgent(context.getProjectPath());
// Parse results
Path resultPath = context.getProjectPath().resolve("whitesource/scanResult.json");
return parseScanResult(resultPath);
} catch (Exception e) {
throw new MendException("Mend scan failed", e);
}
}
public List<Vulnerability> getProjectVulnerabilities(String projectToken) throws MendException {
try {
String endpoint = apiBaseUrl + "/api/v1.3";
Map<String, Object> request = Map.of(
"requestType", "getProjectVulnerabilities",
"userKey", orgToken,
"projectToken", projectToken
);
String response = sendApiRequest(endpoint, request);
return parseVulnerabilities(response);
} catch (Exception e) {
throw new MendException("Failed to fetch vulnerabilities", e);
}
}
public List<License> getProjectLicenses(String projectToken) throws MendException {
try {
String endpoint = apiBaseUrl + "/api/v1.3";
Map<String, Object> request = Map.of(
"requestType", "getProjectLicenses",
"userKey", orgToken,
"projectToken", projectToken
);
String response = sendApiRequest(endpoint, request);
return parseLicenses(response);
} catch (Exception e) {
throw new MendException("Failed to fetch licenses", e);
}
}
public boolean ignoreVulnerability(String vulnerabilityId,
String projectToken,
String comment) throws MendException {
try {
String endpoint = apiBaseUrl + "/api/v1.3";
Map<String, Object> request = Map.of(
"requestType", "ignoreVulnerability",
"userKey", orgToken,
"projectToken", projectToken,
"vulnerabilityId", vulnerabilityId,
"comment", comment
);
String response = sendApiRequest(endpoint, request);
return response.contains("\"success\":true");
} catch (Exception e) {
throw new MendException("Failed to ignore vulnerability", e);
}
}
private Process runUnifiedAgent(Path projectPath) throws IOException {
ProcessBuilder pb = new ProcessBuilder(
"java", "-jar", "whitesource-agent.jar",
"-c", "whitesource.config",
"-apiKey", orgToken,
"-d", projectPath.toString()
);
pb.directory(projectPath.toFile());
return pb.start();
}
private ScanResult parseScanResult(Path resultPath) throws IOException {
String jsonContent = Files.readString(resultPath);
return objectMapper.readValue(jsonContent, ScanResult.class);
}
private String sendApiRequest(String endpoint, Map<String, Object> request) throws IOException {
URL url = new URL(endpoint);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
String requestBody = objectMapper.writeValueAsString(request);
try (OutputStream os = conn.getOutputStream()) {
byte[] input = requestBody.getBytes("utf-8");
os.write(input, 0, input.length);
}
try (BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
return response.toString();
}
}
private List<Vulnerability> parseVulnerabilities(String response) throws IOException {
Map<?, ?> responseMap = objectMapper.readValue(response, Map.class);
List<Map<?, ?>> vulnerabilities = (List<Map<?, ?>>) responseMap.get("vulnerabilities");
List<Vulnerability> result = new ArrayList<>();
for (Map<?, ?> vuln : vulnerabilities) {
result.add(new Vulnerability(
(String) vuln.get("name"),
(String) vuln.get("severity"),
(String) vuln.get("score"),
(String) vuln.get("publishDate"),
(String) vuln.get("type")
));
}
return result;
}
private List<License> parseLicenses(String response) throws IOException {
Map<?, ?> responseMap = objectMapper.readValue(response, Map.class);
List<Map<?, ?>> licenses = (List<Map<?, ?>>) responseMap.get("projectLicenses");
List<License> result = new ArrayList<>();
for (Map<?, ?> license : licenses) {
result.add(new License(
(String) license.get("name"),
(String) license.get("status"),
(String) license.get("riskLevel")
));
}
return result;
}
}
// Domain Models
class ProjectContext {
private final String projectName;
private final String projectVersion;
private final Path projectPath;
public ProjectContext(String projectName, String projectVersion, Path projectPath) {
this.projectName = projectName;
this.projectVersion = projectVersion;
this.projectPath = projectPath;
}
// Getters...
}
class ScanResult {
private final List<Library> libraries;
private final List<Vulnerability> vulnerabilities;
private final List<LicenseViolation> licenseViolations;
private final ScanSummary summary;
public ScanResult(List<Library> libraries, List<Vulnerability> vulnerabilities,
List<LicenseViolation> licenseViolations, ScanSummary summary) {
this.libraries = libraries;
this.vulnerabilities = vulnerabilities;
this.licenseViolations = licenseViolations;
this.summary = summary;
}
// Getters...
}
class Vulnerability {
private final String name;
private final String severity;
private final String score;
private final String publishDate;
private final String type;
public Vulnerability(String name, String severity, String score,
String publishDate, String type) {
this.name = name;
this.severity = severity;
this.score = score;
this.publishDate = publishDate;
this.type = type;
}
// Getters...
}
class MendException extends Exception {
public MendException(String message) {
super(message);
}
public MendException(String message, Throwable cause) {
super(message, cause);
}
}
Policy Management and Automation
Automated Policy Enforcement
package com.company.mend.policy;
import com.company.mend.ScanResult;
import com.company.mend.Vulnerability;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
@Component
public class MendPolicyEnforcer {
private final List<SecurityPolicy> policies;
private final PolicyConfiguration config;
public MendPolicyEnforcer(PolicyConfiguration config) {
this.config = config;
this.policies = loadPolicies();
}
public PolicyValidationResult validateScanResult(ScanResult scanResult) {
List<PolicyViolation> violations = new ArrayList<>();
List<Vulnerability> criticalVulnerabilities = new ArrayList<>();
// Check vulnerability policies
for (Vulnerability vuln : scanResult.getVulnerabilities()) {
PolicyViolation violation = checkVulnerabilityPolicy(vuln);
if (violation != null) {
violations.add(violation);
}
if (isCriticalVulnerability(vuln)) {
criticalVulnerabilities.add(vuln);
}
}
// Check license policies
violations.addAll(checkLicensePolicies(scanResult.getLicenseViolations()));
return new PolicyValidationResult(violations, criticalVulnerabilities);
}
public boolean shouldFailBuild(PolicyValidationResult validation) {
if (config.isFailOnCritical() && !validation.getCriticalVulnerabilities().isEmpty()) {
return true;
}
if (config.isFailOnHighSeverity()) {
long highSeverityCount = validation.getViolations().stream()
.filter(v -> v.getSeverity() == Severity.HIGH)
.count();
return highSeverityCount > config.getMaxAllowedHighSeverity();
}
return !validation.getViolations().isEmpty() && config.isFailOnAnyViolation();
}
private PolicyViolation checkVulnerabilityPolicy(Vulnerability vulnerability) {
for (SecurityPolicy policy : policies) {
if (policy.matches(vulnerability)) {
return new PolicyViolation(
policy.getName(),
vulnerability,
policy.getSeverity(),
policy.getDescription()
);
}
}
return null;
}
private List<PolicyViolation> checkLicensePolicies(List<LicenseViolation> licenseViolations) {
List<PolicyViolation> violations = new ArrayList<>();
for (LicenseViolation licenseViolation : licenseViolations) {
if (isProhibitedLicense(licenseViolation.getLicense())) {
violations.add(new PolicyViolation(
"Prohibited License",
licenseViolation,
Severity.HIGH,
"License " + licenseViolation.getLicense() + " is prohibited"
));
}
}
return violations;
}
private boolean isCriticalVulnerability(Vulnerability vulnerability) {
return "CRITICAL".equalsIgnoreCase(vulnerability.getSeverity()) ||
(vulnerability.getScore() != null &&
Double.parseDouble(vulnerability.getScore()) >= 9.0);
}
private boolean isProhibitedLicense(String license) {
return config.getProhibitedLicenses().stream()
.anyMatch(prohibited -> prohibited.equalsIgnoreCase(license));
}
private List<SecurityPolicy> loadPolicies() {
List<SecurityPolicy> policies = new ArrayList<>();
// Critical vulnerability policy
policies.add(new SecurityPolicy(
"Critical Vulnerability Policy",
vuln -> "CRITICAL".equalsIgnoreCase(vuln.getSeverity()),
Severity.CRITICAL,
"Critical vulnerabilities are not allowed"
));
// High severity with no fix policy
policies.add(new SecurityPolicy(
"High Severity No Fix",
vuln -> "HIGH".equalsIgnoreCase(vuln.getSeverity()) &&
vuln.getType().contains("No Fix"),
Severity.HIGH,
"High severity vulnerabilities with no fix available"
));
// Specific CVE patterns
policies.add(new SecurityPolicy(
"Log4Shell Detection",
vuln -> vuln.getName().contains("CVE-2021-44228") ||
vuln.getName().contains("Log4Shell"),
Severity.CRITICAL,
"Log4Shell vulnerability detected"
));
return policies;
}
}
// Policy Configuration
@Component
@ConfigurationProperties(prefix = "mend.policy")
@Data
public class PolicyConfiguration {
private boolean failOnCritical = true;
private boolean failOnHighSeverity = true;
private boolean failOnAnyViolation = false;
private int maxAllowedHighSeverity = 0;
private List<String> prohibitedLicenses = Arrays.asList(
"GPL-3.0", "AGPL-3.0", "SSPL-1.0"
);
private List<String> allowedLicenses = Arrays.asList(
"Apache-2.0", "MIT", "BSD-3-Clause", "EPL-2.0"
);
}
class SecurityPolicy {
private final String name;
private final VulnerabilityPredicate predicate;
private final Severity severity;
private final String description;
public SecurityPolicy(String name, VulnerabilityPredicate predicate,
Severity severity, String description) {
this.name = name;
this.predicate = predicate;
this.severity = severity;
this.description = description;
}
public boolean matches(Vulnerability vulnerability) {
return predicate.test(vulnerability);
}
// Getters...
}
@FunctionalInterface
interface VulnerabilityPredicate {
boolean test(Vulnerability vulnerability);
}
enum Severity {
CRITICAL, HIGH, MEDIUM, LOW, INFO
}
class PolicyValidationResult {
private final List<PolicyViolation> violations;
private final List<Vulnerability> criticalVulnerabilities;
public PolicyValidationResult(List<PolicyViolation> violations,
List<Vulnerability> criticalVulnerabilities) {
this.violations = violations;
this.criticalVulnerabilities = criticalVulnerabilities;
}
// Getters...
}
Reporting and Dashboard Integration
Custom Report Generation
package com.company.mend.reporting;
import com.company.mend.ScanResult;
import com.company.mend.Vulnerability;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component
public class MendReportGenerator {
private final ObjectMapper objectMapper;
public MendReportGenerator(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
public void generateHtmlReport(ScanResult scanResult, Path outputPath) throws IOException {
String htmlContent = buildHtmlReport(scanResult);
Files.write(outputPath, htmlContent.getBytes());
}
public void generateJsonReport(ScanResult scanResult, Path outputPath) throws IOException {
Map<String, Object> report = buildJsonReport(scanResult);
objectMapper.writerWithDefaultPrettyPrinter()
.writeValue(outputPath.toFile(), report);
}
public void generateMarkdownReport(ScanResult scanResult, Path outputPath) throws IOException {
String markdown = buildMarkdownReport(scanResult);
Files.write(outputPath, markdown.getBytes());
}
public void generateSecurityDashboard(ScanResult scanResult, Path outputPath) throws IOException {
Map<String, Object> dashboardData = buildDashboardData(scanResult);
String htmlDashboard = buildDashboardHtml(dashboardData);
Files.write(outputPath, htmlDashboard.getBytes());
}
private String buildHtmlReport(ScanResult scanResult) {
return """
<!DOCTYPE html>
<html>
<head>
<title>Mend Security Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.critical { color: #d73a49; font-weight: bold; }
.high { color: #f66a0a; }
.medium { color: #ffd33d; }
.low { color: #0366d6; }
.vulnerability { border: 1px solid #e1e4e8; padding: 10px; margin: 10px 0; }
.summary { background: #f6f8fa; padding: 15px; border-radius: 5px; }
</style>
</head>
<body>
<h1>Mend Security Scan Report</h1>
<div class="summary">
<h2>Scan Summary</h2>
<p><strong>Generated:</strong> %s</p>
<p><strong>Total Dependencies:</strong> %d</p>
<p><strong>Total Vulnerabilities:</strong> %d</p>
<p><strong>Critical Vulnerabilities:</strong> <span class="critical">%d</span></p>
<p><strong>High Vulnerabilities:</strong> <span class="high">%d</span></p>
</div>
<h2>Vulnerabilities</h2>
%s
</body>
</html>
""".formatted(
LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
scanResult.getLibraries().size(),
scanResult.getVulnerabilities().size(),
countVulnerabilitiesBySeverity(scanResult.getVulnerabilities(), "CRITICAL"),
countVulnerabilitiesBySeverity(scanResult.getVulnerabilities(), "HIGH"),
buildVulnerabilitiesHtml(scanResult.getVulnerabilities())
);
}
private String buildVulnerabilitiesHtml(List<Vulnerability> vulnerabilities) {
return vulnerabilities.stream()
.sorted(Comparator.comparing(Vulnerability::getSeverity).reversed())
.map(vuln -> """
<div class="vulnerability">
<h3 class="%s">%s - %s</h3>
<p><strong>Score:</strong> %s</p>
<p><strong>Published:</strong> %s</p>
<p><strong>Type:</strong> %s</p>
</div>
""".formatted(
vuln.getSeverity().toLowerCase(),
vuln.getName(),
vuln.getSeverity(),
vuln.getScore(),
vuln.getPublishDate(),
vuln.getType()
))
.collect(Collectors.joining());
}
private String buildMarkdownReport(ScanResult scanResult) {
StringBuilder md = new StringBuilder();
md.append("# Mend Security Scan Report\n\n");
// Summary
md.append("## Scan Summary\n\n");
md.append("- **Generated**: ").append(LocalDateTime.now()).append("\n");
md.append("- **Total Dependencies**: ").append(scanResult.getLibraries().size()).append("\n");
md.append("- **Total Vulnerabilities**: ").append(scanResult.getVulnerabilities().size()).append("\n");
md.append("- **Critical Vulnerabilities**: ").append(countVulnerabilitiesBySeverity(scanResult.getVulnerabilities(), "CRITICAL")).append("\n");
md.append("- **High Vulnerabilities**: ").append(countVulnerabilitiesBySeverity(scanResult.getVulnerabilities(), "HIGH")).append("\n\n");
// Critical Vulnerabilities
List<Vulnerability> criticalVulns = getVulnerabilitiesBySeverity(scanResult.getVulnerabilities(), "CRITICAL");
if (!criticalVulns.isEmpty()) {
md.append("## 🔴 Critical Vulnerabilities\n\n");
for (Vulnerability vuln : criticalVulns) {
md.append("### ").append(vuln.getName()).append("\n");
md.append("- **Severity**: ").append(vuln.getSeverity()).append("\n");
md.append("- **Score**: ").append(vuln.getScore()).append("\n");
md.append("- **Type**: ").append(vuln.getType()).append("\n");
md.append("- **Published**: ").append(vuln.getPublishDate()).append("\n\n");
}
}
return md.toString();
}
private long countVulnerabilitiesBySeverity(List<Vulnerability> vulnerabilities, String severity) {
return vulnerabilities.stream()
.filter(v -> severity.equalsIgnoreCase(v.getSeverity()))
.count();
}
private List<Vulnerability> getVulnerabilitiesBySeverity(List<Vulnerability> vulnerabilities, String severity) {
return vulnerabilities.stream()
.filter(v -> severity.equalsIgnoreCase(v.getSeverity()))
.collect(Collectors.toList());
}
private Map<String, Object> buildJsonReport(ScanResult scanResult) {
return Map.of(
"scanSummary", Map.of(
"generated", LocalDateTime.now().toString(),
"totalDependencies", scanResult.getLibraries().size(),
"totalVulnerabilities", scanResult.getVulnerabilities().size(),
"vulnerabilityBreakdown", getVulnerabilityBreakdown(scanResult.getVulnerabilities())
),
"criticalVulnerabilities", getVulnerabilitiesBySeverity(scanResult.getVulnerabilities(), "CRITICAL"),
"highVulnerabilities", getVulnerabilitiesBySeverity(scanResult.getVulnerabilities(), "HIGH"),
"libraries", scanResult.getLibraries()
);
}
private Map<String, Long> getVulnerabilityBreakdown(List<Vulnerability> vulnerabilities) {
return vulnerabilities.stream()
.collect(Collectors.groupingBy(
Vulnerability::getSeverity,
Collectors.counting()
));
}
}
Best Practices and Configuration
Security Configuration
package com.company.mend.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
@ConfigurationProperties(prefix = "mend")
@Data
public class MendConfiguration {
// API Configuration
private String apiUrl = "https://saas.mend.io";
private String orgToken;
private String productName;
private int timeoutSeconds = 300;
// Scan Configuration
private boolean checkPolicies = true;
private boolean failOnError = true;
private boolean failOnPolicyViolation = true;
private boolean forceUpdate = true;
private List<String> includes = List.of("**/*.jar", "**/*.war", "**/*.ear");
private List<String> excludes = List.of("**/test-classes/**", "**/test/**");
// Reporting Configuration
private boolean generateHtmlReport = true;
private boolean generateJsonReport = true;
private String reportDirectory = "target/mend-reports";
// Alerting Configuration
private boolean alertOnCritical = true;
private boolean alertOnHighSeverity = true;
private List<String> alertEmails;
private String slackWebhookUrl;
// Proxy Configuration
private Proxy proxy;
@Data
public static class Proxy {
private String host;
private int port;
private String username;
private String password;
private boolean enabled = false;
}
public boolean isValid() {
return orgToken != null && !orgToken.trim().isEmpty() &&
productName != null && !productName.trim().isEmpty();
}
}
Integration Checklist
- Obtain Mend Organization Token from your Mend dashboard
- Configure build tools (Maven/Gradle) with Mend plugins
- Set up Unified Agent for comprehensive scanning
- Configure CI/CD pipelines for automated scanning
- Define security policies based on organizational requirements
- Set up reporting and alerting for security findings
- Establish remediation workflows for addressing vulnerabilities
- Monitor and tune scan configurations regularly
Key Benefits
- Comprehensive vulnerability detection across all dependencies
- License compliance management with policy enforcement
- Automated scanning integrated into development workflows
- Detailed reporting with actionable insights
- Policy-based security gates in CI/CD pipelines
By implementing these Mend integration patterns, organizations can effectively manage open-source security risks while maintaining development velocity and compliance requirements.