Skip to content

feat:Add DAST audit command and related functionality - #1078

Open
ankit2995 wants to merge 1 commit into
fortify:feat/v3.x/aviator/26.4from
ankit2995:ankit/dast-audit
Open

feat:Add DAST audit command and related functionality#1078
ankit2995 wants to merge 1 commit into
fortify:feat/v3.x/aviator/26.4from
ankit2995:ankit/dast-audit

Conversation

@ankit2995

@ankit2995 ankit2995 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Note: This PR will be merged into feat/v3.x/aviator/26.4

This PR adds DAST auditing support to FCLI, allowing teams to audit WebInspect findings with Fortify Aviator and upload the updated DAST FPR to SSC.

What changed

  • Added a new DAST audit command:
    • fcli aviator ssc audit-dast
  • Downloads the latest DAST FPR from an SSC application version.
  • Parses eligible WebInspect findings and submits them to Fortify Aviator.
  • Writes audit decisions back to audit.xml.
  • Uploads the updated FPR through the SSC artifact API.
  • Returns the newly created SSC artifact ID.
  • Added configurable DAST tag mapping.
  • Added resilient gRPC streaming with automatic reconnection.
  • Requeues only unfinished findings after a connection failure.
  • Added tests for parsing, request/response mapping, FPR updates, output, and retries.

Behavior

  • Eligible DAST findings are streamed to Fortify Aviator for auditing.
  • Completed findings are retained if the stream reconnects.
  • Skipped and failed findings are reported separately.
  • The output includes:
    • application version ID
    • application and version names
    • uploaded artifact ID
    • audit status

Command

fcli aviator ssc audit-dast  --av <ssc-application>:<version> --app <aviator-application>

A custom tag mapping can be supplied with:

fcli aviator ssc audit-dast --av <ssc-application>:<version> --app <aviator-application> --tag-mapping <file>

Example Response

{
  "id": "42",
  "applicationName": "WebGoat",
  "versionName": "1.0",
  "artifactId": "2786",
  "operation": {
    "audit": {
      "submitted": 6,
      "succeeded": 4,
      "skipped": 2,
      "failed": 0
    }
  },
  "__action__": "PARTIALLY_AUDITED"
}

- Implemented AviatorSSCDastAuditCommand for auditing DAST findings in SSC applications.
- Created DastAuditRequestMapper and DastAuditResponseMapper for handling DAST audit requests and responses.
- Added unit tests for DAST audit request and response mappers, stream processor, and command.
- Introduced AviatorSSCFprTransferHelper for managing DAST FPR downloads and uploads.
- Updated AviatorSSCAuditHelper to include DAST-specific audit statistics.
- Enhanced AviatorSSCCommands to include the new DAST audit command.
- Updated internationalization properties for DAST audit command messages.
@ankit2995 ankit2995 changed the title Added DAST audit command and related functionality feat:Add DAST audit command and related functionality Aug 21, 2026
@ankit2995
ankit2995 marked this pull request as ready for review August 21, 2026 10:18
@ankit2995
ankit2995 requested a review from rsenden August 21, 2026 10:18
int likelyFalsePositives = 0;
int failed = 0;
int serverSkipped = 0;
Set<String> respondedIssueIds = new java.util.HashSet<>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use proper import (here, and any other similar occurences)

String status = succeeded == workItems.size() ? "AUDITED"
: succeeded > 0 ? "PARTIALLY_AUDITED" : "FAILED";
String message = succeeded == 0 ? "No DAST audit responses were successfully processed" : null;
return new DastAuditFprResult(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Way too many constructor arguments; especially where arguments have same type, it's easy to accidentally put them in wrong order. Use Lombok @Builder pattern or similar.

AuditResponse response = DastAuditDecisionMapper.toAuditResponse(result);
if ("SUCCESS".equalsIgnoreCase(response.getStatus()) && response.getAuditResult() != null) {
successfulResponses.put(result.issueId(), response);
var success = (com.fortify.cli.aviator.grpc.DastAuditResult.Success) result;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use import

}

private static EligibilityResult eligibleWorkItems(
List<com.fortify.cli.aviator.dast.DastSession> sessions,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use import

continue;
}
if (auditIssue != null && isProcessedByAviator(auditIssue)) {
processed++;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and in for example EligibilityResult, it's unclear whether processed refers to 'processed in current run', or 'already processed in earlier run'. Better to nae this for example alreadyProcessed.

import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;

@Command(name = "audit-dast")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have separate audit commands for SAST and DAST? From a user perspective, wouldn't it be more logical to have a single audit command that can audit both SAST & DAST issues (either audit all issues with a single command invocation, or have a CLI option to select between SAST or DAST audit)?

Whether this is feasible largely depends on whether SAST & DAST audits share the same CLI options, or we need specific CLI options that are relevant for DAST but not SAST, or vice versa (also see other comment/question as to why audit seems to have many more options than audit-dast).

If we do keep this as separate commands, we should implement consistent command names like we've done elsewhere:

  • Rename AviatorSSCAuditCommand to AviatorSSCSastAuditCommand with corresponding audit-sast command name
  • For backward compatibility, introduce a deprecated audit command that just invokes the audit-sast command

private DastAuditFprResult auditFpr(
Path fprPath,
SSCAppVersionDescriptor appVersion,
com.fortify.cli.aviator._common.session.user.helper.AviatorUserSessionDescriptor session,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use import

public class AviatorSSCDastAuditCommand extends AbstractSSCJsonNodeOutputCommand implements IActionCommandResultSupplier {
private static final Logger LOG = LoggerFactory.getLogger(AviatorSSCDastAuditCommand.class);

@Getter @Mixin private OutputHelperMixins.DetailsNoQuery outputHelper;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SAST audit command seems to have a lot more options (quota management, filter set/folder options, ...); why don't we have the same for DAST audit? In SSC, DAST issues are also organized in folders based on issue templates/filter sets, so I'd expect the filter set/folder-related options to be available for both SAST & DAST audits.

private String actionResult = "SKIPPED";

@Override
public JsonNode getJsonNode(UnirestInstance unirest) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't checked, but I'd assume that SAST & DAST audit commands share the same overall structure; would it make sense to introduce a common abstract base class that defines the shared logic, both to ensure consistency and reduce code duplication?

}

private static String getDastAuditMessage(DastAuditFprResult auditResult) {
return switch (auditResult.status()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String comparison is fragile; if anyone every changes/adds/removes any of these strings in core Aviator code, this switch statement might produce inconsistent results. Better to use enum values for example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants