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.