AFScript
Last updated
Last updated
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.
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.
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.
No support for arrow functions
No support for anonymous functions
No support for creating Classes
No support for try…catch
No support for Symbol
No support for new operator
No support for spread syntax
No support for switch
No support for this
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:
fatal
error
warn
info
debug
trace
Logger.fatal(“a fatal error occurred in xyz”);
Logger.error(“an error occurred in xyz”);
Logger.warn(“a warning for xyz”);
Logger.info(“an informational message for xyz”);
Logger.debug(“a debug statement”);
Logger.trace(“function abc was called”);
datetime
The Date.datetime(“timeValue”, “modifiers”, “isostring”, “epoch”) function can be used to construct a date and time.
You can modify the date and time by passing modifiers.
The default response is the date & time in an ISO string format.
However, you can set the response to be in epoch format by passing in “epoch”.
Similarly, you can explicitly set the response to an ISO string format by passing in “isostring”.
timeValue - must be either:
“now”
“YYYY-MM-DD”
“YYYY-MM-DDTHH:MM”
“YYYY-MM-DDTHH:MM:SS”
“YYYY-MM-DDTHH:MM:SS:MMMZ”
time in milliseconds e.g. "1727246762913"
modifiers - must be either:
“+999 years”
“-999 years”
“+999 months”
“-999 months”
“+999 days”
“-999 days”
“+999 hours”
“-999 hours”
“+999 minutes”
“-999 minutes”
“start of year”
“start of month”
“start of day”
decode
The String.decode() method takes a string and decodes it to a supplied format.
The following formats are supported:
digest
The String.digest() method takes a string and a supplied hashing algorithm and encodes it to a supplied format.
The following hashing algorithms are supported:
The following formats are supported:
encode
The String.encode() method takes a string and encodes it to a supplied format.
The following formats are supported:
The String.endsWith() method determines whether a string ends with the characters of this string, returning true or false as appropriate.
The String.padEnd() method pads this string with a given string (repeated, if needed) so that the resulting string reaches a given length. The padding is applied from the end of this string.
The String.padStart() method pads this string with another string (multiple times, if needed) until the resulting string reaches the given length. The padding is applied from the start of this string.
The String.slice() method extracts a section of this string and returns it as a new string, without modifying the original string.
The String.startsWith() method determines whether this string begins with the characters of a specified string, returning true or false as appropriate.
The String.substring() method returns the part of this string from the start index up to and excluding the end index, or to the end of the string if no end index is supplied.
The String.toLowerCase() method returns this string converted to lowercase.
The String.toUpperCase() method returns this string converted to uppercase.
The String.trim() method removes whitespace from both ends of this string and returns a new string, without modifying the original string.
The String.trimEnd() method removes whitespace from the end of this string and returns a new string, without modifying the original string.
The String.trimStart() method removes whitespace from the beginning of this string and returns a new string, without modifying the original string.
The Array.at() method takes an integer value and returns the item at that index, allowing for positive and negative integers. Negative integers count back from the last item in the array.
The Array.concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.
The Array.every() method tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value.
The Array.findIndex() method returns the index of the first element in an array that satisfies the provided testing function. If no elements satisfy the testing function, -1 is returned.
The Array.flat() method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth.
The Array.flatMap() method returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.
The Array.includes() method determines whether an array includes a certain value among its entries, returning true or false as appropriate.
The Array.indexOf() method returns the first index at which a given element can be found in the array, or -1 if it is not present.
The Array.join() method creates and returns a new string by concatenating all of the elements in this array, separated by commas or a specified separator string. If the array has only one item, then that item will be returned without using the separator.
The Array.lastIndexOf() method returns the last index at which a given element can be found in the array, or -1 if it is not present. The array is searched backwards, starting at fromIndex.
The Array.length data property represents the number of elements in that array. The value is an unsigned, 32-bit integer that is always numerically greater than the highest index in the array.
The Array.map() method creates a new array populated with the results of calling a provided function on every element in the calling array.
The Array.pop() method removes the last element from an array and returns that element. This method changes the length of the array.
The Array.push() method adds the specified elements to the end of an array and returns the new length of the array.
The Array.reduce() method executes a user-supplied "reducer" callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value.
The first time that the callback is run there is no "return value of the previous calculation". If supplied, an initial value may be used in its place. Otherwise the array element at index 0 is used as the initial value and iteration starts from the next element (index 1 instead of index 0).
The Array.reduceRight() method applies a function against an accumulator and each value of the array (from right-to-left) to reduce it to a single value.
The Array.shift() method removes the first element from an array and returns that removed element. This method changes the length of the array.
The Array.some() method tests whether at least one element in the array passes the test implemented by the provided function. It returns true if, in the array, it finds an element for which the provided function returns true; otherwise it returns false. It doesn't modify the array.
The Array.unshift() method adds the specified elements to the beginning of an array and returns the new length of the array.
The JSON.parse() static method parses a JSON string, constructing the JavaScript value or object described by the string.
The JSON.stringify() static method converts a JavaScript value to a JSON string, optionally replacing values if a replacer function is specified or optionally including only the specified properties if a replacer array is specified.
The Math.abs() static method returns the absolute value of a number.
The Math.acos() static method returns the inverse cosine (in radians) of a number.
The Math.acosh() static method returns the inverse hyperbolic cosine of a number.
The Math.asin() static method returns the inverse sine (in radians) of a number.
The Math.asinh() static method returns the inverse hyperbolic sine of a number.
The Math.atan() static method returns the inverse tangent (in radians) of a number.
The Math.atan2() static method returns the angle in the plane (in radians) between the positive x-axis and the ray from (0, 0) to the point (x, y), for Math.atan2(y, x).
The Math.atanh() static method returns the inverse hyperbolic tangent of a number.
The Math.cbrt() static method returns the cube root of a number.
The Math.ceil() static method always rounds up and returns the smallest integer greater than or equal to a given number.
The Math.clz32() static method returns the number of leading zero bits in the 32-bit binary representation of a number.
The Math.cos() static method returns the cosine of a number in radians.
The Math.cosh() static method returns the hyperbolic cosine of a number.
The Math.floor() static method always rounds down and returns the largest integer less than or equal to a given number.
The Math.hypot() static method returns the square root of the sum of squares of its arguments.
The Math.imul() static method returns the result of the C-like 32-bit multiplication of the two parameters.
The Math.log10() static method returns the base 10 logarithm of a number.
The Math.log2() static method returns the base 2 logarithm of a number.
The Math.pow() static method returns the value of a base raised to a power.
The Math.random() static method returns a floating-point, pseudo-random number that's greater than or equal to 0 and less than 1, with approximately uniform distribution over that range — which you can then scale to your desired range. The implementation selects the initial seed to the random number generation algorithm; it cannot be chosen or reset by the user.
The Math.round() static method returns the value of a number rounded to the nearest integer.
The Math.sign() static method returns 1 or -1, indicating the sign of the number passed as argument. If the input is 0 or -0, it will be returned as-is.
The Math.sin() static method returns the sine of a number in radians.
The Math.sinh() static method returns the hyperbolic sine of a number.
The Math.sqrt() static method returns the square root of a number.
The Math.tan() static method returns the tangent of a number in radians.
The Math.tanh() static method returns the hyperbolic tangent of a number.
The Math.trunc() static method returns the integer part of a number by removing any fractional digits.
The Number.isInteger() static method determines whether the passed value is an integer.
The Number.isSafeInteger() static method determines whether the provided value is a number that is a safe integer.
The Number.parseInt() static method parses a string argument and returns an integer of the specified radix or base.
randomId
The Util.randomId() method returns a cryptographically secure random value, encoded using a supported encoding.
The first parameter must be a string specifying one of the following encodings:
The second parameter must be a number which specifies how many bytes the random value should be.
uuidv4
A UUID is 128 bits long, and can guarantee uniqueness across space and time.
You can test with Regular Expressions (RegExp) using the following syntax:
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.
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.
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.
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'.
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.
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'.
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'.
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'.
You can use AFScript to suggest values for fields.
Suggestions can help to guide users into completing forms, based on your own logic.
For example:
Suggest a project code or vulnerability code on a project
Suggest a custom score for a vulnerability
Suggest a budget for a project/test, based on how scoping questions have been answered
Suggest missing evidence for a vulnerability
Suggest execution flows for a test case
!IMPORTANT: Suggested values are only supported on the Project form at present. This feature is expected to be widely supported in Q4, 2025.
Input fields
Must return a string, for example:
Text Area fields
Must return a string, for example:
Rich-Text fields
Must return a string. Can be HTML, for example:
Select fields
Must return a string, for example:
Multi-Select fields
Must return a string array, for example:
Date fields
Must return a string in ISO 8601 UTC format (YYYY-MM-DDThh:mm:ssZ), for example:
Table fields
Must return an array of objects. Each object must include the key for the column field and appropriate values, for example:
List fields
Must return a string array, for example:
User Select fields
Must return a string array with each string as an Object Id, for example:
User Multi-Select fields
Must return a string array with each string as an Object Id, for example:
Group Select fields
Must return a string array with each string as an Object Id, for example:
Group Multi-Select fields
Must return a string array with each string as an Object Id, for example:
You can suggest a custom project code and a vulnerability code prefix when creating or editing a project.
To get started with Project Code
, click on Administration -> Projects -> Fields (Code) -> Suggested Value (Configure)
To get started with Vulnerability Code
, click on Administration -> Projects -> Fields (Vulnerability Code)
This example will suggest a code which is made up of the customer name (or a short name for the customer you can create a mapping for).
Example input: ACME Corp.
Example project code: ACME
PREREQUSITIES:
You must have a SELECT type project custom field which is used to select the customer on the project. This example uses a custom field with a key 'customer'.
Selecting the customer:
Suggested project code:
This example will suggest a code which is made up of the customer name (or a short name for the customer you can create a mapping for) as well as the testing types assigned to the project.
Example input: ACME Corp. + Web App
Example project code: ACME-WEBAPP
PREREQUSITIES:
You must have a SELECT type project custom field which is used to select the customer on the project. This example uses a custom field with a key 'customer'.
You must have a MULTI-SELECT type project custom field which is used to select the testing types assigned to the project. This example uses a custom field with a key 'testing_types'.
Selecting the customer and testing types:
Suggested project code:
This example will suggest a code which is made up of the customer name (or a short name for the customer you can create a mapping for) as well as the testing types assigned to the project, and a random number.
Example input: ACME Corp. + Web App
Example project code: ACME-WEBAPP-192834
PREREQUSITIES:
You must have a SELECT type project custom field which is used to select the customer on the project. This example uses a custom field with a key 'customer'.
You must have a MULTI-SELECT type project custom field which is used to select the testing types assigned to the project. This example uses a custom field with a key 'testing_types'.