Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0633dbd
Initial plan
Copilot Nov 26, 2025
98e73c6
Add JSON-RPC server for IDE plugin integration
Copilot Nov 26, 2025
7786d94
Address code review feedback
Copilot Nov 26, 2025
0c633c0
Add caching and async execution support to JSON-RPC server
Copilot Nov 26, 2025
94a2792
Add product-specific session methods and improve security
Copilot Nov 26, 2025
38cbca8
Update SSC login: remove ci-token, add client-auth-token and sc-sast-url
Copilot Nov 26, 2025
5638637
Update RPC server usage help with correct method signatures
Copilot Nov 27, 2025
65ac253
Merge remote-tracking branch 'origin/copilot/add-json-rpc-server-for-…
SangameshV Jan 13, 2026
f4568ba
Fixing build failures
SangameshV Jan 13, 2026
c06824a
Adding separate nodes for Command Parameters, Command Optons, Output …
SangameshV Jan 21, 2026
92367a0
Supporting additional details in json output of the command cli util…
SangameshV Feb 18, 2026
a4abd36
Enhance JSON-RPC fcli.listCommands with module, related-module, and P…
SangameshV Feb 23, 2026
cd21fb5
fix: correct FoD session login credential grouping and tenant option …
SangameshV Mar 6, 2026
d534d56
fix: enforce FoD app/release exclusivity and correct session login op…
SangameshV Mar 6, 2026
bdd6898
Added datatype support for file
Mar 18, 2026
b7db46d
chore: Merge pull request #935 from fortify/feat/v3.x/sourceanalyzer-…
rsenden Mar 20, 2026
24628b2
fix: allow tenant with client credentials while keeping tenant requir…
SangameshV Mar 20, 2026
39678fb
chore(fix): fixing GraalVM native image build
SangameshV Mar 20, 2026
89c7ffd
Handle all valid --tenant/-t syntaxes for client credentials in prepr…
SangameshV Mar 20, 2026
f16f56e
chore: removed the manual throw of exception, pico cli does it
SangameshV Mar 20, 2026
83359f0
Clarify FoD tenant requirement for user credentials
rsenden Mar 23, 2026
9f24efd
Add TODO comments for future enhancements
rsenden Mar 23, 2026
74de679
Fix FoD tenant preprocessor to correctly handle space and equals synt…
SangameshV Mar 27, 2026
9e552cb
chore: Add support for auditing SSC issues (#919)
jmadhur87 Mar 9, 2026
8ede75d
feat: Support Sourceanalyzer tool registartion and performing local s…
SangameshV Mar 3, 2026
d5c473b
chore: refactor tool definitions handling (handled review points)
SangameshV Mar 11, 2026
c574e43
fix: use compatible tool definition version lookups
SangameshV Mar 29, 2026
2e7ed9e
fix(ci): replace blocked marketplace actions
SangameshV Mar 29, 2026
f26715e
Changed data type for options to File type for few options
Mar 31, 2026
f4889a5
Merge branch 'sangamesh/latest-mcp-json-rpc-impl' of https://github.c…
Mar 31, 2026
d34fe8b
fix: `fcli fod`: Use default attribute values from FoD 26.2+ if avail…
SangameshV Apr 10, 2026
68d2fd4
fix: `fcli fod`: Fix loading of attribute definitions on FoD 26.2+
rsenden Apr 8, 2026
4b5a7a7
Added option for ssc & fod session validation
SangameshV Apr 15, 2026
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
135 changes: 69 additions & 66 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
package com.fortify.cli.generic_action._main.cli.cmd;

import com.fortify.cli.common.cli.cmd.AbstractContainerCommand;
import com.fortify.cli.common.cli.util.ModuleType;
import com.fortify.cli.common.cli.util.ProductModule;
import com.fortify.cli.generic_action.action.cli.cmd.GenericActionAsciidocCommand;
import com.fortify.cli.generic_action.action.cli.cmd.GenericActionGetCommand;
import com.fortify.cli.generic_action.action.cli.cmd.GenericActionHelpCommand;
Expand All @@ -24,6 +26,7 @@

import picocli.CommandLine.Command;

@ProductModule(ModuleType.OTHER)
@Command(
name = "action",
resourceBundle = "com.fortify.cli.generic_action.i18n.GenericActionMessages",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,7 @@ public RemediationMetric processRemediationXML() {
int totalRemediations;
int appliedRemediations;

// Sanitize and normalize the base source directory path once.
String trimmedSourceDir = sourceCodeDirectory.trim();
if (trimmedSourceDir.length() > 1 &&
((trimmedSourceDir.startsWith("\"") && trimmedSourceDir.endsWith("\"")) ||
(trimmedSourceDir.startsWith("'") && trimmedSourceDir.endsWith("'")))) {
trimmedSourceDir = trimmedSourceDir.substring(1, trimmedSourceDir.length() - 1);
}
final Path sourceBasePath = Paths.get(trimmedSourceDir).toAbsolutePath().normalize();
final Path sourceBasePath = Paths.get(sourceCodeDirectory).normalize();

try (InputStream remediationStream = Files.newInputStream(remediationPath)) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@
import com.fortify.cli.aviator.ssc.cli.cmd.AviatorSSCCommands;
import com.fortify.cli.aviator.token.cli.cmd.AviatorTokenCommands;
import com.fortify.cli.common.cli.cmd.AbstractContainerCommand;
import com.fortify.cli.common.cli.util.ModuleType;
import com.fortify.cli.common.cli.util.ProductModule;
import com.fortify.cli.common.cli.util.RelatedModules;

import picocli.CommandLine.Command;

@ProductModule(ModuleType.PRODUCT)
@RelatedModules({"ssc"})
@Command(
name = "aviator",
resourceBundle = "com.fortify.cli.aviator.i18n.AviatorMessages",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package com.fortify.cli.aviator.fod.cli.cmd;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
Expand Down Expand Up @@ -51,7 +52,7 @@ public class AviatorFoDApplyRemediationsCommand extends AbstractFoDJsonNodeOutpu
@Mixin private FoDDelimiterMixin delimiterMixin; // Is automatically injected in resolver mixins
@Mixin private FoDReleaseByQualifiedNameOrIdResolverMixin.RequiredOption releaseResolver;
private static final Logger LOG = LoggerFactory.getLogger(AviatorFoDApplyRemediationsCommand.class);
@Option(names = {"--source-dir"}) private String sourceCodeDirectory = System.getProperty("user.dir");
@Option(names = {"--source-dir"}) private File sourceCodeDirectory = new File(System.getProperty("user.dir"));

@Override @SneakyThrows
public JsonNode getJsonNode(UnirestInstance unirest) {
Expand All @@ -64,7 +65,7 @@ public JsonNode getJsonNode(UnirestInstance unirest) {
}

private void validateSourceCodeDirectory() {
if (sourceCodeDirectory == null || sourceCodeDirectory.isBlank()) {
if (sourceCodeDirectory == null || !sourceCodeDirectory.isDirectory()) {
throw new FcliSimpleException("--source-dir must specify a valid directory path");
}
}
Expand All @@ -78,7 +79,7 @@ private JsonNode processFprRemediations(UnirestInstance unirest, FoDReleaseDescr

logger.progress("Status: Processing FPR with Aviator for Applying Auto Remediations");
try (FprHandle fprHandle = new FprHandle(downloadedFprPath)) {
var remediationMetric = ApplyAutoRemediationOnSource.applyRemediations(fprHandle, sourceCodeDirectory, logger);
var remediationMetric = ApplyAutoRemediationOnSource.applyRemediations(fprHandle, sourceCodeDirectory.getAbsolutePath(), logger);
String status = remediationMetric.appliedRemediations() > 0 ? "Remediation-Applied" : "No-Remediation-Applied";
return AviatorFoDApplyRemediationsHelper.buildResultNode(rd, remediationMetric.totalRemediations(), remediationMetric.appliedRemediations(), remediationMetric.skippedRemediations(), status);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package com.fortify.cli.aviator.ssc.cli.cmd;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -50,7 +51,7 @@ public class AviatorSSCApplyRemediationsCommand extends AbstractSSCJsonNodeOutpu
//Downloading of the FPR will be based on artifact and not app version
@Mixin private SSCArtifactResolverMixin.RequiredOption artifactResolver;
private static final Logger LOG = LoggerFactory.getLogger(AviatorSSCApplyRemediationsCommand.class);
@Option(names = {"--source-dir"}) private String sourceCodeDirectory = System.getProperty("user.dir");
@Option(names = {"--source-dir"}) private File sourceCodeDirectory = new File(System.getProperty("user.dir"));

@Override
@SneakyThrows
Expand All @@ -64,7 +65,7 @@ public JsonNode getJsonNode(UnirestInstance unirest) {
}

private void validateSourceCodeDirectory() {
if (sourceCodeDirectory == null || sourceCodeDirectory.isBlank()) {
if (sourceCodeDirectory == null || !sourceCodeDirectory.isDirectory()) {
throw new FcliSimpleException("--source-dir must specify a valid directory path");
}
}
Expand All @@ -84,7 +85,7 @@ private JsonNode processFprRemediations(UnirestInstance unirest, SSCArtifactDesc
logger.progress("Status: Processing FPR with Aviator for Applying Auto Remediations");

try (FprHandle fprHandle = new FprHandle(fprPath)) {
var remediationMetric = ApplyAutoRemediationOnSource.applyRemediations(fprHandle, sourceCodeDirectory, logger);
var remediationMetric = ApplyAutoRemediationOnSource.applyRemediations(fprHandle, sourceCodeDirectory.getAbsolutePath(), logger);
String status = remediationMetric.appliedRemediations() > 0 ? "Remediation-Applied" : "No-Remediation-Applied";

return AviatorSSCApplyRemediationsHelper.buildResultNode(ad, remediationMetric.totalRemediations(), remediationMetric.appliedRemediations(), remediationMetric.skippedRemediations(), status);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.io.File;
import java.lang.reflect.Field;

import org.junit.jupiter.api.Test;
Expand All @@ -29,12 +30,12 @@ void testSourceCodeDirectoryHasDefaultValue() throws Exception {

Field field = AviatorFoDApplyRemediationsCommand.class.getDeclaredField("sourceCodeDirectory");
field.setAccessible(true);
String fieldValue = (String) field.get(command);
File fieldValue = (File) field.get(command);

assertNotNull(fieldValue,
"sourceCodeDirectory must have default value to prevent NPE when --source-dir not specified");

assertEquals(System.getProperty("user.dir"), fieldValue,
assertEquals(new File(System.getProperty("user.dir")), fieldValue,
"sourceCodeDirectory default should be current working directory");
}

Expand All @@ -45,10 +46,10 @@ void testSourceCodeDirectoryCanBeOverridden() throws Exception {
Field field = AviatorFoDApplyRemediationsCommand.class.getDeclaredField("sourceCodeDirectory");
field.setAccessible(true);

String customPath = "/custom/source/directory";
File customPath = new File("/custom/source/directory");
field.set(command, customPath);

String fieldValue = (String) field.get(command);
File fieldValue = (File) field.get(command);

assertEquals(customPath, fieldValue,
"sourceCodeDirectory should be overridable when --source-dir option is provided");
Expand All @@ -60,7 +61,7 @@ void testBlankSourceCodeDirectoryThrowsException() throws Exception {

Field field = AviatorFoDApplyRemediationsCommand.class.getDeclaredField("sourceCodeDirectory");
field.setAccessible(true);
field.set(command, "");
field.set(command, new File("/test/Blank/SourceCodeDirectory"));

assertThrows(FcliSimpleException.class, () -> command.getJsonNode(null),
"Blank sourceCodeDirectory should throw FcliSimpleException");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.io.File;
import java.lang.reflect.Field;

import org.junit.jupiter.api.Test;
Expand All @@ -29,12 +30,12 @@ void testSourceCodeDirectoryHasDefaultValue() throws Exception {

Field field = AviatorSSCApplyRemediationsCommand.class.getDeclaredField("sourceCodeDirectory");
field.setAccessible(true);
String fieldValue = (String) field.get(command);
File fieldValue = (File) field.get(command);

assertNotNull(fieldValue,
"sourceCodeDirectory must have default value to prevent NPE when --source-dir not specified");

assertEquals(System.getProperty("user.dir"), fieldValue,
assertEquals(new File(System.getProperty("user.dir")), fieldValue,
"sourceCodeDirectory default should be current working directory");
}

Expand All @@ -45,10 +46,10 @@ void testSourceCodeDirectoryCanBeOverridden() throws Exception {
Field field = AviatorSSCApplyRemediationsCommand.class.getDeclaredField("sourceCodeDirectory");
field.setAccessible(true);

String customPath = "/custom/source/directory";
File customPath = new File("/custom/source/directory");
field.set(command, customPath);

String fieldValue = (String) field.get(command);
File fieldValue = (File) field.get(command);

assertEquals(customPath, fieldValue,
"sourceCodeDirectory should be overridable when --source-dir option is provided");
Expand All @@ -60,7 +61,7 @@ void testBlankSourceCodeDirectoryThrowsException() throws Exception {

Field field = AviatorSSCApplyRemediationsCommand.class.getDeclaredField("sourceCodeDirectory");
field.setAccessible(true);
field.set(command, "");
field.set(command, new File("/nonexistent/path/for/testing"));

assertThrows(FcliSimpleException.class, () -> command.getJsonNode(null),
"Blank sourceCodeDirectory should throw FcliSimpleException");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
public class AbstractActionSignCommand extends AbstractOutputCommand implements IJsonNodeSupplier, IActionCommandResultSupplier {
private static final ObjectMapper objectMapper = JsonHelper.getObjectMapper();
private static final Logger LOG = LoggerFactory.getLogger(AbstractActionSignCommand.class);
@Getter @Mixin OutputHelperMixins.TableNoQuery outputHelper;
@Getter @Mixin private OutputHelperMixins.TableNoQuery outputHelper;
@Option(names = "--in", required=true, descriptionKey="fcli.action.sign.in")
private Path actionFileToSign;
@Option(names = "--out", required=true, descriptionKey="fcli.action.sign.out")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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.common.cli.util;

/**
* Enum that holds the type of a module as a product module (SSC, FoD, Aviator, SC-SAST, SC-DAST)
* or a non-product/other module (util, tool, license, actions, config, ...)
*
* @author Sangamesh Vijaykumar
*/

public enum ModuleType {
PRODUCT, // SSC, FoD, Aviator, SC-SAST, SC-DAST
OTHER // util, tool, license, actions, config, ...
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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.common.cli.util;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
* Marks a module as a product module (SSC, FoD, Aviator, SC-SAST, SC-DAST)
* or a non-product/other module (util, tool, license, actions, config, ...).
*
* @author Sangamesh Vijaykumar
*/
@Retention(RUNTIME)
@Target(ElementType.TYPE)
public @interface ProductModule {
ModuleType value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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.common.cli.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Defines which base modules a module is related to.
* Example: @RelatedModules({"ssc","fod"}) on the tool/util module.
*
* @author Sangamesh Vijaykumar
*/

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RelatedModules {
String[] value(); // base modules this module is related to, e.g. {"ssc","fod"}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
import java.util.Date;
import java.util.function.Consumer;

import org.apache.commons.lang3.StringUtils;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fortify.cli.common.cli.mixin.CommonOptionMixins;
Expand Down Expand Up @@ -49,8 +47,8 @@ public abstract class AbstractReportGenerateCommand extends AbstractOutputComman
{
@ArgGroup(exclusive = true, multiplicity = "1") private OutputArgGroup outputArgGroup;
private static final class OutputArgGroup {
@Option(names = {"-z", "--report-zip"}, required = true) private String reportZipName;
@Option(names = {"-d", "--report-dir"}, required = true) private String reportDirName;
@Option(names = {"-z", "--report-zip"}, required = true) private File reportZipName;
@Option(names = {"-d", "--report-dir"}, required = true) private File reportDirName;
}
@Mixin private CommonOptionMixins.RequireConfirmation requireConfirmation;

Expand All @@ -70,21 +68,20 @@ public JsonNode getJsonNode() {
}

private IReportWriter createReportWriter() {
if ( StringUtils.isNotBlank(outputArgGroup.reportZipName) ) {
if ( outputArgGroup.reportZipName != null) {
deleteExisting(outputArgGroup.reportZipName, File::delete);
return new ReportZipWriter(outputArgGroup.reportZipName, getCommandHelper().getMessageResolver());
} else if ( StringUtils.isNotBlank(outputArgGroup.reportDirName) ) {
return new ReportZipWriter(outputArgGroup.reportZipName.getAbsolutePath(), getCommandHelper().getMessageResolver());
} else if ( outputArgGroup.reportDirName != null) {
deleteExisting(outputArgGroup.reportDirName, this::deleteDirectory);
return new ReportDirWriter(outputArgGroup.reportDirName, getCommandHelper().getMessageResolver());
return new ReportDirWriter(outputArgGroup.reportDirName.getAbsolutePath(), getCommandHelper().getMessageResolver());
} else {
throw new FcliSimpleException("Either --report-file or --report-dir must be specified");
}
}

private void deleteExisting(String name, Consumer<File> deleter) {
var file = new File(name);
private void deleteExisting(File file, Consumer<File> deleter) {
if ( file.exists() ) {
requireConfirmation.checkConfirmed(name);
requireConfirmation.checkConfirmed(file);
deleter.accept(file);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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.common.session.cli.mixin;

import java.util.function.Consumer;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;

import picocli.CommandLine.Option;

public class ValidateSessionOptionMixin {
@Option(names = {"--validate"}, required = false, descriptionKey = "fcli.session.validate")
private boolean validate;

public void validateIfNeeded(ArrayNode result, Consumer<JsonNode> validator) {
if (validate) {
result.forEach(validator);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ url = Base URL for accessing the remote system.
k = Disable SSL checks.
connect-timeout = Connection timeout for this session, for example 30s (30 seconds), 5m (5 minutes). Default value: ${default-connect-timeout}.
socket-timeout = Socket timeout for this session, for example 30s (30 seconds), 5m (5 minutes). Default value: ${default-socket-timeout}.
fcli.session.validate = Verify the actual session status against the server.

#################################################################################################################
# The following are technical properties that shouldn't be internationalized ####################################
Expand Down
Loading
Loading