Skip to content

Atomic Remediation with Exception Handling and Compliance Reporting - #1064

Merged
rsenden merged 4 commits into
fortify:dev/v3.xfrom
ankit2995:ankit/encoding-fix
Jul 29, 2026
Merged

Atomic Remediation with Exception Handling and Compliance Reporting#1064
rsenden merged 4 commits into
fortify:dev/v3.xfrom
ankit2995:ankit/encoding-fix

Conversation

@ankit2995

Copy link
Copy Markdown
Contributor

Note: The PR should be merged in dev/v3.x

Implement a robust remediation framework that ensures remediation actions are applied atomically, with comprehensive exception handling and detailed reporting. This approach minimizes partial updates, improves reliability, and provides clear visibility into remediation outcomes, failures, and compliance status.

fix: `fcli aviator ssc apply-remediations`: Atomic Remediation with Exception Handling and Compliance Reporting
fix: `fcli fod aviator apply-remediations`: Atomic Remediation with Exception Handling and Compliance Reporting

@ankit2995
ankit2995 marked this pull request as ready for review July 28, 2026 10:02
return true;
}

private String applyChange(String instanceId, String filename, String fileHash, Charset sourceEncoding, String originalContent,

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.

Wrong indentation for this and various other method declarations

pendingWrite.updatedBytes().length);
writeSourceFile(pendingWrite.filePath(), pendingWrite.updatedBytes(), pendingWrite.filename());
}
} catch (SkipRemediationException e) {

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.

To understand the exception handling in this method, developers need to check writeSourceFile to understand that this converts IOException to SkipRemediationException. Also, this doesn't roll back on any other exceptions (which are unlikely but not impossible), whereas other methods explicitly catch Exception.

LOG.warn("Rolled back remediation {} changes for '{}' after write failure", instanceId, pendingWrite.filename());
} catch (IOException rollbackException) {
LOG.error("Failed to roll back remediation {} changes for '{}'", instanceId, pendingWrite.filename(), rollbackException);
throw new AviatorTechnicalException("Failed to roll back remediation changes for '" + pendingWrite.filename() +

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.

Given that this situation requires special processing (command termination instead of skipping single remediation), it would be better to use a dedicated exception type like RollbackRemediationException or a more generic TerminateRemediationProcessingException or something similar. This would avoid unintended command termination if any other code throws AviatorTechnicalException.

logger.error("Skipping file '{}' as it resolves to a path outside the source directory (potential path traversal attack)", filename);
continue;
try {
if (processRemediation(remediation, sourceBasePath, fvdlMetadataResult, modifiedFiles)) {

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.

See comment on applyRemediation method to have that method define flow and exception handling for applying individual remediations. Ultimate goal would be to have this method simply do the following for every individual remediation, without try/catch block:

  if (processRemediation(...)) {
    appliedRemediations++;
  }

return new RemediationMetric(totalRemediations, appliedRemediations, skippedRemediations, modifiedFiles, skippedByReason);
}

private boolean processRemediation(Element remediation, Path sourceBasePath, FvdlMetadataResult fvdlMetadataResult,

@rsenden rsenden Jul 28, 2026

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.

Ideally, to better document the overall process for applying an individual remediation, and to centralize exception handling, this method should effectively do something like the following:

private boolean processRemediation(...) {
  try {
    var pendingWrites = prepareFileChanges(...); // May throw RemediationSkipExceptions only; either explicitly or through generic try/catch to indicate prepare error. If this already reads source files, result object can include both original contents (for rollback if needed) and the changes to be applied, otherwise original file contents may be collected before updating each source file in commitChanges method.
    try {
      commitChanges(pendingWrites); // May throw RemediationCommitException only, listing any files that were already updated
      return true; // Indicate to caller that remediation was applied successfully
    } catch ( RemediationCommitException e ) {
      rollBackChanges(e.getRollbacks()); // Throws exception in case rollback was unsuccessful (ideally a dedicated exception type), causing command termination. RemediationCommitException::getRollbacks returns file paths and original contents for rollback.
    }
  } catch (RemediationSkipException e) {
    // Log warning, update skippedByReason
  }
  return false; // Indicate to caller that remediation was not applied successfully
}

Or alternatively, instead of boolean to report status, just propagate exceptions to the caller, i.e.:

private void processRemediation(...) {
  var pendingWrites = prepareFileChanges(...); // May throw RemediationSkipExceptions only; either explicitly or through generic try/catch to indicate prepare error. If this already reads source files, result object can include both original contents (for rollback if needed) and the changes to be applied, otherwise original file contents may be collected before updating each source file in commitChanges method.
  try {
    commitChanges(pendingWrites); // May throw RemediationCommitException only, listing any files that were already updated
  } catch ( RemediationCommitException e ) {
    rollBackChanges(e.getRollbacks()); // Throws exception in case rollback was unsuccessful (ideally a dedicated exception type), causing command termination. RemediationCommitException::getRollbacks returns file paths and original contents for rollback.
    throw new RemediationSkipException(...); // Indicate remediation was (successfully) rolled back due to errors
  }
}

With the latter, processRemediationsXML method would only catch RemediationSkipException to log warning & update skippedByReason, all other exceptions (like error during rollback) would result in command termination.

The above likely requires more work, main point is that it clearly encapsulates both processing (prepare, commit, rollback if needed) and exception handling (explicit skip reasons, technical errors, rollback errors, ...).

For some of these exception types, it may make sense to declare them as checked exceptions instead on RuntimeExceptions, as they are integral part of the overall workflow, and to explicitly document what exceptions may be thrown by each method.

@rsenden
rsenden merged commit 92bcbe1 into fortify:dev/v3.x Jul 29, 2026
14 of 15 checks passed
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