From ce3d751915dfb381c63524d78d3ff1f1d9153f69 Mon Sep 17 00:00:00 2001 From: Umadevi Santhanam Date: Tue, 1 Sep 2026 21:33:54 +0530 Subject: [PATCH 1/5] added RemediationKey record --- .../src/main/java/com/fortify/cli/app/FortifyCLI.java | 5 +++-- .../cli/aviator/fpr/processor/RemediationProcessor.java | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java b/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java index f2ab435cb7..2b0c14c5e8 100644 --- a/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java +++ b/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java @@ -17,9 +17,9 @@ /** *

This class provides the {@link #main(String[])} entrypoint into the application, - * and also registers some GraalVM features, allowing the application to run properly + * and also registers some GraalVM features, allowing the application to run properly * as GraalVM native images.

- * + * * @author Ruud Senden */ public class FortifyCLI { @@ -28,6 +28,7 @@ public class FortifyCLI { * @param args Command line options passed to Fortify CLI */ public static final void main(String[] args) { + System.out.println("FCLI started"); System.exit(execute(args)); } diff --git a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java index 2f717dd66b..e30595afde 100644 --- a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java +++ b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java @@ -78,6 +78,8 @@ private record PendingFileWrite(String filename, Path filePath, String content, private record RollbackFileWrite(String filename, Path filePath, byte[] originalBytes) {} + private record RemediationKey(String fileName, Path filePath,int lineFrom,int lineTo,String comparisonCode){} + private enum SkipReason { SOURCE_FILE_MISSING("Source file missing"), SOURCE_FILE_OUTSIDE_SOURCE_DIR("Source file outside source directory"), @@ -157,6 +159,7 @@ public RemediationMetric processRemediationXML() { Set modifiedFiles = new LinkedHashSet<>(); Map skippedByReason = new LinkedHashMap<>(); + // Sanitize and normalize the base source directory path once. String trimmedSourceDir = sourceCodeDirectory.trim(); if (trimmedSourceDir.length() > 1 && From d0c2b3d7a270b9de724106905bc5f76ecb4c05df Mon Sep 17 00:00:00 2001 From: Umadevi Santhanam Date: Tue, 1 Sep 2026 22:43:43 +0530 Subject: [PATCH 2/5] added RemediationKey record --- .../fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java | 1 - 1 file changed, 1 deletion(-) diff --git a/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java b/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java index 2b0c14c5e8..34eaf65c90 100644 --- a/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java +++ b/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/FortifyCLI.java @@ -28,7 +28,6 @@ public class FortifyCLI { * @param args Command line options passed to Fortify CLI */ public static final void main(String[] args) { - System.out.println("FCLI started"); System.exit(execute(args)); } From 2f1e9fa1dd829916a7202b9dddb3d94c8abffdc3 Mon Sep 17 00:00:00 2001 From: Umadevi Santhanam Date: Wed, 2 Sep 2026 11:52:07 +0530 Subject: [PATCH 3/5] added RemediationKey record --- .../fpr/processor/RemediationProcessor.java | 283 +++++++++++++++++- 1 file changed, 270 insertions(+), 13 deletions(-) diff --git a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java index e30595afde..ae16ef2b1d 100644 --- a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java +++ b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java @@ -30,6 +30,8 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.zip.ZipFile; @@ -37,6 +39,7 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import com.fortify.cli.aviator.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -53,8 +56,6 @@ import com.fortify.cli.aviator.fpr.utils.SourceDecoders; import com.fortify.cli.aviator.fpr.utils.SourceEncoder; import com.fortify.cli.aviator.fpr.utils.SourceEncoder.SourceEncodeException; -import com.fortify.cli.aviator.util.FprHandle; -import com.fortify.cli.aviator.util.FuzzyContextSearcher; public class RemediationProcessor { private static final Logger LOG = LoggerFactory.getLogger(RemediationProcessor.class); @@ -156,9 +157,10 @@ public RemediationMetric processRemediationXML() { Document remediationDoc; int totalRemediations; int appliedRemediations; + int identicalRemediations = 0; Set modifiedFiles = new LinkedHashSet<>(); Map skippedByReason = new LinkedHashMap<>(); - + Map remediationLookup = new LinkedHashMap<>(); // Sanitize and normalize the base source directory path once. String trimmedSourceDir = sourceCodeDirectory.trim(); @@ -187,29 +189,109 @@ public RemediationMetric processRemediationXML() { LOG.debug("Loaded {} remediation entries from {}", totalRemediations, remediationPath); appliedRemediations = 0; for (int i = 0; i < remediationNodes.getLength(); i++) { - Element remediation = (Element) remediationNodes.item(i); - if (processRemediation(remediation, sourceBasePath, fvdlMetadata, modifiedFiles, skippedByReason)) { + Element remediation = + (Element) remediationNodes.item(i); + + String instanceId = + remediation.getAttribute("instanceId"); + + List remediationKeys = + createRemediationKeys( + remediation, + sourceBasePath); + + String identicalInstanceId = null; + + for (RemediationKey key : remediationKeys) { + String existingInstanceId = + remediationLookup.get(key); + + if (existingInstanceId != null) { + identicalInstanceId = existingInstanceId; + break; + } + } + + if (identicalInstanceId != null) { + identicalRemediations++; appliedRemediations++; + + LOG.info( + "Identical found: {}", + identicalInstanceId); + + LOG.info( + "Identical Remediation Applied: {} is identical to {}", + instanceId, + identicalInstanceId); + + continue; + } + + if (processRemediation( + remediation, + sourceBasePath, + fvdlMetadata, + modifiedFiles, + skippedByReason)) { + + appliedRemediations++; + + for (RemediationKey key : remediationKeys) { + remediationLookup.put(key, instanceId); + } } } } catch (ParserConfigurationException | SAXException | IOException e) { - LOG.error("Error parsing remediations.xml file: {}", remediationPath, e); - throw new AviatorTechnicalException("Error processing remediation.xml file.", e); + LOG.error( + "Error parsing remediations.xml file: {}", + remediationPath, + e); + + throw new AviatorTechnicalException( + "Error processing remediation.xml file.", + e); + } catch (AviatorTechnicalException e) { throw e; + } catch (Exception e) { - LOG.error("Unexpected error processing remediation.xml: {}", remediationPath, e); - throw new AviatorTechnicalException("Unexpected error processing remediations.xml.", e); + LOG.error( + "Unexpected error processing remediations.xml: {}", + remediationPath, + e); + + throw new AviatorTechnicalException( + "Unexpected error processing remediations.xml.", + e); } - int skippedRemediations = totalRemediations - appliedRemediations; - LOG.info("Auto-remediation summary: total={}, applied={}, skipped={}", totalRemediations, appliedRemediations, skippedRemediations); + + int skippedRemediations = + totalRemediations - appliedRemediations; + + LOG.info( + "Auto-remediation summary: total={}, applied={}, identical={}, skipped={}", + totalRemediations, + appliedRemediations, + identicalRemediations, + skippedRemediations); + if (!skippedByReason.isEmpty()) { - LOG.info("Skipped remediations by reason: {}", formatSkippedReasons(skippedByReason)); + LOG.info( + "Skipped remediations by reason: {}", + formatSkippedReasons(skippedByReason)); } - return new RemediationMetric(totalRemediations, appliedRemediations, skippedRemediations, modifiedFiles, skippedByReason); + + return new RemediationMetric( + totalRemediations, + appliedRemediations, + skippedRemediations, + modifiedFiles, + skippedByReason); } + private boolean processRemediation(Element remediation, Path sourceBasePath, FVDLMetadata fvdlMetadata, Set modifiedFiles, Map skippedByReason) { String instanceId = remediation.getAttribute("instanceId"); @@ -580,4 +662,179 @@ private String formatSkippedReasons(Map skippedByReason) { skippedByReason.forEach((reason, count) -> parts.add(reason + "=" + count)); return String.join(", ", parts); } + + private RemediationKey createRemediationKey( + Element fileChanges, + Element change, + Path sourceBasePath, + String comparisonCode) { + + String fileName = getRequiredElementText(fileChanges, "Filename"); + Path filePath = sourceBasePath.resolve(fileName).normalize(); + + int lineFrom = parseRequiredInt(change, "LineFrom"); + int lineTo = parseRequiredInt(change, "LineTo"); + + return new RemediationKey( + fileName, + filePath, + lineFrom, + lineTo, + comparisonCode + ); + } + + + private String trimBlankLines(String content) { + String[] lines = content.split("\\R", -1); + + int start = 0; + int end = lines.length - 1; + + while (start <= end && lines[start].isBlank()) { + start++; + } + + while (end >= start && lines[end].isBlank()) { + end--; + } + + if (start > end) { + return ""; + } + + return String.join( + System.lineSeparator(), + Arrays.copyOfRange(lines, start, end + 1)); + } + + private String normalizeProposedCode(String content, String fileName) { + if (content == null) { + return null; + } + + String language = FileTypeLanguageMapperUtil.getProgrammingLanguage( + FileUtil.getFileExtension(fileName)); + + String commentSymbol = + LanguageCommentMapperUtil.getProgrammingLanguageComment(language); + + if ("Unknown".equals(commentSymbol)) { + return trimBlankLines(content); + } + + String closingToken = commentSymbol.equals("" + : commentSymbol.equals("<%--") ? "--%>" + : null; + + Pattern markerPattern = Pattern.compile( + "[ \\t]*" + Pattern.quote(commentSymbol) + " L\\d+" + + (closingToken != null + ? "[ \\t]*" + Pattern.quote(closingToken) + : "") + + "[ \\t]*$"); + + String[] lines = content.split("\\R", -1); + StringBuilder result = new StringBuilder(); + + for (int i = 0; i < lines.length; i++) { + Matcher matcher = markerPattern.matcher(lines[i]); + + result.append( + matcher.find() + ? lines[i].substring(0, matcher.start()) + : lines[i]); + + if (i < lines.length - 1) { + result.append(System.lineSeparator()); + } + } + + return trimBlankLines(result.toString()); + } + + private String createComparisonCode(String normalizedCode, String fileName) { + if (normalizedCode == null) { + return null; + } + + String language = FileTypeLanguageMapperUtil.getProgrammingLanguage( + FileUtil.getFileExtension(fileName)); + + String commentSymbol = + LanguageCommentMapperUtil.getProgrammingLanguageComment(language); + + if ("Unknown".equals(commentSymbol)) { + return normalizedCode.replaceAll("\\s+", ""); + } + + String comparisonCode = normalizedCode; + + // Remove block comments + String closingToken = commentSymbol.equals("" + : commentSymbol.equals("<%--") ? "--%>" + : null; + + if (closingToken != null) { + comparisonCode = comparisonCode.replaceAll( + "(?s)" + Pattern.quote(commentSymbol) + + ".*?" + Pattern.quote(closingToken), + ""); + } else if ("//".equals(commentSymbol)) { + comparisonCode = comparisonCode.replaceAll( + "(?m)" + Pattern.quote(commentSymbol) + ".*$", + ""); + comparisonCode = comparisonCode.replaceAll( + "(?s)/\\*.*?\\*/", + ""); + } else if ("#".equals(commentSymbol)) { + comparisonCode = comparisonCode.replaceAll( + "(?m)" + Pattern.quote(commentSymbol) + ".*$", + ""); + } + + // Normalize whitespace + return comparisonCode.replaceAll("\\s+", ""); + } + + private List createRemediationKeys( + Element remediation, + Path sourceBasePath) { + + List keys = new ArrayList<>(); + + NodeList fileChangesNodes = + remediation.getElementsByTagNameNS(NAMESPACE_URI, "FileChanges"); + + for (int i = 0; i < fileChangesNodes.getLength(); i++) { + Element fileChanges = (Element) fileChangesNodes.item(i); + + NodeList changeNodes = + fileChanges.getElementsByTagNameNS(NAMESPACE_URI, "Change"); + + for (int j = 0; j < changeNodes.getLength(); j++) { + Element change = (Element) changeNodes.item(j); + + String fileName = + getRequiredElementText(fileChanges, "Filename"); + + String newCode = + getRequiredElementText(change, "NewCode"); + + String normalizedCode = + normalizeProposedCode(newCode, fileName); + + String comparisonCode = + createComparisonCode(normalizedCode, fileName); + + keys.add(createRemediationKey( + fileChanges, + change, + sourceBasePath, + comparisonCode)); + } + } + + return keys; + } } From ba8e635f7141a3e15acb3260e351f46597c6d07d Mon Sep 17 00:00:00 2001 From: Umadevi Santhanam Date: Wed, 2 Sep 2026 11:55:46 +0530 Subject: [PATCH 4/5] added logic --- .../fpr/processor/RemediationProcessor.java | 38 ++++--------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java index ae16ef2b1d..1f9c6fd6ef 100644 --- a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java +++ b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java @@ -189,14 +189,12 @@ public RemediationMetric processRemediationXML() { LOG.debug("Loaded {} remediation entries from {}", totalRemediations, remediationPath); appliedRemediations = 0; for (int i = 0; i < remediationNodes.getLength(); i++) { - Element remediation = - (Element) remediationNodes.item(i); + Element remediation = (Element) remediationNodes.item(i); String instanceId = remediation.getAttribute("instanceId"); - List remediationKeys = - createRemediationKeys( + List remediationKeys = createRemediationKeys( remediation, sourceBasePath); @@ -244,27 +242,14 @@ public RemediationMetric processRemediationXML() { } } catch (ParserConfigurationException | SAXException | IOException e) { - LOG.error( - "Error parsing remediations.xml file: {}", - remediationPath, - e); - - throw new AviatorTechnicalException( - "Error processing remediation.xml file.", - e); - + LOG.error("Error parsing remediations.xml file: {}", remediationPath, e); + throw new AviatorTechnicalException("Error processing remediation.xml file.", e); } catch (AviatorTechnicalException e) { throw e; } catch (Exception e) { - LOG.error( - "Unexpected error processing remediations.xml: {}", - remediationPath, - e); - - throw new AviatorTechnicalException( - "Unexpected error processing remediations.xml.", - e); + LOG.error("Unexpected error processing remediation.xml: {}", remediationPath, e); + throw new AviatorTechnicalException("Unexpected error processing remediations.xml.", e); } int skippedRemediations = @@ -279,16 +264,9 @@ public RemediationMetric processRemediationXML() { if (!skippedByReason.isEmpty()) { LOG.info( - "Skipped remediations by reason: {}", - formatSkippedReasons(skippedByReason)); + "Skipped remediations by reason: {}",formatSkippedReasons(skippedByReason)); } - - return new RemediationMetric( - totalRemediations, - appliedRemediations, - skippedRemediations, - modifiedFiles, - skippedByReason); + return new RemediationMetric(totalRemediations, appliedRemediations, skippedRemediations, modifiedFiles, skippedByReason); } From b55490b8c0c35e7b51506aa869d9ef4d4e5e0239 Mon Sep 17 00:00:00 2001 From: Umadevi Santhanam Date: Wed, 2 Sep 2026 11:59:27 +0530 Subject: [PATCH 5/5] added logic --- .../aviator/fpr/processor/RemediationProcessor.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java index 1f9c6fd6ef..64fa2cf8c5 100644 --- a/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java +++ b/fcli-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/fpr/processor/RemediationProcessor.java @@ -252,15 +252,8 @@ public RemediationMetric processRemediationXML() { throw new AviatorTechnicalException("Unexpected error processing remediations.xml.", e); } - int skippedRemediations = - totalRemediations - appliedRemediations; - - LOG.info( - "Auto-remediation summary: total={}, applied={}, identical={}, skipped={}", - totalRemediations, - appliedRemediations, - identicalRemediations, - skippedRemediations); + int skippedRemediations = totalRemediations - appliedRemediations; + LOG.info("Auto-remediation summary: total={}, applied={},indentical={},skipped={}", totalRemediations, appliedRemediations, identicalRemediations,skippedRemediations); if (!skippedByReason.isEmpty()) { LOG.info(