Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 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
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 @@ -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
@@ -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 @@ -13,13 +13,16 @@
package com.fortify.cli.config._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.config.language.cli.cmd.LanguageCommands;
import com.fortify.cli.config.proxy.cli.cmd.ProxyCommands;
import com.fortify.cli.config.publickey.cli.cmd.PublicKeyCommands;
import com.fortify.cli.config.truststore.cli.cmd.TrustStoreCommands;

import picocli.CommandLine.Command;

@ProductModule(ModuleType.OTHER)
@Command(
name = "config",
aliases = "cfg",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;

@Command(name = OutputHelperMixins.Login.CMD_NAME, sortOptions = false)
@Command(name = OutputHelperMixins.Login.CMD_NAME, sortOptions = false, preprocessor = FoDSessionTenantIgnoringPreprocessor.class)
public class FoDSessionLoginCommand extends AbstractSessionLoginCommand<FoDSessionDescriptor> {
@Getter @Mixin private OutputHelperMixins.Login outputHelper;
@Getter private FoDSessionHelper sessionHelper = FoDSessionHelper.instance();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* 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.fod._common.session.cli.cmd;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import com.formkiq.graalvm.annotations.Reflectable;

import picocli.CommandLine.IParameterPreprocessor;
import picocli.CommandLine.Model.ArgSpec;
import picocli.CommandLine.Model.CommandSpec;

/**
* Removes --tenant/-t from command line arguments when client credentials are used.
* This allows tenant to stay mandatory in the user credential argument group while
* accepting tenant as a no-op for client credential authentication.
*
* @author Sangamesh Vijayakumar
*/
@Reflectable
public final class FoDSessionTenantIgnoringPreprocessor implements IParameterPreprocessor {
private static final String TENANT_PRIMARY_NAME = "--tenant";
private static final String CLIENT_ID_PRIMARY_NAME = "--client-id";
private static final String CLIENT_SECRET_PRIMARY_NAME = "--client-secret";

@Override
public boolean preprocess(Stack<String> args, CommandSpec commandSpec, ArgSpec argSpec, Map<String, Object> info) {
if (argSpec != null || args == null || args.isEmpty() || commandSpec == null) {
return false;
}

var tenantNames = resolveOptionNames(commandSpec, TENANT_PRIMARY_NAME, "-t");
var clientIdNames = resolveOptionNames(commandSpec, CLIENT_ID_PRIMARY_NAME);
var clientSecretNames = resolveOptionNames(commandSpec, CLIENT_SECRET_PRIMARY_NAME);

var cliArgs = new ArrayList<>(args);
if (!hasClientCredentials(cliArgs, clientIdNames, clientSecretNames)) {
return false;
}

var filtered = filterOutTenantOptions(cliArgs, tenantNames);
if (filtered.size() != cliArgs.size()) {
args.clear();
args.addAll(filtered);
}
return false;
}

private static Set<String> resolveOptionNames(CommandSpec commandSpec, String primaryName, String... fallbackNames) {
var optionSpec = commandSpec.findOption(primaryName);
var names = new LinkedHashSet<String>();
if (optionSpec != null) {
names.addAll(Arrays.asList(optionSpec.names()));
} else {
names.add(primaryName);
names.addAll(Arrays.asList(fallbackNames));
}
return names;
}

private static boolean hasClientCredentials(List<String> cliArgs, Set<String> clientIdNames, Set<String> clientSecretNames) {
return hasAnyOption(cliArgs, clientIdNames) || hasAnyOption(cliArgs, clientSecretNames);
}

private static boolean hasAnyOption(List<String> cliArgs, Set<String> optionNames) {
return cliArgs.stream().anyMatch(arg -> isOptionToken(arg, optionNames));
}

private static List<String> filterOutTenantOptions(List<String> cliArgs, Set<String> tenantNames) {
// The Picocli Stack stores args in reverse order relative to the original command line
// (first arg is at the top / last index). We reverse to process in original left-to-right
// order so that i+1 correctly identifies the space-separated value token.
var orderedArgs = new ArrayList<>(cliArgs);
Collections.reverse(orderedArgs);

var filteredInOrder = new ArrayList<String>();
for (int i = 0; i < orderedArgs.size(); i++) {
var arg = orderedArgs.get(i);
if (isExactOptionToken(arg, tenantNames)) {
if (i + 1 < orderedArgs.size() && !orderedArgs.get(i + 1).startsWith("-")) {
i++; // Skip the separate option value token.
}
continue;
}
if (isInlineOptionToken(arg, tenantNames) || isCompactShortOptionToken(arg, tenantNames)) {
continue;
}
filteredInOrder.add(arg);
}

// Reverse back to Stack order before returning.
Collections.reverse(filteredInOrder);
return filteredInOrder;
}

private static boolean isOptionToken(String arg, Set<String> optionNames) {
return isExactOptionToken(arg, optionNames)
|| isInlineOptionToken(arg, optionNames)
|| isCompactShortOptionToken(arg, optionNames);
}

private static boolean isExactOptionToken(String arg, Set<String> optionNames) {
return optionNames.contains(arg);
}

private static boolean isInlineOptionToken(String arg, Set<String> optionNames) {
return optionNames.stream().anyMatch(name -> arg.startsWith(name + "="));
}

private static boolean isCompactShortOptionToken(String arg, Set<String> optionNames) {
return optionNames.stream()
.filter(name -> name.startsWith("-") && !name.startsWith("--") && name.length() == 2)
.anyMatch(name -> arg.startsWith(name) && arg.length() > name.length() && arg.charAt(name.length()) != '=');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import org.apache.commons.lang3.StringUtils;

import com.fortify.cli.common.exception.FcliSimpleException;
import com.fortify.cli.common.log.LogSensitivityLevel;
import com.fortify.cli.common.log.MaskValue;
import com.fortify.cli.common.rest.cli.mixin.UrlConfigOptions;
Expand All @@ -43,18 +42,21 @@ public static class FoDAuthOptions {
@Getter private FoDCredentialOptions credentialOptions = new FoDCredentialOptions();
@Option(names="--scopes", defaultValue="api-tenant", split=",")
@Getter private String[] scopes;
@Option(names = {"-t", "--tenant"}, required = false)
@MaskValue(sensitivity = LogSensitivityLevel.low, description = "FOD TENANT")
@Getter private String tenant; // Optional: required only for user credentials
}

public static class FoDCredentialOptions {
@ArgGroup(exclusive = false, multiplicity = "1", order = 1)
@Getter private UserCredentialOptions userCredentialOptions = new UserCredentialOptions();
@Getter private FoDUserCredentialOptions userCredentialOptions = new FoDUserCredentialOptions();
@ArgGroup(exclusive = false, multiplicity = "1", order = 2)
@Getter private FoDClientCredentialOptions clientCredentialOptions = new FoDClientCredentialOptions();
}

public static class FoDUserCredentialOptions extends UserCredentialOptions {
@Option(names = {"-t", "--tenant"}, required = true)
@MaskValue(sensitivity = LogSensitivityLevel.low, description = "FOD TENANT")
@Getter private String tenant;
}

public static class FoDClientCredentialOptions implements IFoDClientCredentials {
@Option(names = {"--client-id"}, required = true)
@MaskValue(sensitivity = LogSensitivityLevel.medium, description = "FOD CLIENT ID")
Expand All @@ -64,7 +66,7 @@ public static class FoDClientCredentialOptions implements IFoDClientCredentials
@Getter private String clientSecret;
}

public UserCredentialOptions getUserCredentialOptions() {
public FoDUserCredentialOptions getUserCredentialOptions() {
return Optional.ofNullable(authOptions)
.map(FoDAuthOptions::getCredentialOptions)
.map(FoDCredentialOptions::getUserCredentialOptions)
Expand All @@ -79,16 +81,17 @@ public FoDClientCredentialOptions getClientCredentialOptions() {
}

public final boolean hasUserCredentials() {
return getUserCredentialOptions()!=null;
var userCredentialOptions = getUserCredentialOptions();
return userCredentialOptions!=null
&& StringUtils.isNotBlank(userCredentialOptions.getTenant())
&& StringUtils.isNotBlank(userCredentialOptions.getUser())
&& userCredentialOptions.getPassword()!=null
&& userCredentialOptions.getPassword().length > 0;
}

public final BasicFoDUserCredentials getUserCredentials() {
var u = getUserCredentialOptions();
var t = Optional.ofNullable(authOptions).map(FoDAuthOptions::getTenant).orElse(null);
if ( u==null || StringUtils.isBlank(t) || StringUtils.isBlank(u.getUser()) || u.getPassword()==null ) {
throw new FcliSimpleException("--tenant, --user and --password must all be specified for user credential authentication");
}
return BasicFoDUserCredentials.builder().tenant(t).user(u.getUser()).password(u.getPassword()).build();
return BasicFoDUserCredentials.builder().tenant(u.getTenant()).user(u.getUser()).password(u.getPassword()).build();
}

public final boolean hasClientCredentials() {
Expand Down Expand Up @@ -133,9 +136,6 @@ public static final class Builder {
public Builder user(String user){ this.user=user; return this; }
public Builder password(char[] password){ this.password=password; return this; }
public BasicFoDUserCredentials build(){
if ( StringUtils.isBlank(tenant) || StringUtils.isBlank(user) || password==null || password.length==0 ) {
throw new FcliSimpleException("--tenant, --user and --password must all be specified for user credential authentication");
}
return new BasicFoDUserCredentials(this);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
package com.fortify.cli.fod._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.fod._common.session.cli.cmd.FoDSessionCommands;
import com.fortify.cli.fod.access_control.cli.cmd.FoDAccessControlCommands;
import com.fortify.cli.fod.action.cli.cmd.FoDActionCommands;
Expand All @@ -30,6 +32,7 @@

import picocli.CommandLine.Command;

@ProductModule(ModuleType.PRODUCT)
@Command(
name = "fod",
resourceBundle = "com.fortify.cli.fod.i18n.FoDMessages",
Expand Down
Loading
Loading