AFScript

What is AFScript?

AFScript is an interpreted programming language created by AttackForge.

It was built to help our customers to configure and personalize their AttackForge application to better match their requirements and needs.

Applications of AFScript could include:

  • Changing the logic in parts of the application to align with existing or intended workflows

  • Drive behaviour of forms and their respective fields

  • Create in-app automations

  • Apply pre-and-post data transformations

  • Build bespoke dashboards and analytics

AFScript is the next generation in empowerment for AttackForge customers. It comes off the back of the successes we’ve had with Hide Expressions (custom fields and sections, vulnerability SLAs, custom vulnerability parsing) and Filter Expressions (custom emails, APIs) which have provided customers with ways to make AttackForge their own.

We built AFScript to provide a safe and secure path for our customers to apply their own code to their AttackForge application, in a performant manner and without creating any security holes.

The language was built to look and feel like JavaScript to make it familiar and easy to use for security teams, pentesters and software engineers.

Importantly, the language itself is not executable. This makes it safe to use in a secure way. AttackForge will interpret AFScript and derive actions to take, without executing arbitrary code. This important distinction is why we had to build our own programming language instead of going with many of the existing languages already available.

How does AFScript work?

For the most part, write AFScript the same way you would write JavaScript code.

If you are not familiar with JavaScript, we recommend checking the JavaScript basics guide by Mozilla.

We built AFScript to resemble the primary syntax of JavaScript. You can define and use variables, create loops, create functions, call functions inside functions, etc.

To make the language easier to use, we have included built-in Functions which resemble common JavaScript built-in objects like Math, String and others.

We’ve also added our own functions and syntax which was inspired by various other programming languages, such as Date.datetime.

What can’t AFScript do?

For mostly security reasons, there are some limitations in AFScript that could otherwise be found in JavaScript. This is by design, to prevent accidental or intentional security holes.

The following is a non-exhaustive list of limitations. We will continue to update this list over time as the language evolves.

Logging

Logging is imperative when writing any script or code.

AFScript supports logging to help you debug your scripts and logic.

You can invoke a log message using the following syntax:

Logger.<type>(“message”);

You can also include multiple messages in the same log entry using a comma to separate each message:

Logger.<type>(“message1”,“message2”,“message3”);

Where <type> is one of the following log level types:

  1. fatal

  2. error

  3. warn

  4. info

  5. debug

  6. trace

Fatal

Logger.fatal(“a fatal error occurred in xyz”);

Error

Logger.error(“an error occurred in xyz”);

Warn

Logger.warn(“a warning for xyz”);

Info

Logger.info(“an informational message for xyz”);

Debug

Logger.debug(“a debug statement”);

Trace

Logger.trace(“function abc was called”);

Built-in Functions

Dates

Strings

Arrays

JSON

Math

Numbers

Context

When interacting with AFScript, each supported use will have a context which is automatically injected and made available for you to use.

The context is made up of data which is contextually relevant for the purpose for which you are applying AFScript.

For example, when updating the logic for project status calculations – the context will include data about the project, which can be used in your logic to derive the intended behaviour and result.

When building scripts and debugging, you can update the context to ensure the robustness of your logic and code.

Code Editing

AttackForge has a built-in lightweight code editor to help you to use AFScript.

The editor supports autocomplete which also maps to the context.

You can also easily access Built-in Functions.

Output

You can test the output of your script and logic. This helps to provide assurance that your script is working as expected prior to rolling it into the application.

The output can be viewed at the bottom of the page.

Clicking Run will run your code and show the output.

Clicking Preserve log will preserve logging from multiple runs in the output.

TIP: you can use Logging to help you debug your code.

Supported Use Cases

Project Status Calculations

You can use AFScript to change the logic for how project status is calculated.

To get started, click on Administration -> Projects -> Status -> Configure

The default calculations for project status are included below.

IMPORTANT: Project statuses relying on Date.datetime() will be automatically updated every 5 minutes - it is not a live calculation. This is to ensure performance, especially when re-calculating all projects and comparing to 'now'.

if (Number.isInteger(project.total_not_tested_testcases)
   && Number.isInteger(project.total_in_progress_testcases)
   && Number.isInteger(project.total_tested_testcases)
   && Number.isInteger(project.total_testcases)
   && Number.isInteger(project.total_retest_vulnerabilities)
   && Number.isInteger(project.total_not_applicable_testcases)
   && project.end_date !== undefined
){
   const waitingCounter = project.total_not_tested_testcases;
   const initiatedCounter = project.total_in_progress_testcases;
   const completedCounter = project.total_tested_testcases + project.total_not_applicable_testcases;
   const past24hours = Date.datetime('now', '-1 days', 'epoch');
   const endDateTime = Date.datetime(project.end_date, 'epoch');
   const overrun = endDateTime < past24hours;
 
   let status;
 
   if (project.on_hold) {
      status = 'On Hold';
   }
   else if ((completedCounter < project.total_testcases && completedCounter > 0 && overrun) || (project.total_testcases > 0 && completedCounter === 0 && overrun)){
      status = 'Overrun';
   }
   else if (completedCounter === project.total_testcases && project.total_retest_vulnerabilities > 0){
      status = 'Retest';
   }
   else if (completedCounter === project.total_testcases) {
      status = 'Completed';
   }
   else if ((completedCounter < project.total_testcases && completedCounter > 0) || (completedCounter < project.total_testcases && initiatedCounter > 0)){
      status = 'Testing';
   }
   else if (waitingCounter === project.total_testcases) {
      status = 'Waiting to Start';
   }
   return status;
}

The return value must be one of the following values:

  • "On Hold"

  • "Overrun"

  • "Retest"

  • "Completed"

  • "Testing"

  • "Waiting to Start"

Your code does not need to handle all statuses above if you do not intend to use all statuses.

Example 1: Project Completed based on Project End Date

This example will automatically show the project as completed if now (the time you are viewing the project status in the application) is any time after the project end date.

NOTE: Overrun status has been removed as it logically does not apply under this example use.

IMPORTANT: Project statuses relying on Date.datetime() will be automatically updated every 5 minutes - it is not a live calculation. This is to ensure performance, especially when re-calculating all projects and comparing to 'now'.

if (Number.isInteger(project.total_not_tested_testcases)
   && Number.isInteger(project.total_in_progress_testcases)
   && Number.isInteger(project.total_tested_testcases)
   && Number.isInteger(project.total_testcases)
   && Number.isInteger(project.total_retest_vulnerabilities)
   && Number.isInteger(project.total_not_applicable_testcases)
   && project.end_date !== undefined
){
   const waitingCounter = project.total_not_tested_testcases;
   const initiatedCounter = project.total_in_progress_testcases;
   const completedCounter = project.total_tested_testcases + project.total_not_applicable_testcases;
   const now = Date.datetime('now', 'epoch');
   const endDatePlus24Hours = Date.datetime(project.end_date, '1 days', 'epoch');
  // Check if project is completed. Allow for 24-hour grace period.
  const projectCompleted = now > endDatePlus24Hours || completedCounter === project.total_testcases;
 
   let status;
 
   if (project.on_hold) {
      status = 'On Hold';
   }
   else if (completedCounter === project.total_testcases && project.total_retest_vulnerabilities > 0){
      status = 'Retest';
   }
   else if (projectCompleted) {
      status = 'Completed';
   }
   else if ((completedCounter < project.total_testcases && completedCounter > 0) || (completedCounter < project.total_testcases && initiatedCounter > 0)){
      status = 'Testing';
   }
   else if (waitingCounter === project.total_testcases) {
      status = 'Waiting to Start';
   }
   return status;
}

Example 2: Project Completed based on Custom Field

This example will automatically show the project as completed if a project custom field ‘project_completed’ has a value of ‘Yes’.

NOTE: This example requires configuration of a project custom field. For more information on how to do this, see Custom Fields & Forms.

NOTE: Overrun status has been removed as it logically does not apply under this example use.

IMPORTANT: Project statuses relying on Date.datetime() will be automatically updated every 5 minutes - it is not a live calculation. This is to ensure performance, especially when re-calculating all projects and comparing to 'now'.

if (Number.isInteger(project.total_not_tested_testcases)
   && Number.isInteger(project.total_in_progress_testcases)
   && Number.isInteger(project.total_tested_testcases)
   && Number.isInteger(project.total_testcases)
   && Number.isInteger(project.total_retest_vulnerabilities)
   && Number.isInteger(project.total_not_applicable_testcases)
   && project.end_date !== undefined
){
   const waitingCounter = project.total_not_tested_testcases;
   const initiatedCounter = project.total_in_progress_testcases;
   const completedCounter = project.total_tested_testcases + project.total_not_applicable_testcases;
   const now = Date.datetime('now', 'epoch');
   const endDatePlus24Hours = Date.datetime(project.end_date, '1 days', 'epoch');
  // Check if project is completed based on a project custom field "project_completed"
  const projectCompleted = (project.project_custom_fields?.project_completed === "Yes");
 
   let status;
 
   if (project.on_hold) {
      status = 'On Hold';
   }
   else if (completedCounter === project.total_testcases && project.total_retest_vulnerabilities > 0){
      status = 'Retest';
   }
   else if (projectCompleted) {
      status = 'Completed';
   }
   else if ((completedCounter < project.total_testcases && completedCounter > 0) || (completedCounter < project.total_testcases && initiatedCounter > 0)){
      status = 'Testing';
   }
   else if (waitingCounter === project.total_testcases) {
      status = 'Waiting to Start';
   }
   return status;
}

Example 3: Project Retest based on Retesting Rounds

This example will automatically show the project as retest if there is at least one outstanding retesting round.

IMPORTANT: Project statuses relying on Date.datetime() will be automatically updated every 5 minutes - it is not a live calculation. This is to ensure performance, especially when re-calculating all projects and comparing to 'now'.

if (Number.isInteger(project.total_not_tested_testcases)
   && Number.isInteger(project.total_in_progress_testcases)
   && Number.isInteger(project.total_tested_testcases)
   && Number.isInteger(project.total_testcases)
   && Number.isInteger(project.total_retest_vulnerabilities)
   && Number.isInteger(project.total_not_applicable_testcases)
   && project.end_date !== undefined
){
   const waitingCounter = project.total_not_tested_testcases;
   const initiatedCounter = project.total_in_progress_testcases;
   const completedCounter = project.total_tested_testcases + project.total_not_applicable_testcases;
   const past24hours = Date.datetime('now', '-1 days', 'epoch');
   const endDateTime = Date.datetime(project.end_date, 'epoch');
   const overrun = endDateTime < past24hours;
   const isRetest = project.total_retests_completed < project.total_retests_requested;
 
   let status;
 
   if (project.on_hold) {
      status = 'On Hold';
   }
   else if ((completedCounter < project.total_testcases && completedCounter > 0 && overrun) || (project.total_testcases > 0 && completedCounter === 0 && overrun)){
      status = 'Overrun';
   }
   else if (isRetest){
      status = 'Retest';
   }
   else if (completedCounter === project.total_testcases) {
      status = 'Completed';
   }
   else if ((completedCounter < project.total_testcases && completedCounter > 0) || (completedCounter < project.total_testcases && initiatedCounter > 0)){
      status = 'Testing';
   }
   else if (waitingCounter === project.total_testcases) {
      status = 'Waiting to Start';
   }
   return status;
}

Last updated

Check YouTube for more tutorials: https://youtube.com/@attackforge