Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fcli-core/fcli-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
// Inter-project dependencies
val refs = listOf(
"fcliCommonRef","fcliCommonThirdpartyRef","fcliCommonCiRef","fcliCommonActionRef","fcliCommonToolRef",
"fcliActionRef","fcliAiAssistRef","fcliAviatorRef","fcliConfigRef",
"fcliActionRef","fcliAiAssistRef","fcliAviatorCommonRef","fcliAviatorRef","fcliConfigRef",
"fcliFoDRef","fcliSSCRef","fcliSCSastRef","fcliSCDastRef",
"fcliToolRef","fcliLicenseRef","fcliUtilRef"
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2021-2026 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*/
package com.fortify.cli.aviator._common.cli.converter;

import com.fortify.cli.aviator.fpr.utils.ISourceDecoder;
import com.fortify.cli.aviator.fpr.utils.SourceDecoders;

import picocli.CommandLine.ITypeConverter;
import picocli.CommandLine.TypeConversionException;

/**
* Picocli adapter: maps a single {@code --source-encodings} token to an
* {@link ISourceDecoder} via the domain factory {@link SourceDecoders}.
*/
public final class SourceDecoderConverter implements ITypeConverter<ISourceDecoder> {
@Override
public ISourceDecoder convert(String value) {
try {
return SourceDecoders.fromToken(value);
} catch (IllegalArgumentException e) {
// Covers blank tokens, IllegalCharsetNameException, UnsupportedCharsetException
throw new TypeConversionException(
e.getMessage() != null ? e.getMessage() : "Invalid source encoding '" + value + "'");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2021-2026 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*/
package com.fortify.cli.aviator._common.cli.mixin;

import java.util.List;

import com.fortify.cli.aviator._common.cli.converter.SourceDecoderConverter;
import com.fortify.cli.aviator.fpr.utils.ISourceDecoder;
import com.fortify.cli.aviator.fpr.utils.SourceDecoders;

import lombok.Getter;
import picocli.CommandLine.Option;

/**
* Shared {@code --source-encodings} option for Aviator commands that decode
* (and optionally re-encode) source files from an FPR.
*/
public class SourceEncodingsMixin {
@Getter
@Option(names = {"--source-encodings"},
split = ",",
converter = SourceDecoderConverter.class,
defaultValue = SourceDecoders.DEFAULT_SOURCE_ENCODINGS,
paramLabel = "encoding",
descriptionKey = "fcli.aviator.source-encodings")
private List<ISourceDecoder> sourceDecoders;

/**
* Returns a single decoder that tries the configured candidates in order.
*/
public ISourceDecoder getSourceDecoder() {
return SourceDecoders.of(sourceDecoders);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package com.fortify.cli.aviator.applyRemediation;

import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -20,6 +22,8 @@
import com.fortify.cli.aviator.config.IAviatorLogger;
import com.fortify.cli.aviator.fpr.processor.RemediationProcessor;
import com.fortify.cli.aviator.fpr.processor.RemediationProcessor.RemediationMetric;
import com.fortify.cli.aviator.fpr.utils.ISourceDecoder;
import com.fortify.cli.aviator.fpr.utils.SourceDecoders;
import com.fortify.cli.aviator.util.FprHandle;


Expand All @@ -28,6 +32,12 @@ public class ApplyAutoRemediationOnSource {

public static RemediationMetric applyRemediations(FprHandle fprHandle, String sourceCodeDirectory, IAviatorLogger logger)
throws AviatorSimpleException, AviatorTechnicalException {
return applyRemediations(fprHandle, sourceCodeDirectory, SourceDecoders.defaults(), logger);
}

public static RemediationMetric applyRemediations(FprHandle fprHandle, String sourceCodeDirectory,
ISourceDecoder sourceDecoder, IAviatorLogger logger)
throws AviatorSimpleException, AviatorTechnicalException {

LOG.info("Starting apply auto-remediation process for file: {}", fprHandle.getFprPath());

Expand All @@ -37,8 +47,8 @@ public static RemediationMetric applyRemediations(FprHandle fprHandle, String so
}
LOG.info("FPR validation successful");

RemediationProcessor remediationProcessor = new RemediationProcessor(fprHandle, sourceCodeDirectory);
RemediationProcessor remediationProcessor = new RemediationProcessor(fprHandle, sourceCodeDirectory,
Objects.requireNonNull(sourceDecoder, "sourceDecoder"));
return remediationProcessor.processRemediationXML();

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import java.io.File;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -39,6 +40,7 @@
import com.fortify.cli.aviator.fpr.model.FPRInfo;
import com.fortify.cli.aviator.fpr.processor.AuditProcessor;
import com.fortify.cli.aviator.fpr.processor.StreamingFVDLProcessor;
import com.fortify.cli.aviator.fpr.utils.ISourceDecoder;
import com.fortify.cli.aviator.util.FprHandle;
import com.fortify.cli.aviator.util.ResourceUtil;

Expand All @@ -52,8 +54,11 @@ public static FPRAuditResult auditFPR(AuditFprOptions options)
options.getFprHandle().validate();
AviatorConfigManager.getInstance();

// Non-null: AuditFprOptions defaults via @Builder.Default; CLI mixin always supplies a decoder.
ISourceDecoder sourceDecoder = options.getSourceDecoder();

// --- STAGE 1: PARSING ---
ParsedFprData parsedData = prepareAndParseFpr(options.getFprHandle());
ParsedFprData parsedData = prepareAndParseFpr(options.getFprHandle(), sourceDecoder);
TagMappingConfig tagMappingConfig = loadTagMappingConfig(options.getTagMappingPath());
Map<String, String> issueCategoryLookup = tagMappingConfig.requiresCategoryForSuppressionEvaluation()
? buildIssueCategoryLookup(parsedData.vulnerabilities)
Expand All @@ -69,22 +74,21 @@ public static FPRAuditResult auditFPR(AuditFprOptions options)
Map<String, AuditResponse> auditResponses = new ConcurrentHashMap<>();
AuditOutcome auditOutcome = performAviatorAudit(
parsedData, options.getLogger(), options.getToken(), options.getAppVersion(), options.getUrl(), options.getSscAppName(), options.getSscAppVersion(),
auditResponses, filterSelection, options.getFprHandle(), options.getFolderPriorityOrder()
auditResponses, filterSelection, options.getFprHandle(), options.getFolderPriorityOrder(), sourceDecoder
);

// --- STAGE 4: FINALIZATION ---
return finalizeFprAudit(
auditOutcome, auditResponses, parsedData.auditProcessor,
tagMappingConfig, issueCategoryLookup, parsedData.fprInfo
tagMappingConfig, issueCategoryLookup, parsedData.fprInfo, parsedData.streamingFVDLProcessor
);
}

private static ParsedFprData prepareAndParseFpr(FprHandle fprHandle) {
private static ParsedFprData prepareAndParseFpr(FprHandle fprHandle, ISourceDecoder sourceDecoder) {
try {
// Processors now take the FprHandle directly, no more extracted path
AuditProcessor auditProcessor = new AuditProcessor(fprHandle);
//FVDLProcessor fvdlProcessor = new FVDLProcessor(fprHandle);
StreamingFVDLProcessor streamingFVDLProcessor = new StreamingFVDLProcessor(fprHandle);
AuditProcessor auditProcessor = new AuditProcessor(fprHandle, sourceDecoder);
StreamingFVDLProcessor streamingFVDLProcessor = new StreamingFVDLProcessor(fprHandle, sourceDecoder);

Map<String, AuditIssue> auditIssueMap = auditProcessor.processAuditXML();
FPRProcessor fprProcessor = new FPRProcessor(fprHandle, auditIssueMap, auditProcessor);
Expand Down Expand Up @@ -126,7 +130,8 @@ private static Map<String, String> buildIssueCategoryLookup(List<Vulnerability>
private static AuditOutcome performAviatorAudit(
ParsedFprData parsedData, IAviatorLogger logger,
String token, String appVersion, String url, String sscAppName, String sscAppVersion,
Map<String, AuditResponse> auditResponsesToFill, FilterSelection filterSelection, FprHandle fprHandle, List<String> folderPriorityOrder) {
Map<String, AuditResponse> auditResponsesToFill, FilterSelection filterSelection, FprHandle fprHandle,
List<String> folderPriorityOrder, ISourceDecoder sourceDecoder) {
SourceLanguageResolver sourceLanguageResolver =
new SourceLanguageResolver(parsedData.streamingFVDLProcessor.getFvdlMetadata());
parsedData.streamingFVDLProcessor.getFvdlMetadata().clearSourceFileTypeIndexes();
Expand All @@ -141,7 +146,9 @@ private static AuditOutcome performAviatorAudit(
filterSelection,
logger,
folderPriorityOrder,
sourceLanguageResolver
sourceLanguageResolver,
sourceDecoder,
parsedData.streamingFVDLProcessor.getFvdlMetadata()
);
return issueAuditor.performAudit(
auditResponsesToFill, token, appVersion, parsedData.fprInfo.getBuildId(), url, fprHandle
Expand All @@ -151,7 +158,7 @@ private static AuditOutcome performAviatorAudit(
private static FPRAuditResult finalizeFprAudit(
AuditOutcome auditOutcome, Map<String, AuditResponse> auditResponses,
AuditProcessor auditProcessor, TagMappingConfig tagMappingConfig,
Map<String, String> issueCategoryLookup, FPRInfo fprInfo) {
Map<String, String> issueCategoryLookup, FPRInfo fprInfo, StreamingFVDLProcessor streamingFVDLProcessor) {

int totalIssuesToAudit = auditOutcome.getTotalIssuesToAudit();
if (auditResponses.isEmpty()) {
Expand All @@ -167,6 +174,8 @@ private static FPRAuditResult finalizeFprAudit(
long issuesSuccessfullyAudited = auditResponses.values().stream()
.filter(response -> "SUCCESS".equalsIgnoreCase(response.getStatus()))
.count();
Map<String, Integer> skippedByReason = getSkippedAuditReasons(auditResponses, totalIssuesToAudit);
int issuesSkipped = skippedByReason.values().stream().mapToInt(Integer::intValue).sum();

String status;
String message = null;
Expand All @@ -192,10 +201,67 @@ private static FPRAuditResult finalizeFprAudit(
File updatedFile = null;
if (issuesSuccessfullyAudited > 0) {
updatedFile = auditProcessor.updateAndSaveAuditAndRemediationsXml(
auditResponses, tagMappingConfig, issueCategoryLookup, fprInfo);
auditResponses, tagMappingConfig, issueCategoryLookup, fprInfo,
streamingFVDLProcessor.getFvdlMetadata());
}
AuditProcessor.RemediationGenerationMetric remediationGenerationMetric = auditProcessor.getLastRemediationGenerationMetric();

if (!skippedByReason.isEmpty()) {
LOG.info("Skipped audit issues by reason: {}", skippedByReason);
}
if (!remediationGenerationMetric.skippedByReason().isEmpty()) {
LOG.info("Skipped audit remediation generation by reason: {}", remediationGenerationMetric.skippedByReason());
}

LOG.info("FPR audit process completed with status: {}", status);
return new FPRAuditResult(updatedFile, status, message, (int) issuesSuccessfullyAudited, totalIssuesToAudit);
return new FPRAuditResult(updatedFile, status, message, (int) issuesSuccessfullyAudited, totalIssuesToAudit,
issuesSkipped, skippedByReason, remediationGenerationMetric.skippedRemediations(),
remediationGenerationMetric.skippedByReason());
}

private static Map<String, Integer> getSkippedAuditReasons(Map<String, AuditResponse> auditResponses, int totalIssuesToAudit) {
Map<String, Integer> skippedByReason = new LinkedHashMap<>();
auditResponses.values().stream()
.filter(response -> !"SUCCESS".equalsIgnoreCase(response.getStatus()))
.map(AuditFPR::getSkippedAuditReason)
.forEach(reason -> recordSkipped(skippedByReason, reason));
int missingResponses = Math.max(0, totalIssuesToAudit - auditResponses.size());
if (missingResponses > 0) {
skippedByReason.merge("No audit response received", missingResponses, Integer::sum);
}
return skippedByReason;
}

private static String getSkippedAuditReason(AuditResponse response) {
String statusMessage = response == null ? null : response.getStatusMessage();
String message = statusMessage == null || statusMessage.isBlank()
? response == null ? null : response.getStatus()
: statusMessage;
if (message == null || message.isBlank()) {
return "Unknown audit failure";
}
if (message.startsWith("Client-side pre-processing error: ")) {
message = message.substring("Client-side pre-processing error: ".length());
}
if (message.startsWith("Could not decode source file")) {
return "Source file decode failed";
}
if (message.contains("was not found in the FPR")) {
return "Source file not found in FPR";
}
if (message.contains("could not be read from the FPR")) {
return "Source file read failed";
}
if ("FAILED".equalsIgnoreCase(message)) {
return "Audit failed";
}
if ("SKIPPED".equalsIgnoreCase(message)) {
return "Skipped by Aviator";
}
return message;
}

private static void recordSkipped(Map<String, Integer> skippedByReason, String reason) {
skippedByReason.merge(reason, 1, Integer::sum);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
Expand Down Expand Up @@ -48,7 +49,10 @@
import com.fortify.cli.aviator.fpr.filter.VulnerabilityFilterer;
import com.fortify.cli.aviator.fpr.model.AuditIssue;
import com.fortify.cli.aviator.fpr.model.FPRInfo;
import com.fortify.cli.aviator.fpr.model.FVDLMetadata;
import com.fortify.cli.aviator.fpr.processor.AuditProcessor;
import com.fortify.cli.aviator.fpr.utils.ISourceDecoder;
import com.fortify.cli.aviator.fpr.utils.SourceDecoders;
import com.fortify.cli.aviator.grpc.AviatorGrpcClient;
import com.fortify.cli.aviator.grpc.AviatorGrpcClientHelper;
import com.fortify.cli.aviator.util.Constants;
Expand Down Expand Up @@ -80,6 +84,8 @@ public class IssueAuditor {
private TagDefinition humanAuditTag;
private TagDefinition aviatorStatusTag;
private final SourceLanguageResolver sourceLanguageResolver;
private final ISourceDecoder sourceDecoder;
private final FVDLMetadata fvdlMetadata;

private final IAviatorLogger logger;
private final List<String> customPriorityOrder;
Expand All @@ -88,6 +94,15 @@ public IssueAuditor(List<Vulnerability> vulnerabilities, AuditProcessor auditPro
FPRInfo fprInfo, String SSCApplicationName, String SSCApplicationVersion,
FilterSelection filterSelection, IAviatorLogger logger, List<String> customPriorityOrder,
SourceLanguageResolver sourceLanguageResolver) {
this(vulnerabilities, auditProcessor, auditIssueMap, fprInfo, SSCApplicationName, SSCApplicationVersion,
filterSelection, logger, customPriorityOrder, sourceLanguageResolver, SourceDecoders.defaults(), null);
}

public IssueAuditor(List<Vulnerability> vulnerabilities, AuditProcessor auditProcessor, Map<String, AuditIssue> auditIssueMap,
FPRInfo fprInfo, String SSCApplicationName, String SSCApplicationVersion,
FilterSelection filterSelection, IAviatorLogger logger, List<String> customPriorityOrder,
SourceLanguageResolver sourceLanguageResolver, ISourceDecoder sourceDecoder,
FVDLMetadata fvdlMetadata) {
this.logger = logger;
this.customPriorityOrder = customPriorityOrder;
this.MAX_PER_CATEGORY = Constants.MAX_PER_CATEGORY;
Expand All @@ -103,6 +118,8 @@ public IssueAuditor(List<Vulnerability> vulnerabilities, AuditProcessor auditPro
this.SSCApplicationName = SSCApplicationName;
this.SSCApplicationVersion = SSCApplicationVersion;
this.sourceLanguageResolver = sourceLanguageResolver;
this.sourceDecoder = Objects.requireNonNull(sourceDecoder, "sourceDecoder");
this.fvdlMetadata = fvdlMetadata;
this.analysisTag = fprInfo.getFilterTemplate().getTagDefinitions().stream().filter(t -> "Analysis".equalsIgnoreCase(t.getName())).findFirst().orElse(null);
this.resultsTag = resolveResultTag("", "", analysisTag);
}
Expand Down Expand Up @@ -162,7 +179,8 @@ public AuditOutcome performAudit(Map<String, AuditResponse> auditResponses, Stri
} else {
try (AviatorGrpcClient client = AviatorGrpcClientHelper.createClient(url, logger, DEFAULT_PING_INTERVAL_SECONDS)) {
CompletableFuture<Map<String, AuditResponse>> future =
client.processBatchRequests(promptsToAudit, projectName, fprInfo.getBuildId(), SSCApplicationName, SSCApplicationVersion, token, fprHandle, customPriorityOrder);
client.processBatchRequests(promptsToAudit, projectName, fprInfo.getBuildId(), SSCApplicationName,
SSCApplicationVersion, token, fprHandle, customPriorityOrder, sourceDecoder, fvdlMetadata);
Map<String, AuditResponse> responses = future.get(500, TimeUnit.MINUTES);
responses.forEach((requestId, response) -> auditResponses.put(response.getIssueId(), response));
logger.progress("Audit completed");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import java.util.List;

import com.fortify.cli.aviator.config.IAviatorLogger;
import com.fortify.cli.aviator.fpr.utils.ISourceDecoder;
import com.fortify.cli.aviator.fpr.utils.SourceDecoders;
import com.fortify.cli.aviator.util.FprHandle;

import lombok.Builder;
Expand All @@ -34,4 +36,5 @@ public class AuditFprOptions {
private final boolean noFilterSet;
private final List<String> folderNames;
private final List<String> folderPriorityOrder;
@Builder.Default private final ISourceDecoder sourceDecoder = SourceDecoders.defaults();
}
Loading
Loading