Extension Development
This guide walks you through creating a custom Faction extension, from setting up your development environment to testing and deploying your extension.
Prerequisites
Before you begin, ensure you have:
Java JDK 8 or higher - Faction extensions use Java 8 compatibility
Maven 3.x - For dependency management and building
Faction instance - A running Faction server for testing (development or production)
IDE - IntelliJ IDEA, Eclipse, or your preferred Java IDE
Project Setup
1. Create a Maven Project
Create a new Maven project for your extension:
mvn archetype:generate \
-DgroupId=com.yourcompany.faction \
-DartifactId=faction-custom-extension \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
cd faction-custom-extension
Add the FactionExtender library dependency to your pom.xml:
< project xmlns = "http://maven.apache.org/POM/4.0.0"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd" >
< modelVersion > 4.0.0 </ modelVersion >
< groupId > com.yourcompany.faction </ groupId >
< artifactId > faction-custom-extension </ artifactId >
< version > 1.0.0 </ version >
< packaging > jar </ packaging >
< properties >
< maven.compiler.source > 8 </ maven.compiler.source >
< maven.compiler.target > 8 </ maven.compiler.target >
</ properties >
< dependencies >
<!-- FactionExtender API -->
< dependency >
< groupId > com.factionsecurity </ groupId >
< artifactId > faction-extender </ artifactId >
< version > 2.7 </ version >
< scope > provided </ scope >
</ dependency >
</ dependencies >
< build >
< plugins >
< plugin >
< groupId > org.apache.maven.plugins </ groupId >
< artifactId > maven-compiler-plugin </ artifactId >
< version > 3.10.1 </ version >
< configuration >
< source > 8 </ source >
< target > 8 </ target >
</ configuration >
</ plugin >
</ plugins >
</ build >
</ project >
The FactionExtender dependency uses <scope>provided</scope> because Faction’s runtime already includes this library.
FactionExtender Library
The FactionExtender library provides interfaces for hooking into Faction events. Your extension implements one or more of these interfaces:
Available Interfaces
Interface Event Type Purpose AssessmentManagerASMT_MANAGERReact to assessment lifecycle events VulnerabilityManagerVULN_MANAGERProcess vulnerability changes VerificationManagerVER_MANAGERHandle retest and verification events ApplicationInventoryINVENTORYIntegrate with asset/inventory systems ReportManagerREPORT_MANAGERCustomize report generation
Each interface defines methods that Faction calls when specific events occur.
Extension Events and Listeners
Extensions receive events through well-defined interfaces. Let’s explore each type:
AssessmentManager Interface
Triggered when assessments are created, updated, or completed.
Key Method :
AssessmentManagerResult assessmentChange (
com . faction . elements . Assessment assessment,
List < com . faction . elements . Vulnerability > vulnerabilities,
Operation operation
)
Operation Types :
CREATE - New assessment created
UPDATE - Assessment details modified
COMPLETE - Assessment marked as complete
DELETE - Assessment deleted
Example Implementation :
package com.yourcompany.faction.extension;
import com.faction.extender.AssessmentManager;
import com.faction.elements.Assessment;
import com.faction.elements.Vulnerability;
import com.faction.elements.results.AssessmentManagerResult;
import com.faction.elements.utils.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class JiraIntegrationExtension implements AssessmentManager {
private Map < String , String > config ;
private List < Log > logs = new ArrayList <>();
@ Override
public void setConfigs ( Map < String , String > configs ) {
this . config = configs;
}
@ Override
public AssessmentManagerResult assessmentChange (
Assessment assessment ,
List < Vulnerability > vulnerabilities ,
Operation operation ) {
logs . add ( new Log ( "Processing assessment: " + assessment . getName ()));
// Only create Jira tickets when assessment is complete
if (operation == Operation . COMPLETE ) {
for ( Vulnerability vuln : vulnerabilities) {
createJiraTicket (assessment, vuln);
}
}
// Return null if you don't need to modify data
return null ;
}
private void createJiraTicket ( Assessment assessment , Vulnerability vuln ) {
String jiraUrl = config . get ( "jiraUrl" );
String apiToken = config . get ( "apiToken" );
String projectKey = config . get ( "projectKey" );
// Your Jira API integration code here
logs . add ( new Log ( "Created Jira ticket for: " + vuln . getTitle ()));
}
@ Override
public List < Log > getLogs () {
return logs;
}
}
VulnerabilityManager Interface
Triggered when individual vulnerabilities are created, updated, or deleted.
Key Method :
com . faction . elements . Vulnerability vulnChange (
com . faction . elements . Assessment assessment,
com . faction . elements . Vulnerability vulnerability,
Operation operation
)
Example Implementation :
public class VulnerabilityEnrichmentExtension implements VulnerabilityManager {
private Map < String , String > config ;
private List < Log > logs = new ArrayList <>();
@ Override
public void setConfigs ( Map < String , String > configs ) {
this . config = configs;
}
@ Override
public Vulnerability vulnChange (
Assessment assessment ,
Vulnerability vulnerability ,
Operation operation ) {
if (operation == Operation . CREATE || operation == Operation . UPDATE ) {
// Enrich vulnerability with external data
enrichWithThreatIntel (vulnerability);
// Return modified vulnerability
return vulnerability;
}
return null ;
}
private void enrichWithThreatIntel ( Vulnerability vuln ) {
// Add CVSS scores, CVE references, etc.
logs . add ( new Log ( "Enriched vulnerability: " + vuln . getTitle ()));
}
@ Override
public List < Log > getLogs () {
return logs;
}
}
VerificationManager Interface
Triggered during retest and verification workflows.
Key Method :
com . faction . elements . Vulnerability verificationChange (
com . faction . elements . User assessor,
com . faction . elements . Vulnerability vulnerability,
com . faction . elements . Verification verification,
Operation operation
)
Example Implementation :
public class RetestNotificationExtension implements VerificationManager {
private Map < String , String > config ;
private List < Log > logs = new ArrayList <>();
@ Override
public void setConfigs ( Map < String , String > configs ) {
this . config = configs;
}
@ Override
public Vulnerability verificationChange (
User assessor ,
Vulnerability vulnerability ,
Verification verification ,
Operation operation ) {
if (operation == Operation . COMPLETE ) {
sendSlackNotification (
assessor,
vulnerability,
verification . getStatus ()
);
}
return null ;
}
private void sendSlackNotification (
User assessor ,
Vulnerability vuln ,
String status ) {
// Send Slack webhook notification
logs . add ( new Log ( "Sent Slack notification for retest: " + status));
}
@ Override
public List < Log > getLogs () {
return logs;
}
}
ReportManager Interface
Triggered during report generation, allowing you to modify report content.
Key Method :
String reportCreate (
com . faction . elements . Assessment assessment,
List < com . faction . elements . Vulnerability > vulnerabilities,
String reportText
)
Example Implementation :
public class ChartGeneratorExtension implements ReportManager {
private Map < String , String > config ;
private List < Log > logs = new ArrayList <>();
@ Override
public void setConfigs ( Map < String , String > configs ) {
this . config = configs;
}
@ Override
public String reportCreate (
Assessment assessment ,
List < Vulnerability > vulnerabilities ,
String reportText ) {
// Generate severity distribution chart
String chartHtml = generateSeverityChart (vulnerabilities);
// Insert chart into report
String updatedReport = reportText . replace (
"{{SEVERITY_CHART}}" ,
chartHtml
);
logs . add ( new Log ( "Generated severity chart" ));
return updatedReport;
}
private String generateSeverityChart ( List < Vulnerability > vulns ) {
// Generate HTML/base64 chart image
return "<img src='data:image/png;base64,...' />" ;
}
@ Override
public List < Log > getLogs () {
return logs;
}
}
ApplicationInventory Interface
Triggered when searching for applications in the inventory.
Key Method :
com . faction . elements . results . InventoryResult [] search (
String applicationId,
String applicationName
)
Example Implementation :
public class CmdbIntegrationExtension implements ApplicationInventory {
private Map < String , String > config ;
private List < Log > logs = new ArrayList <>();
@ Override
public void setConfigs ( Map < String , String > configs ) {
this . config = configs;
}
@ Override
public InventoryResult [] search ( String appId , String appName ) {
List < InventoryResult > results = new ArrayList <>();
// Query external CMDB
String cmdbUrl = config . get ( "cmdbUrl" );
String apiKey = config . get ( "apiKey" );
// Populate results from CMDB response
logs . add ( new Log ( "Searched CMDB for: " + appName));
return results . toArray ( new InventoryResult [ 0 ]);
}
@ Override
public List < Log > getLogs () {
return logs;
}
}
Registering Your Extension
Faction uses Java’s ServiceLoader mechanism to discover extensions. You must create a service provider configuration file.
Create Service Provider Configuration
Create the directory structure:
mkdir -p src/main/resources/META-INF/services
Create a file named after the interface you’re implementing:
For AssessmentManager :
# File: src/main/resources/META-INF/services/com.faction.extender.AssessmentManager
com.yourcompany.faction.extension.JiraIntegrationExtension
For VulnerabilityManager :
# File: src/main/resources/META-INF/services/com.faction.extender.VulnerabilityManager
com.yourcompany.faction.extension.VulnerabilityEnrichmentExtension
For ReportManager :
# File: src/main/resources/META-INF/services/com.faction.extender.ReportManager
com.yourcompany.faction.extension.ChartGeneratorExtension
The file name must match the fully qualified interface name, and the content should be the fully qualified name of your implementation class.
Building Your Extension
Compile and Package
Build your extension JAR:
This generates a JAR file in the target/ directory:
target/faction-custom-extension-1.0.0.jar
Verify Package Structure
Ensure your JAR contains:
Compiled class files
META-INF/services/ configuration files
Any additional resources
Verify with:
jar tf target/faction-custom-extension-1.0.0.jar | grep -E "(services|class)"
Testing Extensions Locally
1. Upload to Faction
Log into your Faction instance
Navigate to the App Store
Click Upload Extension
Select your JAR file
Configure the extension with test parameters
2. Enable for Testing
Enable the extension for the appropriate event type
Set it to execute last (higher order number) to avoid disrupting existing extensions
3. Trigger Test Events
Depending on your extension type:
AssessmentManager : Create or complete a test assessment
VulnerabilityManager : Add or modify a vulnerability
VerificationManager : Run a retest
ReportManager : Generate a report
ApplicationInventory : Search the application inventory
4. Review Logs
Navigate to Settings > System Logs
Filter by your extension name
Check for errors or expected log messages
Verify your extension executed correctly
Debugging Tips
Use extensive logging (getLogs()) to trace execution
Test with simple data first, then add complexity
Check for null values in assessment/vulnerability data
Validate configuration parameters before use
Handle exceptions gracefully and log errors
Extension Best Practices
Configuration Management
private Map < String , String > config ;
@ Override
public void setConfigs ( Map < String, String > configs) {
this . config = configs;
// Validate required configuration
if ( ! config . containsKey ( "apiUrl" )) {
logs . add ( new Log ( "ERROR: apiUrl configuration is required" ));
}
}
Error Handling
@ Override
public AssessmentManagerResult assessmentChange (
Assessment assessment,
List < Vulnerability > vulnerabilities,
Operation operation) {
try {
// Your extension logic
processAssessment (assessment);
} catch ( Exception e ) {
logs . add ( new Log ( "ERROR: " + e . getMessage ()));
e . printStackTrace ();
}
return null ;
}
Logging
import com.faction.elements.utils.Log;
private List < Log > logs = new ArrayList <>();
// Add informational logs
logs . add ( new Log ( "Processing vulnerability: " + vuln . getId ()));
// Add error logs
logs . add ( new Log ( "ERROR: Failed to connect to external API" ));
@ Override
public List < Log > getLogs () {
return logs;
}
Return Values
Return null if you don’t need to modify data
Return modified objects to persist changes
Faction automatically persists returned data to the database
Security Considerations
Never hardcode credentials or API keys
Always use the configuration system for sensitive data
Validate all external input
Use HTTPS for external API calls
Sanitize data before persisting
Next Steps
API Reference Explore detailed API documentation
App Store Learn how to submit your extension