The Flows module is a comprehensive, end-to-end automation engine powered by .
Flows can help you to automate AttackForge with nearly unlimited systems. You can streamline processes across your organization to save time and focus on what's important.
Some examples you can do with Flows:
Create custom webhooks.
Send custom email notifications on events.
Getting Access to Flows
Flows is included in all AttackForge Enterprise plans, and in the AttackForge Core SME plan. For all others plans, Flows can be add-on from the Administration -> Subscriptions page.
To get started with building a Flow:
You must be granted access to another person's existing Flow
Flow Overview
A Flow is comprised of the following:
Name - the name of the Flow.
Run Overview
A Run refers to a single execution of a Flow, meaning when a set of actions defined in your Flow is triggered and carried out from start to finish, that is considered one "Run" of the flow; essentially, it's a single instance of your Flow being executed.
Key points about a Run:
Trackable status - You can monitor the status of a Run, including whether it succeeded, failed, or is currently running.
Sharing Flows with Teams
When a Flow is created, it belongs to the user who created the Flow (the Flow Owner). Flow Owners cannot be changed (for the time being).
Only Flow Owners are allowed to share their Flows with other users.
To share your Flow:
Open your Flow and click on the Settings button
Click on Add Access
Insert the user's email address or look up and select the user
Once a user has been given access to a Flow, they will be able to do the following:
Manually run the Flow
Enable/Disable the Flow
You can remove a users' access to your Flows from the Settings.
Triggers
The following Triggers are currently supported:
Project Created
Project Updated
Project Request Created
Project Request Updated
Project Retest Requested
Project Retest Completed
Project Retest Cancelled
Vulnerability Created
Vulnerability Updated
Vulnerability Remediation Note Created
Vulnerability Remediation Note Updated
Vulnerability Evidence Created
Vulnerability Evidence Updated
A Flow can be assigned to only one Trigger.
Triggers can be assigned to a Flow when either creating or editing the Flow.
COMING SOON! Ability to create your own custom Triggers which are invoked from custom URLs. This will allow you to execute a Flow on-demand whenever you would need it to run, and also allow for creating custom webhook receivers for your security toolsets and systems to communicate back to AttackForge.
Secrets
Secrets are any piece of sensitive information that needs to be kept confidential, such as passwords and API keys.
You can create Secrets which belong to the Flow. Only users with access to the Flow would be able to view the associated Secrets.
To create a Secret, start by clicking on the Secrets button when creating or editing a Flow.
From here, you can see and manage all of the existing Secrets associated to the Flow.
Click on Add Secret to create a new Secret and enter a Key and a Value. Note the Key must be letters, numbers and underscores only.
NOTE: Secrets are stored encrypted in the database.
COMING SOON: You will be able to create User Secrets which belong to the user and are managed in one place. This makes is easy to rotate passwords and credentials without having to update the Flows.
There are two (2) ways in which you can refer to your Secrets in your Flow:
Secrets in Headers
Secrets in Request/Response Scripts
secrets.<KEY>
Where <KEY> is replaced with the Key associated with the Secret.
IMPORTANT: Make sure to select Use Secrets to ensure your secrets are used in your script.
Actions
For example, if the use case for your Flow is:
to create a JIRA Issue every time a Vulnerability is created
You may choose to include two (2) Actions in your Flow:
Action 1 - Create JIRA Issue
Action 2 - Update Vulnerability with JIRA Issue Key
Every Action is made up of the following components:
Verify Certificate - this determines whether to verify if the TLS certificate is valid for the URL.
IMPORTANT: When more than one Action is included in a Flow, the output of an Action will become the input into the next Action.
The Request is a HTTP/HTTPS request to a web address.
The Request is made up of the following components:
Body - an optional HTTP body. This is typically required for POST, PUT and PATCH HTTP requests.
IMPORTANT: Requests can be made over both HTTP and HTTPS.
Response
The Response is made of the of the following components:
Request Script
The Request Script is made up of the following components:
Response Script
The Request Script is made up of the following components:
Code
This makes it possible to write logic to help you handle all various use cases for how you want your Flows to work.
You can test and debug your code using the Run option:
If your code fails after running it, you will see an error message with the relevant stack trace:
Data
You can reference the information included within Data as follows:
data.<KEY>
The Data Object
In the following example, we can see that Data Object has vulnerability-related information due to the "vulnerability-created" Event.
You can refer to keys on the Data Object using the following syntax:
data.<KEY>
Using the example above, if you wanted to store the vulnerability Id in a constant, you could do the following:
const vuln_id = data.vulnerability_id;
Keeping with the example above, if you wanted to extract the project Id for the vulnerability, you could do the following:
let project_id = undefined;
if (data.vulnerability_projects) {
for (let x = 0; x < data.vulnerability_projects.length; x++) {
if (data.vulnerability_projects[x].id) {
project_id = data.vulnerability_projects[x].id;
}
}
}
Event - the related Event which triggered the Flow.
Manually Run Flow
Re-Run
Importing/Exporting Flows
You can export your existing Flows and import Flows at any time. This utility can help to:
Share Flows with others, without giving them access to your Flows
Create a backup of your Flows
Bootstrap a new Flow based on a template
To export a Flow, click on the Export Flow button. The Flow will be exported with a .flow extension format.
To import a Flow, click on Import Flow:
Select the Flow you would like to import (.flow file):
You will have an opportunity to review and adjust the Flow prior to saving.
Examples
The following examples can help you to automate and integrate with common security and enterprise tools. Each example includes a Flow which can be imported in to your own AttackForge to help you get started fast!
Create JIRA Issue
Initial Set Up
Important: This example requires access to the AttackForge Self-Service API and AttackForge Flows
Event: Vulnerability Created
Secrets:
Action 1 - Create JIRA Issue
Method: POST
URL: https://<YOUR-JIRA>/rest/api/2/issue
Headers:
Key = Accept; Type = Value; Value = application/json
Key = Content-Type; Type = Value; Value = application/json
Key = Authorization; Type = Secret; Value = jira_auth
Request Script:
let jiraProjectKey = '';
let afProjectId = '';
if (data.vulnerability_projects) {
for (let i = 0; i < data.vulnerability_projects.length; i++) {
const project = data.vulnerability_projects[i];
if (project.custom_fields) {
for (let j = 0; j < project.custom_fields.length; j++) {
const projectCustomField = project.custom_fields[j];
if (projectCustomField.key === 'jira_project_key') {
jiraProjectKey = projectCustomField.value;
break;
}
}
}
if (project.id) {
afProjectId = project.id;
}
if (afProjectId && jiraProjectKey) {
break;
}
}
}
jiraProjectKey = String.replace(jiraProjectKey, m/"/g, '\"');
jiraProjectKey = String.replace(jiraProjectKey, m/\\"/g, '\"');
return {
data: {
af_project_id: afProjectId,
af_vuln_id: data?.vulnerability_id
},
request: {
body: buildRequestBody(jiraProjectKey)
}
};
function buildRequestBody(jiraProjectKey) {
let summary = '';
if (data.vulnerability_title) {
summary = '[SECURITY][VULNERABILITY] ' + data.vulnerability_title;
}
summary = String.replace(summary, m/"/g, '\"');
summary = String.replace(summary, m/\\"/g, '\"');
let description = '*_{color:red}WARNING: Contents of this ticket may be overwritten by automated tooling!{color}_* \n ';
if (data.vulnerability_title) {
description += 'h1. Vulnerability: ' + data.vulnerability_title + ' \n\n ';
}
if (data.vulnerability_description) {
description += '*Description* \n ' + data.vulnerability_description + ' \n\n ';
}
let cvssScore;
let cvssVector;
let cvssVectorEscaped;
if (data.vulnerability_priority && data.vulnerability_tags) {
for (let i = 0; i < data.vulnerability_tags.length; i++) {
if (data.vulnerability_tags[i] =~ m/CVSSv3.1 Base Score: /) {
cvssScore = String.replace(data.vulnerability_tags[i], 'CVSSv3.1 Base Score: ', '');
}
if (data.vulnerability_tags[i] =~ m/CVSSv3.1 Temporal Score: /) {
cvssScore = String.replace(data.vulnerability_tags[i], 'CVSSv3.1 Temporal Score: ', '');
}
if (data.vulnerability_tags[i] =~ m/CVSSv3.1 Environmental Score: /) {
cvssScore = String.replace(data.vulnerability_tags[i], 'CVSSv3.1 Environmental Score: ', '');
}
if (data.vulnerability_tags[i] =~ m/CVSS:3.1\//) {
cvssVector = String.replace(data.vulnerability_tags[i], 'CVSS:3.1/', '');
cvssVectorEscaped = String.replace(cvssVector, m/:/g, ':{anchor}');
}
}
if (cvssScore && cvssVector && cvssVectorEscaped) {
description += '*Technical Severity* \n ||*Rating*||*CVSSv3 Score*||\n|' + data.vulnerability_priority + '|' + cvssScore + '|\n\nCVSS 3.1 Vector String: [' + cvssVectorEscaped + '|https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?version=3.1&vector=' + cvssVector + '] \n\n ';
}
else {
description += '*Technical Severity* \n ||*Rating* \n |' + data.vulnerability_priority + ' \n\n ';
}
}
if (data.vulnerability_attack_scenario && data.vulnerability_likelihood_of_exploitation) {
description += '*Attack Scenario (Technical Risk)* \n\n Likelihood of Exploitation: ' + data.vulnerability_likelihood_of_exploitation + '/10 \n\n ' + data.vulnerability_attack_scenario + ' \n\n ';
}
if (data.vulnerability_affected_asset_name) {
description += '*Affected Asset* \n\n * ' + data.vulnerability_affected_asset_name + ' \n\n ';
}
else if (data.vulnerability_affected_assets) {
description += '*Affected Asset(s)*: \n';
for (let i = 0; i < data.vulnerability_affected_assets.length; i++) {
if (data.vulnerability_affected_assets[i].asset?.name) {
description += '\n * ' + data.vulnerability_affected_assets[i].asset.name;
}
}
description += '\n\n';
}
if (data.vulnerability_steps_to_reproduce) {
description += '*Steps to Reproduce* \n\n ' + data.vulnerability_steps_to_reproduce + ' \n\n ';
}
let notes;
if (data.vulnerability_notes) {
for (let i = 0; i < data.vulnerability_notes.length; i++) {
if (notes === undefined) {
notes = data.vulnerability_notes[i].note;
}
else {
notes += '\n\n' + data.vulnerability_notes[i].note;
}
}
}
if (notes) {
description += '*Notes* \n\n ' + notes + ' \n\n ';
}
if (data.vulnerability_remediation_recommendation) {
description += '*Recommendations* \n\n ' + data.vulnerability_remediation_recommendation + ' \n\n ';
}
const labels = [];
const tags = [];
if (data.vulnerability_tags) {
for (let i = 0; i < data.vulnerability_tags.length; i++) {
let newtag = '* ' + data.vulnerability_tags[i] + '\n';
newtag = String.replace(newtag, m/:/g, '{color:black}:{color}');
Array.push(tags, newtag);
let label = data.vulnerability_tags[i];
label = String.replace(label, m/\s/g, '');
Array.push(labels, label);
}
}
Logger.debug(JSON.stringify(tags));
if (tags.length > 0) {
description = description + '*Tags* \n\n ';
for (let i = 0; i < tags.length; i++) {
description = description + tags[i];
}
}
description = String.replace(description, m/"/g, '\"');
description = String.replace(description, m/\\"/g, '\"');
let priority = 'Lowest';
if (data.vulnerability_priority === 'Critical') {
priority = 'Highest';
}
else if (data.vulnerability_priority === 'High') {
priority = 'High';
}
else if (data.vulnerability_priority === 'Medium') {
priority = 'Medium';
}
else if (data.vulnerability_priority === 'Low') {
priority = 'Low';
}
else if (data.vulnerability_priority === 'Info') {
priority = 'Lowest';
}
return {
fields: {
project: {
key: jiraProjectKey
},
summary: summary,
description: description,
priority: {
name: priority
},
issuetype: {
name: 'Bug'
},
labels: labels
}
};
}
Response Script:
let body;
if (response.headers['Content-Type'] === 'application/json;charset=UTF-8') {
body = JSON.parse(response.body);
}
else {
return {
decision: {
status: 'abort',
message: 'Content-Type is expected to be application/json;charset=UTF-8'
}
};
}
if (!body?.key) {
Logger.error(JSON.stringify(response));
return {
decision: {
status: 'abort',
message: 'missing JIRA Issue Key (body.key)',
},
};
}
else if (!data?.af_project_id) {
if (data) {
Logger.error(JSON.stringify(data));
}
return {
decision: {
status: 'abort',
message: 'missing data.af_project_id',
},
};
}
else if (!data?.af_vuln_id) {
if (data) {
Logger.error(JSON.stringify(data));
}
return {
decision: {
status: 'abort',
message: 'missing data.af_vuln_id',
},
};
}
else {
return {
data: {
af_project_id: data?.af_project_id,
af_vuln_id: data.af_vuln_id,
jira_issue_key: body.key
}
};
}
Action 2 - Update AF Vuln with JIRA Issue Key
Method: PUT
URL: <defined in Request Script>
Headers:
Key = Content-Type; Type = Value; Value = application/json
Integrate your vulnerability data with ticketing tools like , , , and others.
Visualize your pentesting data in powerful tools like and
Help make better risk decisions by sending your vulnerability data to GRC platforms like , , and .
Create workflow automations by chaining together
Trigger automated scanning activities in your security toolset like , and .
Create messages on collaboration platforms like and .
Prioritize vulnerabilities with threat-intelligence like
You must have ; or
- the trigger which initiates a .
- a sequence of steps which are executed in order during a .
- any piece of sensitive information that needs to be kept confidential, such as passwords and API keys.
Triggered by an Event - A Run is initiated by a , like a new vulnerability or an update to a project, or a manual action.
Provides details - Each Run has details like start time, duration, and the specific taken within the Flow.
IMPORTANT: A normal Run will only be executed in the context of the and related . For example, if the was "vulnerability-created" - the Run will only initiate for the vulnerability for which the has access to the vulnerability. Test runs can be manually executed with test data at any time.
Edit the Flow, including changing Name, , and
View and individual Runs, including Re-run
the Flow
A Trigger is an action which initiates a . Triggers can be initiated from or manually initiated.
You can also view, manage and create secrets in the and in the :
Select the Secret directly in the
Refer to the Secret in the or
When creating or modifying within the , you can select 'Secret' for the header type. This will then allow you to select from an existing Secret, or create a new Secret.
When creating or modifying the or the , you can refer to secrets using the following syntax:
Actions are either one activity, or a sequence of activities, which are executed in order during a .
This involves formatting the vulnerability into the necessary format, and making a HTTPS request to the JIRA API to create the issue.
This involves making a HTTPS request to the to set the JIRA Issue Key custom field.
The primary purpose of an Action is to make an update to a system. The system could be AttackForge (via the ) or an external system.
Every Action is made up of a and a .
The is the HTTP request which is made by the Action.
The is the HTTP response from the server which received the HTTP request.
- this is the HTTP method i.e. GET, POST, PUT, etc. that will be used for the
- this is the URL that will be used for the .
- these are the headers that will be sent when the is made.
- this is the script which will execute before the is made.
- this is the script which will execute after the is returned.
Methods are the HTTP methods/verbs that will be used for the i.e. GET, POST, PUT, etc.
Methods can be selected when editing the :
Methods can also be programatically set in your in the :
The URL is the web address that will be used for the , for example https://acmecorp.atlassian.net/rest/api/2/issue
The URL can be entered in when editing the :
The URL can also be programatically set in your in the . This is useful if your URL has a dynamic component which needs to be computed:
The Headers are the HTTP headers that will be sent when the is made.
The Headers can be manually entered in when editing the :
The Headers can also be programatically set in your in the . This is useful if your Headers have a dynamic component which needs to be computed:
The Response is the HTTP server response to a .
The Request Script is the script which will execute before the is made.
The Response Script is the script which will execute after the is returned.
Flows support - a powerful interpreted programming language created by AttackForge.
You can take advantage of in to help you to debug and test your code.
Data is contextually relevent information for your and .
Where <KEY> is replaced with the associated key on the .
For more information on Data, please see .
is contextually relevant information for your and .
The Data Object is an that holds the .
The first in your Flow will contain in the which is relevent to your . For example, if your Flow was assigned to the "vulnerability-created" Event, then your Data Object will contain all of the information relating to the vulnerability.
However from this point forward, you can control how you would like your Data Object to look for the and any subsequent going forward.
If you needed to pass this information to the next step of this Flow, which using the example above will be the on the first - you can include the "data" key in your and pass in an with key/value pairs as follows:
Continuing with this example, the will now have the following Data Object:
When viewing the details of a - you can see what was passed as input and output into an
If you would need to log the Data Object for visibility or debugging during execution of a you can do the following:
You can then view the details in the
The Response Object is the HTTP information which is sent back from the server during the .
The Response Object is available in the .
The statusCode will be accessible as a .
The headers will be accessible as a .
The body will be accessible as a .
If you are expecting the body to be returned as a payload (which is common for ) - you must first parse the body into JSON format before you can access it using , see example below:
When viewing the details of a - you can see the and Response Headers and Response Body:
If you would need to log the Response Object for visibility or debugging during execution of a you can do the following:
You can then view the details in the
The Return Statement is the action to take for your and .
Continue will instruct your script to continue with normal execution. For example, continuing in the will result in the being made. Continuing in the will result in executing the next .
Next will instruct your or to move to the next . This is useful if a in your Flow is conditional i.e. it may or may not need to be made.
The Request is the HTTP Request information that your will use. Request is made up of the following components:
The URL is the URL that the will be sent to. This field is optional. If it is not specified, the set in the will prevail. If it is specified, it will take precedence over the set in the .
The Body is the payload body that will be sent in the . This field is optional. If it is not specified, no body will be sent in the .
The Headers are the HTTPS Headers that the will use. This field is optional. If it is not specified, the set in the will prevail. If it is specified, it will take precedence over the set in the .
The Method is the HTTPS Method that the will be sent to. This field is optional. If it is not specified, the set in the will prevail. If it is specified, it will take precedence over the set in the .
Data is an object that can be used to pass information between to , and from to the next .
The Data in the for the first of the Flow will be the Event information, for example "vulnerability-created" fields. From then onwards, you can override what the data will be in the and beyond.
Example with Data in the of the first in the Flow:
Example with Data in the of the first in the Flow. This example will pass on the "af_project_id" and "af_vuln" that was passed in the Data from the on to the next in the Flow.
Flow Runs is where you can view the history of each you have access to, across all of your Flows. It is a consolidated view for every . You can access this page from the Flows module.
You can also view for a specific Flow by clicking on Flows and then clicking on the name of a Flow.
When clicking on a , you will see an overview of the history for that , including the following information:
Run Status - Whether the was Completed or Failed.
Started - the timestamp of when the started execution.
Duration - the duration (in milliseconds) for execution of the until completion or failure.
Actions - the in-scope during the .
Data - the input and output of each .
HTTP - the , , , , Response Status Code and for each in the Flow.
Logs - the logs for each .
If you include in your or , you will be able to see the logs here during execution of a Flow.
You can manually run a Flow at any time. This is useful for testing your Flow. When you manually run a Flow, the input into the first will be test data. You can modify the test data to match your testing needs.
You can manually re-run a Flow at any time. When you Re-Run a Flow, it will execute with exactly the same input data into the first .
After a Flow has Re-Run, you will notice the will show that it was Re-Run.
IMPORTANT: Exported Flows do not contain the values of . However for compatibility and convenience, the key/name will be included in the export.
NOTE: These are just some common examples so far. We are constantly adding more Flow examples as we continue to roll out Flows. Remember - Flows can interact with any HTTP interface, including AttackForge and any other external systems! Unleash your imagination and creativity
The purpose of this example is to create a when a Vulnerability is created in AttackForge, and to update AttackForge to assign the JIRA Issue Key against the Vulnerability.
This example Flow can be downloaded from our and into your AttackForge.
af_auth - your .
jira_auth- your
The purpose of this example is to update a when a Vulnerability is updated in AttackForge.
This example Flow can be downloaded from our and into your AttackForge.
jira_auth- your
The purpose of this example is to create a when a Vulnerability is created in AttackForge, and to update AttackForge to assign the SNOW Incident Id against the Vulnerability.
This example Flow can be downloaded from our and into your AttackForge.
af_auth - your .
snow_auth- your
The purpose of this example is to create a when a Vulnerability is created in AttackForge, and to update AttackForge to assign the Azure DevOps Work Item Id against the Vulnerability.
This example Flow can be downloaded from our and into your AttackForge.
af_auth - your .
ado_auth- your
The purpose of this example is to prioritize a vulnerability based on threat intelligence information harnessed from and to apply a custom score/rating to the vulnerability.
This example Flow can be downloaded from our and into your AttackForge.
af_auth - your .
The purpose of this example is to schedule a when a Project is created in AttackForge.
This example Flow can be downloaded from our and into your AttackForge.
tenable_auth- your
The purpose of this example is to create a message in a when a Vulnerability is created in AttackForge.
This example Flow can be downloaded from our and into your AttackForge.
slack_auth- your
URL:
The purpose of this example is to create a message in a when a Vulnerability is created in AttackForge.
This example Flow can be downloaded from our and into your AttackForge.
teams_auth- your
The purpose of this example is to send data to when a Vulnerability is created in AttackForge.
This example Flow can be downloaded from our and into your AttackForge.
powerbi_key- your
The purpose of this example is to create a when a Project is requested in AttackForge.
This example Flow can be downloaded from our and into your AttackForge.
sf_client_id- your
sf_client_secret - your
sf_password - Your
sf_username - Your
The purpose of this example is to post data to a when a Vulnerability is created in AttackForge.
This example Flow can be downloaded from our and into your AttackForge.
This example Flow can be downloaded from our and into your AttackForge.