LogoLogo
  • AttackForge Support
  • Release Notes
    • 2025
    • 2024
    • 2023
    • 2022
    • 2021
    • 2020
    • 2019
  • Core & Enterprise
    • Getting Started
      • How It Works
      • Requesting A Project
      • Creating & Updating Projects
      • Invite User To Project
      • View Project Team
      • Project Scope
      • Project Workspace
      • Project Notes
      • Project Pages
      • Test Cases
      • Creating Vulnerabilities
      • Updating Vulnerabilities
      • Review & QA
      • Attack Chains
      • Reporting
      • Retesting & Remediation
      • Notifications
      • Custom Fields & Forms
      • Vulnerability SLAs
      • User Settings
      • Login Redirects
    • Modules
      • Dashboard
      • Analytics
      • Vulnerabilities
      • Projects
      • Scheduling
      • Portfolios
      • Groups
      • Attack Chains
      • Assets
      • Writeups
      • Test Suites
      • Report Templates
        • Overview
        • Tutorial
        • Tips & Tricks
        • Troubleshooting
        • Template - Report Templates
        • Template - Tags
        • Template - Options
        • Template - Functions
        • Template - Filters
        • Template - Styles
        • Template - Tables
        • Template - Charts
        • Template - Conditions
        • ReportGen CLI
        • ReportGen Library
      • Users
      • Administration
      • Flows
      • Self-Service RESTful API
        • GETTING STARTED
        • EXPORTING TO CSV
        • ADVANCED QUERY FILTER
        • ActivateUser
        • AddProjectMembershipAdministrators
        • AddTestcaseToTestsuite
        • AddTestcasesToTestsuite
        • AddUserToGroup
        • ApproveProjectRequestById
        • ArchivePortfolio
        • ArchiveProject
        • CancelProjectRetestRound
        • CloneProject
        • CompleteProjectRetestRound
        • CreateAssetInLibrary
        • CreateGroup
        • CreatePortfolio
        • CreateProject
        • CreateProjectNote
        • CreateProjectRequest
        • CreateProjectTestCase
        • CreateProjectWorkspaceNote
        • CreateRemediationNote
        • CreateScope
        • CreateTestcaseNote
        • CreateTestsuite
        • CreateUser
        • CreateUsers
        • CreateVulnerability
        • CreateVulnerabilityBulk
        • CreateVulnerabilityLibraryIssue
        • CreateVulnerabilityWithLibrary
        • DeactivateUser
        • DownloadProjectTestCaseFile
        • DownloadProjectTestCaseNoteFile
        • DownloadProjectTestCaseWorkspaceNoteFile
        • DownloadVulnerabilityEvidence
        • DownloadVulnerabilityLibraryFile
        • DownloadWorkspaceFile
        • GetApplicationAuditLogs
        • GetAssets
        • GetAssetsByGroup
        • GetAssetInLibrary
        • GetAssetsInLibrary
        • GetCustomFieldsConfig
        • GetFormConfig
        • GetGroup
        • GetGroups
        • GetMostCommonVulnerabilities
        • GetMostFailedTestcases
        • GetMostVulnerableAssets
        • GetPortfolio
        • GetPortfolios
        • GetPortfolioStream
        • GetProjectAuditLogs
        • GetProjectById
        • GetProjects
        • GetProjectsAndVulnerabilities
        • GetProjectsByGroup
        • GetProjectMembershipAdministrators
        • GetProjectNotes
        • GetProjectReport
        • GetProjectReportData
        • GetProjectRequests
        • GetProjectRequestById
        • GetProjectTestcasesById
        • GetProjectVulnerabilitiesById
        • GetProjectWorkspace
        • GetTestsuiteById
        • GetTestsuites
        • GetUserByEmail
        • GetUserById
        • GetUserByUsername
        • GetUserAuditLogs
        • GetUserGroups
        • GetUserLoginHistory
        • GetUserProjects
        • GetUsers
        • GetVulnerabilityById
        • GetVulnerabilities
        • GetVulnerabilitiesByAssetName
        • GetVulnerabilitiesByGroup
        • GetVulnerabilityLibraryIssues
        • GetVulnerabilityRevisionHistory
        • InviteUserToProject
        • InviteUsersToProjectTeam
        • RejectProjectRequestById
        • RegenerateAPIKey
        • RemoveProjectMembershipAdministrators
        • RemoveProjectTeamMembers
        • RequestNewProjectRetest
        • RestoreProject
        • SendEmail
        • SendDailyCommencementEmail
        • SendDailyCompletionEmail
        • UpdateAssetInLibrary
        • UpdateCustomFieldsConfig
        • UpdateExecSummaryNotes
        • UpdateFormConfig
        • UpdateGroup
        • UpdatePortfolio
        • UpdateProjectById
        • UpdateProjectMembershipAdministrators
        • UpdateProjectNote
        • UpdateProjectRequestById
        • UpdateProjectRetestRound
        • UpdateProjectWorkspaceNote
        • UpdateScope
        • UpdateTestcase
        • UpdateTestcaseOnTestsuite
        • UpdateTestsuite
        • UpdateUserAccessOnGroup
        • UpdateUserAccessOnProject
        • UpdateUser
        • UpdateVulnerabilityById
        • UpdateVulnerabilityLibraryIssue
        • UpdateVulnerabilitySLAs
        • UpdateVulnerabilityWithLibrary
        • UploadTestcaseFile
        • UploadVulnerabilityEvidence
        • UploadVulnerabilityLibraryFile
        • UploadWorkspaceFile
      • Self-Service Events API
        • GETTING STARTED
        • Project Created
        • Project Updated
        • Project Request Created
        • Project Request Updated
        • Project Retest Requested
        • Project Retest Completed
        • Project Retest Cancelled
        • Vulnerability Created
        • Vulnerability Updated
        • Vulnerability Evidence Created
        • Vulnerability Evidence Updated
        • Vulnerability Remediation Note Created
        • Vulnerability Remediation Note Updated
    • AFScript
    • Access Control Matrix
    • Raising Support Tickets
    • Security
  • Contact
Powered by GitBook

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

On this page
  • What is AFScript?
  • How does AFScript work?
  • What can’t AFScript do?
  • Logging
  • Built-in Functions
  • Dates
  • Strings
  • Arrays
  • JSON
  • Math
  • Numbers
  • Util
  • Regular Expressions
  • Context
  • Code Editing
  • Output
  • Supported Use Cases
  • Project Status Calculations
  • Suggested Values
  1. Core & Enterprise

AFScript

PreviousVulnerability Remediation Note UpdatedNextAccess Control Matrix

Last updated 3 months ago

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

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.

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?

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.

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

Function
Description
Example

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”

Strings

Function
Description
Example

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.

Arrays

Function
Description
Example

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.

JSON

Function
Description
Example

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.

Math

Function
Description
Example

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.

Numbers

Function
Description
Example

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.

Util

Function
Description
Example

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.

Regular Expressions

const myVariable = "WEB-APP-027458";

// Test if myVariable ends with six-digits
if (myVariable =~ m/\d{6}$/) {
  return "myVariable has a numbered code applied";
}

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.

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.

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.

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.

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: Overrun status has been removed as it logically does not apply under this example use.

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.

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;
}

Suggested Values

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.

Suggestion Formats

Input fields

Must return a string, for example:

return "This is some text";

Text Area fields

Must return a string, for example:

return "This is some text.\nThis is some more text.";

Rich-Text fields

Must return a string. Can be HTML, for example:

return "<h1>This is some heading</h1>";

Select fields

Must return a string, for example:

return "Yes";

Multi-Select fields

Must return a string array, for example:

return ["Yes","Maybe"];

Date fields

Must return a string in ISO 8601 UTC format (YYYY-MM-DDThh:mm:ssZ), for example:

return "2024-09-18T21:12:16.478Z";

Table fields

Must return an array of objects. Each object must include the key for the column field and appropriate values, for example:

return [
    {
        "name": "Bruce Wayne",
        "role": "Defender of Gotham"
    }
];

List fields

Must return a string array, for example:

return ["Tag 1","Tag 2"];

User Select fields

Must return a string array with each string as an Object Id, for example:

return ["63cb153fedc40abef76bf991"];

User Multi-Select fields

Must return a string array with each string as an Object Id, for example:

return ["63cb153fedc40abef76bf991"];

Group Select fields

Must return a string array with each string as an Object Id, for example:

return ["63cb153fedc40abef76bf991"];

Group Multi-Select fields

Must return a string array with each string as an Object Id, for example:

return ["63cb153fedc40abef76bf991"];

Project Code and Vulnerability Code

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)

Example 1: Suggest A Code Based on the Customer

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'.

const customerMap = {
    "ACME Corp.": "ACME",
    "Red Team": "REDTEAM",
    "Pentesters": "PEN",
    "Globex Corp.": "GLOBEX-CORP"
};

if (project?.project_custom_fields?.customer?.name
    && customerMap[project.project_custom_fields.customer.name]
) {
    let projectCode = customerMap[project.project_custom_fields.customer.name];

    return projectCode;
}
else {
    return project?.last_project_code;
}

Selecting the customer:

Suggested project code:

Example 2: Suggest A Code Based on the Customer and Testing Types

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'.

const customerMap = {
    "ACME Corp.": "ACME",
    "Red Team": "REDTEAM",
    "Pentesters": "PEN",
    "Globex Corp.": "GLOBEX-CORP"
};

const testingTypeMap = {
    "Web App": "WEBAPP",
    "API": "API",
    "Red Team": "REDTEAM",
    "Bug Bounty": "BB" 
};

if (project?.project_custom_fields?.customer?.name
    && customerMap[project.project_custom_fields.customer.name]
    && project.project_custom_fields.testing_types[0]
) {
    let projectCode = customerMap[project.project_custom_fields.customer.name];

    for (let x=0; x < project.project_custom_fields.testing_types.length; x++) {
        if (testingTypeMap[project.project_custom_fields.testing_types[x]]) {
            projectCode = projectCode + "-";
            projectCode = projectCode + testingTypeMap[project.project_custom_fields.testing_types[x]];
        }
    }

    return projectCode;
}
else {
    return project?.last_project_code;
}

Selecting the customer and testing types:

Suggested project code:

Example 3: Suggest A Code Based on the Customer, Testing Types and a Generated Random Number

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'.

const customerMap = {
    "ACME Corp.": "ACME",
    "Red Team": "REDTEAM",
    "Pentesters": "PEN",
    "Globex Corp.": "GLOBEX-CORP"
};

const testingTypeMap = {
    "Web App": "WEBAPP",
    "API": "API",
    "Red Team": "REDTEAM",
    "Bug Bounty": "BB" 
};

if (project?.project_custom_fields?.customer?.name
    && customerMap[project.project_custom_fields.customer.name]
    && project.project_custom_fields.testing_types[0]
) {
    let projectCode = customerMap[project.project_custom_fields.customer.name];
    const randomNumber = Math.floor(Math.random() * 999999);

    for (let x=0; x < project.project_custom_fields.testing_types.length; x++) {
        if (testingTypeMap[project.project_custom_fields.testing_types[x]]) {
            projectCode = projectCode + "-";
            projectCode = projectCode + testingTypeMap[project.project_custom_fields.testing_types[x]];
        }
    }

    projectCode = projectCode + "-";
    projectCode = projectCode + randomNumber;

    return projectCode;
}
else {
    return project?.last_project_code;
}

Selecting the customer and testing types:

Suggested project code:

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

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

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

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

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

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

No support for

No support for

No support for creating

No support for

No support for

No support for

No support for and loops

No support for

No support for

No support for or

No support for

The String.length data property of a value contains the length of the string in UTF-16 code units.

The String.replace() method returns a new string with one, some, or all matches of a pattern replaced by a replacement. The pattern can be a string or a , and the replacement can be a string or a function called for each match. If pattern is a string, only the first occurrence will be replaced. The original string is left unchanged.

The String.replaceAll() method returns a new string with all matches of a pattern replaced by a replacement. The pattern can be a string or a , and the replacement can be a string or a function to be called for each match. The original string is left unchanged.

To return a new string with whitespace trimmed from just one end, use or .

The Array.find() method returns the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function, is returned.

If you need the index of the found element in the array, use .

If you need to find the index of a value, use . (It's similar to , but checks each element for equality with the value instead of using a testing function.)

If you need to find if a value exists in an array, use . Again, it checks each element for equality with the value instead of using a testing function.

If you need to find if any element satisfies the provided testing function, use .

See also the method, which returns the first element that satisfies the testing function (rather than its index).

The Array.isArray() static method determines whether the passed value is an .

The Array.reverse() method reverses an array and returns the reference to the same array, the first array element now becoming the last, and the last array element becoming the first. In other words, elements order in the array will be turned towards the direction opposite to that previously stated.

The Array.slice() method returns a of a portion of an array into a new array object selected from start to end (end not included) where start and end represent the index of items in that array. The original array will not be modified.

The Array.sort() method sorts the elements of an array and returns the reference to the same array, now sorted. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.

The Array.splice() method changes the contents of an array by removing or replacing existing elements and/or adding new elements .

To access part of an array without modifying it, see .

The Math.exp() static method returns raised to the power of a number.

The Math.expm1() static method returns raised to the power of a number, subtracted by 1.

The Math.fround() static method returns the nearest float representation of a number.

The Math.log() static method returns the natural logarithm (base ) of a number.

The Math.log1p() static method returns the natural logarithm (base ) of 1 + x, where x is the argument.

The Math.max() static method returns the largest of the numbers given as input parameters, or - if there are no parameters.

The Math.min() static method returns the smallest of the numbers given as input parameters, or if there are no parameters.

The Number.isFinite() static method determines whether the passed value is a finite number — that is, it checks that a given value is a number, and the number is neither positive , negative Infinity, nor .

The Number.isNaN() static method determines whether the passed value is the number value , and returns false if the input is not of the Number type. It is a more robust version of the original, global function.

The Number.parseFloat() static method parses an argument and returns a floating point number. If a number cannot be parsed from the argument, it returns .

The Util.uuidv4() method returns a also known as a GUID (Globally Unique IDentifier).

You can test with (RegExp) using the following syntax:

You can also easily access .

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

IMPORTANT: Project statuses relying on 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'.

IMPORTANT: Project statuses relying on 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'.

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

IMPORTANT: Project statuses relying on 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'.

IMPORTANT: Project statuses relying on 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'.

//Example 1:
Logger.debug(Date.datetime("2020-06-01"));
// Expected output: “2020-06-01T00:00:00.000Z”

//Example 2: 
Logger.debug(Date.datetime("now"));
// Expected output: “2024-09-09T13:24:05.274Z”

//Example 3:
Logger.debug(Date.datetime("now", "epoch"));
// Expected output: 1725888245274

//Example 4:
Logger.debug(Date.datetime("now", "-7 days"));
// Expected output: “2024-09-02T13:24:05.274Z”

//Example 5:
Logger.debug(Date.datetime("now", "-7 days", "+1 years"));
// Expected output: “2025-09-02T13:24:05.274Z”
Logger.debug(String.decode('dGhpcyBpcyBlbmNvZGVk', 'base64'));
// Expected output: this is encoded

Logger.debug(String.decode('dGhpcyBpcyBlbmNvZGVk', 'base64url'));
// Expected output: this is encoded
 
Logger.debug(String.decode('ORUGS4ZANFZSAZLOMNXWIZLE', 'base32'));
// Expected output: this is encoded

Logger.debug(String.decode('EHK6ISP0D5PI0PBECDNM8PB4', 'base32hex'));
// Expected output: this is encoded

Logger.debug(String.decode('7468697320697320656E636F646564', 'base16'));
// Expected output: this is encoded

Logger.debug(String.decode('BzbxfazC)twO#0@wmYo{'));
// Expected output: this is encoded!
Logger.debug(String.digest('hash this', 'SHA1'));
// Expected output: E1449548860671BCFED5A5D1E4701532A0308D20

Logger.debug(String.digest('hash this', 'SHA1', 'base64'));
// Expected output: 4USVSIYGcbz+1aXR5HAVMqAwjSA=

Logger.debug(String.digest('hash this', 'SHA224', 'base64'));
// Expected output: q6y5930Dpk8Y5BZSLgRFXYDoBfmzbWi10vCftQ====

ogger.debug(String.digest('hash this', 'SHA256', 'base64'));
// Expected output: GUZ3iLwM8ReQoHXqcYRSzs8OedtZ0ZZGcEdeX+LkphE=

ogger.debug(String.digest('hash this', 'SHA384', 'base64'));
// Expected output: Evu6KXz7wzv1U8QjEV20uNvBuWWoIGJ/O7YgG5EQFekQaqOd21lct9vFjtkLmxGP

ogger.debug(String.digest('hash this', 'SHA512', 'base64'));
// Expected output: zwboR4ZuVgz0iMrhfBmT6ntxTfAhXyFrBMKDQY62mymdjq99N4QzncNLtZcyfzobqo8a1GWL2IY5p4Ri1vgvcA==

ogger.debug(String.digest('hash this', 'MD5', 'base64'));
// Expected output: 6AxxXl1OiF9o16OFO1/Kcw==

ogger.debug(String.digest('hash this', 'RMD160', 'base64'));
// Expected output: 1UQ6FU8WfiwTMvbecs+0xqucjBc=
Logger.debug(String.encode('this is encoded', 'base64'));
// Expected output: dGhpcyBpcyBlbmNvZGVk

Logger.debug(String.encode('this is encoded', 'base64url'));
// Expected output: dGhpcyBpcyBlbmNvZGVk
 
Logger.debug(String.encode('this is encoded', 'base32'));
// Expected output: ORUGS4ZANFZSAZLOMNXWIZLE

Logger.debug(String.encode('this is encoded', 'base32hex'));
// Expected output: EHK6ISP0D5PI0PBECDNM8PB4

Logger.debug(String.encode('this is encoded', 'base16'));
// Expected output: 7468697320697320656E636F646564

Logger.debug(String.encode('this is encoded!', 'Z85'));
// Expected output: BzbxfazC)twO#0@wmYo{
const str1 = 'Cats are the best!';
 
Logger.debug(String.endsWith(str1, 'best!'));
// Expected output: true
Logger.debug(String.length('The quick brown fox jumps over the lazy dog.'));
// Expected output: 44
const str1 = 'Breaded Mushrooms';
 
Logger.debug(String.padEnd(str1, 25, '.'));
// Expected output: "Breaded Mushrooms........"
const str1 = '5';
 
Logger.debug(String.padStart(str1, 2, '0'));
// Expected output: "05"
const paragraph = "I think Ruth's dog is cuter than your dog!";

Logger.debug(String.replace(paragraph, "Ruth's", 'my'));
// Expected output: "I think my dog is cuter than your dog!"

Logger.debug(String.replace(paragraph, m/Dog/i, 'ferret'));
// Expected output: "I think Ruth's ferret is cuter than your dog!"
const paragraph = "I think Ruth's dog is cuter than your dog!";

Logger.debug(String.replaceAll(paragraph, 'dog', 'monkey'));
// Expected output: "I think Ruth's monkey is cuter than your monkey!"

// Global flag required when calling replaceAll with regex
Logger.debug(String.replaceAll(paragraph, m/Dog/gi, 'ferret'));
// Expected output: "I think Ruth's ferret is cuter than your ferret!"
const str1 = 'The quick brown fox jumps over the lazy dog.';
 
Logger.debug(String.slice(str1, 31));
// Expected output: "the lazy dog."
 
Logger.debug(String.slice(str1, 4, 19));
// Expected output: "quick brown fox"
const str1 = 'Saturday night plans';
 
Logger.debug(String.startsWith(str1, 'Sat'));
// Expected output: true
 
Logger.debug(String.startsWith(str1, 'Sat', 3));
// Expected output: false
const str1 = 'Mozilla';
 
Logger.debug(String.substring(str1, 1, 3));
// Expected output: "oz"
 
Logger.debug(String.substring(str1, 2));
// Expected output: "zilla"
Logger.debug(String.toLowerCase('The quick brown fox jumps over the lazy dog.'));
// Expected output: the quick brown fox jumps over the lazy dog.
Logger.debug(String.toUpperCase('The quick brown fox jumps over the lazy dog.'));
// Expected output: THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.
const str1 = '   Hello world!   ';
 
Logger.debug(str1);
// Expected output: "   Hello world!   ";
 
Logger.debug(String.trim(str1));
// Expected output: "Hello world!";
const str1 = '   Hello world!   ';
 
Logger.debug(str1);
// Expected output: "   Hello world!   ";
 
Logger.debug(String.trimEnd(str1));
// Expected output: "   Hello world!";
const str1 = '   Hello world!   ';
 
Logger.debug(str1);
// Expected output: "   Hello world!   ";
 
Logger.debug(String.trimStart(str1));
// Expected output: "Hello world!   ";
const array1 = [5, 12, 8, 130, 44];
 
Logger.debug(Array.at(array1, 2));
// Expected output: 8
 
Logger.debug(Array.at(array1, -2));
// Expected output: 130
const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3 = Array.concat(array1, array2);
 
Logger.debug(JSON.stringify(array3));
// Expected output: “["a","b","c","d","e","f"]”
function isBelowThreshold(currentValue) {
  return currentValue < 40;
}
 
const array1 = [1, 30, 39, 29, 10, 13];
 
Logger.debug(Array.every(array1, isBelowThreshold));
// Expected output: true
const array1 = [5, 12, 8, 130, 44];
 
function isFound(number) {
   return number === 8;
}
 
const found = Array.find(array1, isFound);
 
Logger.debug(found);
// Expected output: 8
const array1 = [5, 12, 8, 130, 44];
 
function isLargeNumber(number) {
   return number > 13;
}
 
Logger.debug(Array.findIndex(array1, isLargeNumber));
// Expected output: 3
const arr1 = [0, 1, 2, [3, 4]];
 
Logger.debug(JSON.stringify(Array.flat(arr1)));
// Expected output: “[0,1,2,3,4]”
 
const arr2 = [0, 1, [2, [3, [4, 5]]]];
 
Logger.debug(JSON.stringify(Array.flat(arr2)));
// Expected output: “[0,1,2,[3,[4,5]]]”
 
Logger.debug(JSON.stringify(Array.flat(arr2, 2)));
// Expected output: “[0,1,2,3, Array [4,5]]”
 
Logger.debug(JSON.stringify(Array.flat(arr2, Infinity)));
// Expected output: “[0,1,2,3,4,5]”
const arr1 = [1, 2, 1];
 
function callbackFunc(number) {
   return number === 2 ? [2, 2] : 1;
}
 
const result = Array.flatMap(arr1, callbackFunc);
 
Logger.debug(JSON.stringify(result));
// Expected output: “[1,2,2,1]”
const array1 = [1, 2, 3];
 
Logger.debug(Array.includes(array1, 2));
// Expected output: true
 
const pets = ['cat', 'dog', 'bat'];
 
Logger.debug(Array.includes(pets, 'cat'));
// Expected output: true
 
Logger.debug(Array.includes(pets, 'at'));
// Expected output: false
const beasts = ['ant', 'bison', 'camel', 'duck', 'bison'];
 
Logger.debug(Array.indexOf(beasts, 'bison'));
// Expected output: 1
 
// Start from index 2
Logger.debug(Array.indexOf(beasts, 'bison', 2));
// Expected output: 4
 
Logger.debug(Array.indexOf(beasts, 'giraffe'));
// Expected output: -1
Logger.debug(Array.isArray([1, 3, 5]));
// Expected output: true
 
Logger.debug(Array.isArray('[]'));
// Expected output: false
const elements = ['Fire', 'Air', 'Water'];
 
Logger.debug(Array.join(elements));
// Expected output: "Fire,Air,Water"
 
Logger.debug(Array.join(elements, ''));
// Expected output: "FireAirWater"
 
Logger.debug(Array.join(elements, '-'));
// Expected output: "Fire-Air-Water"
const animals = ['Dodo', 'Tiger', 'Penguin', 'Dodo'];
 
Logger.debug(Array.lastIndexOf(animals, 'Dodo'));
// Expected output: 3
 
Logger.debug(Array.lastIndexOf(animals, 'Tiger'));
// Expected output: 1
const clothing = ['shoes', 'shirts', 'socks', 'sweaters'];
 
Logger.debug(Array.length(clothing));
// Expected output: 4
const array1 = [1, 4, 9, 16];
 
function double(number) {
   return number * 2;
}
 
const map1 = Array.map(array1, double);
 
Logger.debug(JSON.stringify(map1));
// Expected output: “[2,8,18,32]”
const plants = ['broccoli', 'cauliflower', 'cabbage', 'kale', 'tomato'];
 
Logger.debug(Array.pop(plants));
// Expected output: "tomato"
 
Logger.debug(JSON.stringify(plants));
// Expected output: “["broccoli","cauliflower","cabbage","kale"]”
 
Array.pop(plants);
 
Logger.debug(JSON.stringify(plants));
// Expected output: “["broccoli","cauliflower","cabbage"]”
const animals = ['pigs', 'goats', 'sheep'];
 
const count = Array.push(animals, 'cows');
Logger.debug(count);
// Expected output: 4
Logger.debug(JSON.stringify(animals));
// Expected output: “["pigs","goats","sheep","cows"]”
 
Array.push(animals, 'chickens', 'cats', 'dogs');
Logger.debug(JSON.stringify(animals));
// Expected output: “["pigs","goats","sheep","cows","chickens","cats","dogs"]”
const array1 = [1, 2, 3, 4];
 
function reducer(accumulator, currentValue, index) {
   return accumulator + currentValue;
}
 
// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = Array.reduce(array1, reducer,
 initialValue,
);
 
Logger.debug(sumWithInitial);
// Expected output: 10
const array1 = [
  [0, 1],
  [2, 3],
  [4, 5],
];
 
function reducer(accumulator, currentValue) {
   return Array.concat(accumulator, currentValue);
}
 
const result = Array.reduceRight(array1, reducer);
 
Logger.debug(JSON.stringify(result));
// Expected output: “[4,5,2,3,0,1]”
const array1 = ['one', 'two', 'three'];
Logger.debug(JSON.stringify(array1));
// Expected output: “["one","two","three"]”
 
const reversed = Array.reverse(array1);
Logger.debug(JSON.stringify(reversed));
// Expected output: “["three","two","one"]”
const array1 = [1, 2, 3];
 
const firstElement = Array.shift(array1);
 
Logger.debug(JSON.stringify(array1));
// Expected output: “[2,3]”
 
Logger.debug(firstElement);
// Expected output: 1
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
 
Logger.debug(JSON.stringify(Array.slice(animals, 2)));
// Expected output: “["camel","duck","elephant"]”
 
Logger.debug((JSON.stringify(Array.slice(animals, 2, 4)));
// Expected output: “["camel","duck"]”
 
Logger.debug((JSON.stringify(Array.slice(animals, 1, 5)));
// Expected output: “["bison","camel","duck", "elephant"]”
 
Logger.debug((JSON.stringify(Array.slice(animals, -2)));
// Expected output: “["duck","elephant"]”
 
Logger.debug((JSON.stringify(Array.slice(animals, 2, -1)));
// Expected output: “["camel","duck"]”
 
Logger.debug((JSON.stringify(Array.slice(animals)));
// Expected output: “["ant","bison","camel", "duck", "elephant"]”
const array = [1, 2, 3, 4, 5];
 
function isEven(number) {
   return number % 2 === 0;
}
 
Logger.debug(Array.some(array, isEven));
// Expected output: true
//Example 1 – Sort ascending:
const arr1 = [5,2,1,9];
 
Array.sort(arr1);
 
Logger.debug(JSON.stringify(arr1));
// Expected output: “[1,2,5,9]”
 
//Example 2 – Sort descending:
const arr1 = [5,2,1,9];
 
function compareFn(a, b) {
   return b - a;
}
 
Array.sort(arr1, compareFn);
 
Logger.debug(JSON.stringify(arr1));
// Expected output: “[9,5,2,1]”
 
//Example 3:
const arr2 = ["b","d","c","a"];
 
function compareFn(a, b) {
   if (a > b) {
      return 1;
   }
   else if (a < b) {
      return -1;
   }
   else {
      return 0;
   }
}
 
Array.sort(arr2, compareFn);
 
Logger.debug(JSON.stringify(arr2));
// Expected output: “["a","b","c","d"]”
 
//Example 4:
const arr3 = [{v:"b"},{v:"d"},{v:"c"},{v:"a"}];
 
function compareFn(a, b) {
   if (a.v > b.v) {
      return 1;
   }
   else if (a.v < b.v) {
      return -1;
   }
   else {
      return 0;
   }
}
 
Array.sort(arr3, compareFn);
 
Logger.debug(JSON.stringify(arr3));
// Expected output: “[{"v":"a"},{"v":"b"},{"v":"c"},{"v":"d"}]”
const months = ['Jan', 'March', 'April', 'June'];
Array.splice(months, 1, 0, 'Feb');
// Inserts at index 1
Logger.debug(JSON.stringify(months));
// Expected output: “["Jan","Feb","March","April", "June"]”
 
Array.splice(months, 4, 1, 'May');
// Replaces 1 element at index 4
Logger.debug(JSON.stringify(months));
// Expected output: “["Jan","Feb","March","April","May"]”
const array1 = [1, 2, 3];
 
Logger.debug(Array.unshift(array1, 4, 5));
// Expected output: 5
 
Logger.debug(JSON.stringify(array1));
// Expected output: “[4,5,1,2,3]”
const json = ‘{“result”:true, “count”:42}’;
const obj = JSON.parse(json);
 
Logger.debug(JSON.stringify(obj.count));
// Expected output: “42”
 
Logger.debug(JSON.stringify(obj.result));
// Expected output: “true”
Logger.debug(JSON.stringify({ x: 5, y: 6 }));
// Expected output: “{"x":5,"y":6}”
 
Logger.debug(JSON.stringify(Date.datetime('2006-01-02T15:04:05.000Z'));
// Expected output: "2006-01-02T15:04:05.000Z"
function difference(a, b) {
  return Math.abs(a - b);
}
 
Logger.debug(difference(3, 5));
// Expected output: 2
 
Logger.debug(difference(5, 3));
// Expected output: 2
 
Logger.debug(difference(1.23456, 7.89012));
// Expected output: 6.6555599999999995
// Calculates angle of a right-angle triangle in radians
function calcAngle(adjacent, hypotenuse) {
  return Math.acos(adjacent / hypotenuse);
}
 
Logger.debug(calcAngle(8, 10));
// Expected output: 0.6435011087932843
 
Logger.debug(calcAngle(5, 3));
// Expected output: NaN
Logger.debug(Math.acosh(0.999999999999));
// Expected output: NaN
 
Logger.debug(Math.acosh(1));
// Expected output: 0
 
Logger.debug(Math.acosh(2));
// Expected output: 1.3169578969248166
 
Logger.debug(Math.acosh(2.5));
// Expected output: 1.566799236972411
// Calculates angle of a right-angle triangle in radians
function calcAngle(opposite, hypotenuse) {
  return Math.asin(opposite / hypotenuse);
}
 
Logger.debug(calcAngle(6, 10));
// Expected output: 0.6435011087932844
 
Logger.debug(calcAngle(5, 3));
// Expected output: NaN
Logger.debug(Math.asinh(1));
// Expected output: 0.881373587019543
 
Logger.debug(Math.asinh(0));
// Expected output: 0
 
Logger.debug(Math.asinh(-1));
// Expected output: -0.881373587019543
 
Logger.debug(Math.asinh(2));
// Expected output: 1.4436354751788103
// Calculates angle of a right-angle triangle in radians
function calcAngle(opposite, adjacent) {
  return Math.atan(opposite / adjacent);
}
 
Logger.debug(calcAngle(8, 10));
// Expected output: 0.6747409422235527
 
Logger.debug(calcAngle(5, 3));
// Expected output: 1.0303768265243125
function calcAngleDegrees(x, y) {
  return (Math.atan2(y, x) * 180) / Math.PI;
}
 
Logger.debug(calcAngleDegrees(5, 5));
// Expected output: 45
 
Logger.debug(calcAngleDegrees(10, 10));
// Expected output: 45
 
Logger.debug(calcAngleDegrees(0, 10));
// Expected output: 90
Logger.debug(Math.atanh(-1));
// Expected output: -Infinity

Logger.debug(Math.atanh(0));
// Expected output: 0

Logger.debug(Math.atanh(0.5));
// Expected output: 0.549306144334055 (approximately)

Logger.debug(Math.atanh(1));
// Expected output: Infinity
Logger.debug(Math.cbrt(-1));
// Expected output: -1
 
Logger.debug(Math.cbrt(1));
// Expected output: 1
 
Logger.debug(Math.cbrt(Infinity));
// Expected output: Infinity
 
Logger.debug(Math.cbrt(64));
// Expected output: 4
Logger.debug(Math.ceil(0.95));
// Expected output: 1
 
Logger.debug(Math.ceil(4));
// Expected output: 4
 
Logger.debug(Math.ceil(7.004));
// Expected output: 8
 
Logger.debug(Math.ceil(-7.004));
// Expected output: -7
// 00000000000000000000000000000001
Logger.debug(Math.clz32(1));
// Expected output: 31
 
// 00000000000000000000000000000100
Logger.debug(Math.clz32(4));
// Expected output: 29
 
// 00000000000000000000001111101000
Logger.debug(Math.clz32(1000));
// Expected output: 22
function getCircleX(radians, radius) {
  return Math.cos(radians) * radius;
}
 
Logger.debug(getCircleX(1, 10));
// Expected output: 5.403023058681398
 
Logger.debug(getCircleX(2, 10));
// Expected output: -4.161468365471424
 
Logger.debug(getCircleX(Math.PI, 10));
// Expected output: -10
Logger.debug(Math.cosh(0));
// Expected output: 1
 
Logger.debug(Math.cosh(1));
// Expected output: 1.543080634815244 (approximately)
 
Logger.debug(Math.cosh(-1));
// Expected output: 1.543080634815244 (approximately)
 
Logger.debug(Math.cosh(2));
// Expected output: 3.7621956910836314
Logger.debug(Math.exp(0));
// Expected output: 1
 
Logger.debug(Math.exp(1));
// Expected output: 2.718281828459 (approximately)
 
Logger.debug(Math.exp(-1));
// Expected output: 0.36787944117144233
 
Logger.debug(Math.exp(2));
// Expected output: 7.38905609893065
Logger.debug(Math.expm1(0));
// Expected output: 0
 
Logger.debug(Math.expm1(1));
// Expected output: 1.718281828459045
 
Logger.debug(Math.expm1(-1));
// Expected output: -0.6321205588285577
 
Logger.debug(Math.expm1(2));
// Expected output: 6.38905609893065
Logger.debug(Math.floor(5.95));
// Expected output: 5
 
Logger.debug(Math.floor(5.05));
// Expected output: 5
 
Logger.debug(Math.floor(5));
// Expected output: 5
 
Logger.debug(Math.floor(-5.05));
// Expected output: -6
Logger.debug(Math.fround(5.5));
// Expected output: 5.5
 
Logger.debug(Math.fround(5.05));
// Expected output: 5.050000190734863
 
Logger.debug(Math.fround(5));
// Expected output: 5
 
Logger.debug(Math.fround(-5.05));
// Expected output: -5.050000190734863
Logger.debug(Math.hypot(3, 4));
// Expected output: 5
 
Logger.debug(Math.hypot(5, 12));
// Expected output: 13
 
Logger.debug(Math.hypot(3, 4, 5));
// Expected output: 7.0710678118654755
 
Logger.debug(Math.hypot(-5));
// Expected output: 5
Logger.debug(Math.imul(3, 4));
// Expected output: 12
 
Logger.debug(Math.imul(-5, 12));
// Expected output: -60
 
Logger.debug(Math.imul(0xffffffff, 5));
// Expected output: -5
 
Logger.debug(Math.imul(0xfffffffe, 5));
// Expected output: -10
function getBaseLog(x, y) {
  return Math.log(y) / Math.log(x);
}
 
// 2 x 2 x 2 = 8
Logger.debug(getBaseLog(2, 8));
// Expected output: 3
 
// 5 x 5 x 5 x 5 = 625
Logger.debug(getBaseLog(5, 625));
// Expected output: 4
Logger.debug(Math.log10(100000));
// Expected output: 5
 
Logger.debug(Math.log10(2));
// Expected output: 0.3010299956639812
 
Logger.debug(Math.log10(1));
// Expected output: 0
 
Logger.debug(Math.log10(0));
// Expected output: -Infinity
Logger.debug(Math.log1p(1));
// Expected output: 0.6931471805599453
 
Logger.debug(Math.log1p(0));
// Expected output: 0
 
Logger.debug(Math.log1p(-1));
// Expected output: -Infinity
 
Logger.debug(Math.log1p(-2));
// Expected output: NaN
Logger.debug(Math.log2(3));
// Expected output: 1.584962500721156
 
Logger.debug(Math.log2(2));
// Expected output: 1
 
Logger.debug(Math.log2(1));
// Expected output: 0
 
Logger.debug(Math.log2(0));
// Expected output: -Infinity
Logger.debug(Math.max(1, 3, 2));
// Expected output: 3
 
Logger.debug(Math.max(-1, -3, -2));
// Expected output: -1
Logger.debug(Math.min(2, 3, 1));
// Expected output: 1
 
Logger.debug(Math.min(-2, -3, -1));
// Expected output: -3
Logger.debug(Math.pow(7, 3));
// Expected output: 343
 
Logger.debug(Math.pow(4, 0.5));
// Expected output: 2
 
Logger.debug(Math.pow(7, -2));
// Expected output: 0.02040816326530612
// (1/49)
 
Logger.debug(Math.pow(-7, 0.5));
// Expected output: NaN
function getRandomInt(max) {
  return Math.floor(Math.random() * max);
}
 
Logger.debug(getRandomInt(3));
// Expected output: 0, 1 or 2
 
Logger.debug(getRandomInt(1));
// Expected output: 0
 
Logger.debug(Math.random());
// Expected output: a number from 0 to <1
Logger.debug(Math.round(0.9));
// Expected output: 1
 
Logger.debug(Math.round(5.95), Math.round(5.5), Math.round(5.05));
// Expected output: 6 6 5
 
Logger.debug(Math.round(-5.05), Math.round(-5.5), Math.round(-5.95));
// Expected output: -5 -5 -6
Logger.debug(Math.sign(3));
// Expected output: 1
 
Logger.debug(Math.sign(-3));
// Expected output: -1
 
Logger.debug(Math.sign(0));
// Expected output: 0
 
Logger.debug(Math.sign('-3'));
// Expected output: -1
function getCircleY(radians, radius) {
  return Math.sin(radians) * radius;
}
 
Logger.debug(getCircleY(1, 10));
// Expected output: 8.414709848078965
 
Logger.debug(getCircleY(2, 10));
// Expected output: 9.092974268256818
 
Logger.debug(getCircleY(Math.PI, 10));
// Expected output: 1.2246467991473533e-15
Logger.debug(Math.sinh(0));
// Expected output: 0
 
Logger.debug(Math.sinh(1));
// Expected output: 1.1752011936438014
 
Logger.debug(Math.sinh(-1));
// Expected output: -1.1752011936438014
 
Logger.debug(Math.sinh(2));
// Expected output: 3.626860407847019
function calcHypotenuse(a, b) {
  return Math.sqrt(a * a + b * b);
}
 
Logger.debug(calcHypotenuse(3, 4));
// Expected output: 5
 
Logger.debug(calcHypotenuse(5, 12));
// Expected output: 13
 
Logger.debug(calcHypotenuse(0, 0));
// Expected output: 0
function getTanFromDegrees(degrees) {
  return Math.tan((degrees * Math.PI) / 180);
}
 
Logger.debug(getTanFromDegrees(0));
// Expected output: 0
 
Logger.debug(getTanFromDegrees(45));
// Expected output: 0.9999999999999999
 
Logger.debug(getTanFromDegrees(90));
// Expected output: 16331239353195370
Logger.debug(Math.tanh(-1));
// Expected output: -0.7615941559557649
 
Logger.debug(Math.tanh(0));
// Expected output: 0
 
Logger.debug(Math.tanh(Infinity));
// Expected output: 1
 
Logger.debug(Math.tanh(1));
// Expected output: 0.7615941559557649
Logger.debug(Math.trunc(13.37));
// Expected output: 13
 
Logger.debug(Math.trunc(42.84));
// Expected output: 42
 
Logger.debug(Math.trunc(0.123));
// Expected output: 0
 
Logger.debug(Math.trunc(-0.123));
// Expected output: -0
Logger.debug(Number.isFinite(1 / 0));
// Expected output: false
 
Logger.debug(Number.isFinite(10 / 5));
// Expected output: true
 
Logger.debug(Number.isFinite(0 / 0));
// Expected output: false
function fits(x, y) {
  if (Number.isInteger(y / x)) {
    return 'Fits!';
  }
  return 'Does NOT fit!';
}
 
Logger.debug(fits(5, 10));
// Expected output: "Fits!"
 
Logger.debug(fits(5, 11));
// Expected output: "Does NOT fit!"
function typeOfNaN(x) {
  if (Number.isNaN(x)) {
    return 'Number NaN';
  }
}
 
Logger.debug(typeOfNaN('100F'));
// Expected output: "NaN"
function warn(x) {
  if (Number.isSafeInteger(x)) {
    return 'Precision safe.';
  }
  return 'Precision may be lost!';
}
 
Logger.debug(warn(Math.pow(2, 53)));
// Expected output: "Precision may be lost!"
 
Logger.debug(warn(Math.pow(2, 53) - 1));
// Expected output: "Precision safe."
function circumference(r) {
  if (Number.isNaN(Number.parseFloat(r))) {
    return 0;
  }
  return Number.parseFloat(r) * 2.0 * Math.PI;
}
 
Logger.debug(circumference('4.567abcdefgh'));
// Expected output: 28.695307297889173
 
Logger.debug(circumference('abcdefgh'));
// Expected output: 0
function roughScale(x, base) {
  const parsed = Number.parseInt(x, base);
  if (Number.isNaN(parsed)) {
    return 0;
  }
  return parsed * 100;
}
 
Logger.debug(roughScale(' 0xF', 16));
// Expected output: 1500
 
Logger.debug(roughScale('321', 2));
// Expected output: 0
Logger.debug(Util.randomId('base64'));
// Expected output format: yC8tpqBMl2j3zxp7tsO3zdHsLcRCY1wyU44Djc5pCWI=
 
Logger.debug(Util.randomId('base64', 5));
// Expected output format: 1GRl4lI=

Logger.debug(Util.randomId('base64url'));
// Expected output format: lRnhwySDycxXBYz2C2q_MPyPKP51FExtyiJAwurz47Q=
 
Logger.debug(Util.randomId('base32'));
// Expected output format: C4GZRB7LHO4GXMV7HFR7263FVHVHPKQNQAVHWXUOHLZJJTOGOXWQ====

Logger.debug(Util.randomId('base32hex'));
// Expected output format: 72Q94N0LGT4FULVV87G0VCUJOOBLCPOJU0SO2TLN1B647501SIM0====

Logger.debug(Util.randomId('base16'));
// Expected output format: 31302C51572B49743F366A67294C785152267D3566555859643753446C35614C

Logger.debug(Util.randomId('Z85'));
// Expected output format: @YD$z&i[oD%i/M:@3CO(P<8[(OSer)[ge]cK[uia
Logger.debug(Util.uuidv4());
// Expected output format: b591fc9a-3c5d-43b1-9dfd-246a1e5d4941
custom fields and sections
vulnerability SLAs
custom vulnerability parsing
custom emails
APIs
JavaScript
JavaScript
JavaScript basics
Math
String
Date.datetime
arrow functions
anonymous functions
Classes
try…catch
Symbol
new operator
for…of
for…in
spread syntax
switch
import
export
this
Regular Expressions
Built-in Functions
Logging
Date.datetime()
Date.datetime()
Custom Fields & Forms
Date.datetime()
Date.datetime()
base64
base64url
base32
base32hex
base16
Z85
SHA1
SHA224
SHA256
SHA384
SHA512
MD5
RMD160
base64
base64url
base32
base32hex
base16
Z85
base64
base64url
base32
base32hex
base16
Z85
endsWith
length
String
padEnd
padStart
replace
RegExp
replaceAll
RegExp
slice
startsWith
substring
toLowerCase
toUpperCase
trim
trimStart()
trimEnd()
trimEnd
trimStart
at
concat
every
find
undefined
findIndex()
indexOf()
findIndex()
includes()
some()
findIndex
find()
flat
flatMap
includes
indexOf
isArray
Array
join
lastIndexOf
length
map
pop
push
reduce
reduceRight
reverse
in place
shift
slice
shallow copy
some
sort
in place
splice
in place
slice()
unshift
parse
stringify
abs
acos
acosh
asin
asinh
atan
atan2
atanh
cbrt
ceil
clz32
cos
cosh
exp
e
expm1
e
floor
fround
32-bit single precision
hypot
imul
log
e
log10
log1p
e
log2
max
Infinity
min
Infinity
pow
random
round
sign
sin
sinh
sqrt
tan
tanh
trunc
isFinite
Infinity
NaN
isInteger
isNaN
NaN
isNaN()
isSafeInteger
parseFloat
NaN
parseInt
base64
base64url
base32
base32hex
base16
Z85
Universally Unique IDentifier (UUID)