For the complete documentation index, see llms.txt. This page is also available as Markdown.

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.

Writing AFScript

AFScript is a sandboxed subset of JavaScript. A script is a program: top-level statements run in order, and whatever it returns is the result. It gets let/const, functions, closures, arrows, template strings, destructuring, spread and a curated standard library. It gets no path into the host runtime — no require, no eval, no Function, no timers, no network, no filesystem, no console, and no reachable JavaScript prototypes.

If you already know JavaScript you know 90% of AFScript. The remaining 10% is where every mistake lives, so read the next two sections before writing a line.

The one rule: no method call syntax

a.b is a property read, never a method lookup. There is no a.b() call form for anything the value did not itself put there.

Reading a name the value owns works exactly as in JavaScript:

Reading a name that only a prototype would have supplied throws. It does not return undefined — it ends the script:

length is the one exception worth knowing: it is a genuine own property of an array and of a string, so items.length and s.length do read. Use Array.length(items) and String.length(s) anyway — they are the documented API, and they check their receiver, where .length silently answers undefined on anything that is not an array or a string.

Built-ins live on namespace objects, and the receiver is the first argument:

The mechanical rule. Before you write a dot, ask what is on the left.

  • About to write x.foo(a, b) → write Namespace.foo(x, a, b).

  • About to write x.length → write Array.length(x) or String.length(x).

  • Left side is a value you built (an object literal, a host global, a parsed JSON result) and the name is one you put there → the dot is correct, leave it.

The ten namespaces are Array, String, Object, JSON, XML, Date, Math, Number, Util, Logger. All ten are const — readable, never replaceable. Infinity and NaN are the only bare globals the language defines.

JavaScript → AFScript

JavaScript
AFScript

xs.length

Array.length(xs).length does read, but is unchecked; prefer this

s.length

String.length(s)

xs.map(f) · .filter · .reduce · .find · .some · .every · .sort · .join · .includes · .slice · .push

Array.map(xs, f), Array.filter(xs, f), …

s.trim() · .split · .replace · .toUpperCase · .startsWith · .padStart

String.trim(s), String.split(s, ','), …

'' + n or `${n}` used as a coercion

String.from(n)String.concat refuses a non-string

Object.keys(o) · entries · values

same — but Object.keys('abc') throws

Object.assign · freeze · fromEntries · hasOwn

absent — use { ...a, ...b } in place of assign

JSON.parse · JSON.stringify

same — JSON.parse answers undefined on bad input, it does not throw

new Date() · d.toISOString()

Date.datetime('now') — there is no Date value type

/re/.test(s)

s =~ m/re/

s.match(/re/g)

String.match(s, m/re/g) — answers a plain array of strings, or null

let a = 1, b = 2;

two statements — one binding per declaration

x => ({ a: 1 })

same — { after => starts a block, so an object literal must be parenthesised

({ a }) => a

same — a destructuring arrow parameter must be parenthesised

[...a, ...b] · { ...o } · f(...args)

same — spread works in all three places

for (const x of xs)

same — but only over an array or a string

'k' in obj

obj.k !== undefined — there is no in operator

console.log(x)

Logger.info(x) — wrap objects in JSON.stringify first

try { … } catch { … }

no equivalent — validate before you act

class · new · async · await · yield · var · instanceof · void

no equivalent

What the language has

Present:

  • let/const and block scoping;

  • hoisted function declarations;

  • arrow functions;

  • real closures;

  • parameter defaults;

  • destructuring in declarations, assignment, parameters and for…of/for…in heads (with defaults, rest, nesting, renamed and computed keys);

  • spread in array literals, object literals and call arguments;

  • shorthand properties;

  • template strings with ${};

  • if/else, while, do…while, C-style for, for…of, for…in, switch with fall-through, break, continue, return;

  • arithmetic including **, bitwise ops, shifts, comparison, ===/!==, &&, ||, ??, ternary, typeof, delete, pre/post ++/--, compound assignment (+=, ??=, …);

  • optional access a?.b and a?[k] (no dot before the bracket);

  • regex literals m/pattern/flags matched with =~;

  • numbers in decimal, hex, octal and binary, plus their BigInt forms;

  • // and /* */ comments;

Absent:

  • class/extends/super; new; try/catch/finally/throw; async/await and promises;

  • generators and yield;

  • var;

  • this;

  • arguments;

  • instanceof;

  • in as a binary operator;

  • void;

  • the comma operator and multi-declarator let a = 1, b = 2 (so no for (let i = 0, j = 0; …) either);

  • labelled statements and break label;

  • tagged templates and String.raw;

  • /re/ literal syntax;

  • optional call a?.();

  • getters, setters and object method shorthand { foo() {} };

  • a trailing comma in a parameter list (allowed in call arguments, literals and patterns);

  • iterators and Symbol.iterator;

  • import/export/require;

  • eval;

  • Function;

  • timers;

  • I/O;

  • console;

  • Map, Set, Symbol, Promise, typed arrays;

  • RegExp, Date and Error object types;

Values are checked, never coerced

Almost every built-in validates what it is handed and throws rather than guessing. This is the second-biggest source of surprise after method calls.

Convert explicitly. String.from is the one member that exists to coerce, and the one that never throws:

The exceptions: Array.isArray, all of Number, and all of Math except secureRandom and secureRandomInt are host intrinsics passed straight through, so they coerce exactly as JavaScript does.

Examples

for…of and for…in

for…of walks an array's elements or a string's characters, and throws on anything else. Strings are walked by code point, so an astral character is one iteration rather than a surrogate pair.

for…in walks own enumerable keys as strings — an object's names, an array's or string's index names, and nothing at all for null, undefined or a primitive.

for…of gives the loop variable a fresh binding each iteration, so a function made in the body captures that iteration's value. A C-style for header keeps its single binding. Both match JavaScript, and the difference is worth knowing:

Arrow functions

An expression that evaluates to a function, so a callback no longer has to be hoisted out into a named declaration.

An expression body is the result outright; a block body needs return. x => {} is an empty block that answers undefined, not an object literal. function f() {} — an empty body on a declaration — is now legal too.

Template string literals

Interpolations hold any expression, nest, and coerce their value exactly as + would. Escapes \`` and $` are available for a literal backtick or dollar. Tagged templates are not included.

Switch

=== matching, fall-through until a break, and default honoured wherever it sits in the clause list. The whole body shares one block scope.

Spread and shorthand properties

Spread works in the three places JavaScript puts it — array literals, object literals and call arguments — and object literals gained { x, y } shorthand.

What may be spread is decided by type, not by asking the value for an iterator: arrays and strings for the array and argument forms, own enumerable keys for the object form. A function value cannot be spread at all.

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”

format

The Date.format(“timeValue”, “mask”, "timezone") function can be used to convert a date and time to a specified mask and optional timezone.

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"

mask - must be either:

Named formats

  • default

    • ddd mmm dd yyyy HH:MM:ss

    • Sat Jun 09 2007 17:46:21

  • shortDate

    • m/d/yy

    • 6/9/07

  • paddedShortDate

    • mm/dd/yyyy

    • 06/09/2007

  • mediumDate

    • mmm d, yyyy

    • Jun 9, 2007

  • longDate

    • mmmm d, yyyy

    • June 9, 2007

  • fullDate

    • dddd, mmmm d, yyyy

    • Saturday, June 9, 2007

  • shortTime

    • h:MM TT

    • 5:46 PM

  • mediumTime

    • h:MM:ss TT

    • 5:46:21 PM

  • longTime

    • h:MM:ss TT Z

    • 5:46:21 PM EST

  • isoDate

    • yyyy-mm-dd

    • 2007-06-09

  • isoTime

    • HH:MM:ss

    • 17:46:21

  • isoDateTime

    • yyyy-mm-dd'T'HH:MM:sso

    • 2007-06-09T17:46:21+0700

  • isoUtcDateTime

    • UTC:yyyy-mm-dd'T'HH:MM:ss'Z'

    • 2007-06-09T22:46:21Z

Mask options

  • d

    • Day of the month as digits; no leading zero for single-digit days.

  • dd

    • Day of the month as digits; leading zero for single-digit days.

  • ddd

    • Day of the week as a three-letter abbreviation.

  • DDD

    • "Ysd", "Tdy" or "Tmw" if date lies within these three days. Else fall back to ddd.

  • dddd

    • Day of the week as its full name.

  • DDDD

    • "Yesterday", "Today" or "Tomorrow" if date lies within these three days. Else fall back to dddd.

  • m

    • Month as digits; no leading zero for single-digit months.

  • mm

    • Month as digits; leading zero for single-digit months.

  • mmm

    • Month as a three-letter abbreviation.

  • mmmm

    • Month as its full name.

  • yy

    • Year as last two digits; leading zero for years less than 10.

  • yyyy

    • Year represented by four digits.

  • h

    • Hours; no leading zero for single-digit hours (12-hour clock).

  • hh

    • Hours; leading zero for single-digit hours (12-hour clock).

  • H

    • Hours; no leading zero for single-digit hours (24-hour clock).

  • HH

    • Hours; leading zero for single-digit hours (24-hour clock).

  • M

    • Minutes; no leading zero for single-digit minutes.

  • MM

    • Minutes; leading zero for single-digit minutes.

  • N

    • ISO 8601 numeric representation of the day of the week.

  • o

    • GMT/UTC timezone offset, e.g. -0500 or +0230.

  • p

    • GMT/UTC timezone offset, e.g. -05:00 or +02:30.0

  • s

    • Seconds; no leading zero for single-digit seconds.

  • ss

    • Seconds; leading zero for single-digit seconds.

  • S

    • The date's ordinal suffix (st, nd, rd, or th). Works well with d.

  • l

    • Milliseconds; gives 3 digits.

  • L

    • Milliseconds; gives 2 digits.

  • t

    • Lowercase, single-character time marker string: a or p.

  • tt

    • Lowercase, two-character time marker string: am or pm.

  • T

    • Uppercase, single-character time marker string: A or P.

  • TT

    • Uppercase, two-character time marker string: AM or PM.

  • W

    • ISO 8601 week number of the year, e.g. 4, 42

  • WW

    • ISO 8601 week number of the year, leading zero for single-digit, e.g. 04, 42

  • Z

    • US timezone abbreviation, e.g. EST or MDT. For non-US timezones, the GMT/UTC offset is returned, e.g. GMT-0500

  • '...', "..."

    • Literal character sequence. Surrounding quotes are removed.

  • UTC

    • Must be the first four characters of the mask. Converts the date from local time to UTC/GMT/Zulu time before applying the mask. The "UTC:" prefix is removed.

timezone - must be a canonical timezone

Strings

Function
Description
Example

The String.at() method takes an integer value and returns a new String consisting of the single UTF-16 code unit located at the specified offset. This method allows for positive and negative integers. Negative integers count back from the last string character.

The String.charAt() method returns a new string consisting of the single UTF-16 code unit at the given index.

String.charAt() always indexes the string as a sequence of UTF-16 code units, so it may return lone surrogates.

The String.concat() method concatenates the string arguments to this string and returns a new string.

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.

from

The String.from() method returns a string representing the primitive or object.

hmac

The String.hmac() method is used to create an HMAC string that uses the stated 'algorithm' and 'secret'.

The following hashing algorithms are supported:

The following encodings are supported:

The String.includes() method performs a case-sensitive search to determine whether a given string may be found within this string, returning true or false as appropriate.

The String.indexOf() method searches this string and returns the index of the first occurrence of the specified substring. It takes an optional starting position and returns the first occurrence of the specified substring at an index greater than or equal to the specified number.

The String.lastIndexOf() method searches this string and returns the index of the last occurrence of the specified substring. It takes an optional starting position and returns the last occurrence of the specified substring at an index less than or equal to the specified number.

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

The String.match() method of String values retrieves the result of matching this string against a regular expression.

The String.matchAll() method of String values returns an iterator of all results matching this string against a regular expression, including capturing groups.

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.repeat() method constructs and returns a new string which contains the specified number of copies of this string, concatenated together.

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 RegExp, 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 RegExp, and the replacement can be a string or a function to be called for each match. The original string is left unchanged.

The String.search() method executes a search for a match between a regular expression and this string, returning the index of the first match in the string.

sign / verify

The String.sign() method is used to cryptographically sign a message using a private key for signing, which can be later verified with a public key using the String.verify() method.

The following hashing algorithms are supported:

The following encodings are supported:

The String.slice() method extracts a section of this string and returns it as a new string, without modifying the original string.

The String.split() method takes a pattern and divides this string into an ordered list of substrings by searching for the pattern, puts these substrings into an array, and returns the array.

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.

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

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.entries() method returns a new array iterator object that contains the key/value pairs for each index in the 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.fill() method changes all elements within a range of indices in an array to a static value. It returns the modified array.

The Array.filter() method creates a shallow copy of a portion of a given array, filtered down to just the elements from the given array that pass the test implemented by the provided function.

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, undefined is returned.

  • If you need the index of the found element in the array, use findIndex().

  • If you need to find the index of a value, use indexOf(). (It's similar to findIndex(), 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 includes(). 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 some().

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.

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

The Array.findLast() method iterates the array in reverse order and returns the value of the first element that satisfies the provided testing function. If no elements satisfy the testing function, undefined is returned.

The Array.findLastIndex() method iterates the array in reverse order and returns the index of the first element 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.forEach() method executes a provided function once for each array element.

The Array.from() static method creates a new, shallow-copied Array instance from an iterable or array-like object.

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.isArray() static method determines whether the passed value is an Array.

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.keys() method returns a new array iterator object that contains the keys for each index in the array.

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.of() static method creates a new Array instance from a variable number of arguments, regardless of number or type of the arguments.

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.reverse() method reverses an array in place 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.shift() method removes the first element from an array and returns that removed element. This method changes the length of the array.

The Array.slice() method returns a shallow copy 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.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.sort() method sorts the elements of an array in place 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 in place.

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

The Array.unshift() method adds the specified elements to the beginning of an array and returns the new length of the array.

The Array.values() method returns a new array iterator object that iterates the value of each item in the array.

Objects

Function
Description
Example

The Object.entries() static method returns an array of a given object's own enumerable string-keyed property key-value pairs.

The Object.keys() static method returns an array of a given object's own enumerable string-keyed property names.

The Object.values() static method returns an array of a given object's own enumerable string-keyed property values.

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.

XML

Function
Description
Example

The XML.parse() static method parses a XML string, constructing the JSON value or object described by the string.

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.exp() static method returns e raised to the power of a number.

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

The Math.floor() static method always rounds down and returns the largest integer less than or equal to a given number.

The Math.fround() static method returns the nearest 32-bit single precision float representation of a 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.log() static method returns the natural logarithm (base e) of a number.

The Math.log10() static method returns the base 10 logarithm of a number.

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

The Math.log2() static method returns the base 2 logarithm of a number.

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

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

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.

secureRandom

The Math.secureRandom() static method returns a floating-point, random number generated using a CSPRNG (Cryptographically Secure Pseudo-Random Number Generator) 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.

secureRandomInt

The Math.secureRandomInt() static method returns an integer in [min, max], inclusive at both ends, random number generated using a CSPRNG (Cryptographically Secure Pseudo-Random Number Generator).

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.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 Infinity, negative Infinity, nor NaN.

The Number.isInteger() static method determines whether the passed value is an integer.

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

The Number.isSafeInteger() static method determines whether the provided value is a number that is a safe integer.

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

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

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

A UUID is 128 bits long, and can guarantee uniqueness across space and time.

Regular Expressions

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

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

Workflow Automation and Custom Integrations

You can use AFScript in your Actions using Flows.

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

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

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

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

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:

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:

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

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

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

Selecting the customer and testing types:

Suggested project code:

Standard library reference

Every built-in AFScript offers is a member of a namespace object in the global scope — Array, String, Object, JSON, XML, Date, Math, Number, Util and Logger — plus the two bare globals Infinity and NaN. All ten namespaces are const: a script can read them, and cannot replace them.

There is no method call syntax in AFScript, so nothing here is invoked on a value. The receiver is the first argument:

Array.isArray, Array.of and String.from are the exceptions — they are statics in JavaScript too, and take no receiver.

Array

Thirty-five members. Every one but Array.isArray and Array.of takes the array as its first argument and throws if it is handed anything else — there is no array-like coercion anywhere in this namespace. The members that mutate their receiver (fill, pop, push, reverse, shift, sort, splice, unshift) mutate it in place and answer what their JavaScript counterparts answer.

Array.entries, Array.keys and Array.values answer arrays, not iterators: AFScript has no iterator protocol, so an iterator would be a value no script could consume. for…of walks an array or a string, so a pair is taken apart by destructuring the header.

Array.at(array, index)

Answers the element at index, counting from the end when index is negative.

Parameter
Type
Notes

array

array

the receiver

index

number

negative counts back from the end

Returns the element, or undefined when index is out of range.

Throws if array is not an array, or index is not a number.

Array.concat(array, ...values)

Answers a new array holding array's elements followed by each of values; an argument that is itself an array is spread one level deep.

Parameter
Type
Notes

array

array

the receiver

...values

any

arrays are flattened one level

Returns a new array. array is unchanged.

Throws if array is not an array.

Array.entries(array)

Answers the index/element pairs.

Parameter
Type
Notes

array

array

the receiver

Returns an array of two-element [index, element] arrays.

Throws if array is not an array.

Differs from JavaScript, which answers an iterator. The array it answers walks the same way, and the pair destructures:

Array.every(array, callback)

Answers whether callback is truthy for every element. Stops at the first falsy answer.

Parameter
Type
Notes

array

array

the receiver

callback

function (element, index, array)

Returns a boolean. An empty array answers true.

Throws if array is not an array, or callback is not a function.

Array.fill(array, value, start?, end?)

Overwrites the elements from start up to but not including end with value, in place.

Parameter
Type
Notes

array

array

the receiver; mutated

value

any

written into every slot in range

start?

number

integer; defaults to 0, negative counts from the end

end?

number

integer; defaults to the array's length

Returns the same array.

Throws if array is not an array, or start/end is present and not an integer.

Array.filter(array, callback)

Answers a new array of the elements for which callback is truthy.

Parameter
Type
Notes

array

array

the receiver

callback

function (element, index, array)

Returns a new array, possibly empty. array is unchanged.

Throws if array is not an array, or callback is not a function.

Array.find(array, callback)

Answers the first element for which callback is truthy.

Parameter
Type
Notes

array

array

the receiver

callback

function (element, index, array)

Returns the element, or undefined if none matches.

Throws if array is not an array, or callback is not a function.

Array.findIndex(array, callback)

Answers the index of the first element for which callback is truthy.

Parameter
Type
Notes

array

array

the receiver

callback

function (element, index, array)

Returns the index, or -1 if none matches.

Throws if array is not an array, or callback is not a function.

Array.findLast(array, callback)

Answers the last element for which callback is truthy, searching from the end.

Parameter
Type
Notes

array

array

the receiver

callback

function (element, index, array)

Returns the element, or undefined if none matches.

Throws if array is not an array, or callback is not a function.

Array.findLastIndex(array, callback)

Answers the index of the last element for which callback is truthy.

Parameter
Type
Notes

array

array

the receiver

callback

function (element, index, array)

Returns the index, or -1 if none matches.

Throws if array is not an array, or callback is not a function.

Array.flat(array, depth?)

Answers a new array with nested arrays spliced into it, down to depth levels.

Parameter
Type
Notes

array

array

the receiver

depth?

number

defaults to 1; pass Infinity to flatten completely

Returns a new array. array is unchanged.

Throws if array is not an array, or depth is present and not a number.

Array.flatMap(array, callback)

Maps every element and flattens the result by one level — Array.map followed by Array.flat(_, 1), in a single pass.

Parameter
Type
Notes

array

array

the receiver

callback

function (element, index, array)

may answer a value or an array

Returns a new array. array is unchanged.

Throws if array is not an array, or callback is not a function.

Array.forEach(array, callback)

Calls callback once for each element, for its effect.

Parameter
Type
Notes

array

array

the receiver

callback

function (element, index, array)

Returns undefined, deliberately — use Array.map when you want the answers.

Throws if array is not an array, or callback is not a function.

Array.from(value, callback?)

Copies an array, or splits a string into its characters.

Parameter
Type
Notes

value

array or string

a string splits by code point

callback?

function (element, index)

maps each element on the way out

Returns a new array.

Throws if value is neither an array nor a string, or callback is present and not a function.

Differs from JavaScript, which answers [] for anything it cannot iterate — Array.from(5) throws here rather than answering an empty array, because a silent empty array is the harder thing to debug. Array-like objects are not accepted. Splitting by code point means an astral character is one element, where String.length would count two UTF-16 units.

Array.includes(array, searchElement, fromIndex?)

Answers whether searchElement is in array, compared with === (except that NaN matches NaN).

Parameter
Type
Notes

array

array

the receiver

searchElement

any

fromIndex?

number

integer; defaults to 0, negative counts from the end

Returns a boolean.

Throws if array is not an array, or fromIndex is present and not an integer.

Array.indexOf(array, searchElement, fromIndex?)

Answers the first index at which searchElement is found, compared with ===.

Parameter
Type
Notes

array

array

the receiver

searchElement

any

fromIndex?

number

integer; defaults to 0, negative counts from the end

Returns the index, or -1.

Throws if array is not an array, or fromIndex is present and not an integer.

Array.isArray(value)

Answers whether value is an array. A static — there is no receiver, and nothing to throw on.

Parameter
Type
Notes

value

any

Returns a boolean.

Array.join(array, separator?)

Concatenates the elements into a string.

Parameter
Type
Notes

array

array

the receiver

separator?

string

defaults to ','

Returns a string. undefined and null elements become the empty string.

Throws if array is not an array, or separator is present and not a string.

Array.keys(array)

Answers the indices.

Parameter
Type
Notes

array

array

the receiver

Returns an array of numbers.

Throws if array is not an array.

Differs from JavaScript, which answers an iterator.

Array.lastIndexOf(array, searchElement, fromIndex?)

Answers the last index at which searchElement is found, searching backwards.

Parameter
Type
Notes

array

array

the receiver

searchElement

any

fromIndex?

number

integer; defaults to the last index

Returns the index, or -1.

Throws if array is not an array, or fromIndex is present and not an integer.

Array.length(array)

Answers the number of elements — AFScript has no property read for it.

Parameter
Type
Notes

array

array

the receiver

Returns a number.

Throws if array is not an array.

Array.map(array, callback)

Answers a new array of callback's answer for each element.

Parameter
Type
Notes

array

array

the receiver

callback

function (element, index, array)

Returns a new array of the same length. array is unchanged.

Throws if array is not an array, or callback is not a function.

Array.of(...values)

Answers an array of its arguments. A static, like its JavaScript original — there is no receiver.

Parameter
Type
Notes

...values

any

every argument becomes one element

Returns a new array.

Differs from JavaScript's new Array(7), not from Array.of(7): this answers a one-element array holding 7, never an array of length seven.

Array.pop(array)

Removes the last element, in place.

Parameter
Type
Notes

array

array

the receiver; mutated

Returns the removed element, or undefined if the array was empty.

Throws if array is not an array.

Array.push(array, ...values)

Appends values, in place.

Parameter
Type
Notes

array

array

the receiver; mutated

...values

any

appended in order

Returns the array's new length.

Throws if array is not an array.

Array.reduce(array, callback, initialValue?)

Folds the array left to right into a single value.

Parameter
Type
Notes

array

array

the receiver

callback

function (accumulator, element, index, array)

initialValue?

any

when omitted, the first element seeds the fold

Returns the final accumulator.

Throws if array is not an array, or callback is not a function. An empty array with no initialValue throws from the host.

Note. An initialValue of undefined is indistinguishable from omitting it, so a fold cannot be seeded with undefined.

Array.reduceRight(array, callback, initialValue?)

Folds the array right to left.

Parameter
Type
Notes

array

array

the receiver

callback

function (accumulator, element, index, array)

initialValue?

any

when omitted, the last element seeds the fold

Returns the final accumulator.

Throws if array is not an array, or callback is not a function.

Array.reverse(array)

Reverses the order of the elements, in place.

Parameter
Type
Notes

array

array

the receiver; mutated

Returns the same array.

Throws if array is not an array.

Array.shift(array)

Removes the first element, in place.

Parameter
Type
Notes

array

array

the receiver; mutated

Returns the removed element, or undefined if the array was empty.

Throws if array is not an array.

Array.slice(array, start?, end?)

Answers a shallow copy of the elements from start up to but not including end.

Parameter
Type
Notes

array

array

the receiver

start?

number

defaults to 0, negative counts from the end

end?

number

defaults to the array's length

Returns a new array. array is unchanged.

Throws if array is not an array.

Note. This member counts its arguments, so Array.slice(xs), Array.slice(xs, undefined) and Array.slice(xs, undefined, undefined) are three different calls — pass real indices rather than undefined placeholders.

Array.some(array, callback)

Answers whether callback is truthy for at least one element. Stops at the first truthy answer.

Parameter
Type
Notes

array

array

the receiver

callback

function (element, index, array)

Returns a boolean. An empty array answers false.

Throws if array is not an array, or callback is not a function.

Array.sort(array, compare?)

Sorts the elements in place.

Parameter
Type
Notes

array

array

the receiver; mutated

compare?

function (a, b)

must declare exactly two parameters

Returns the same array, so a sort can be written inline in a larger expression.

Throws if array is not an array, or compare is given and is not a two-parameter function. Omitting compare sorts by string comparison, as in JavaScript.

Array.splice(array, start, deleteCount?, ...items)

Removes deleteCount elements from start and inserts items there, in place.

Parameter
Type
Notes

array

array

the receiver; mutated

start

number

negative counts from the end

deleteCount?

number

when omitted, everything from start is removed

...items

any

inserted at start

Returns an array of the removed elements.

Throws if array is not an array.

Note. Like Array.slice, this counts its arguments: Array.splice(xs, 1) removes the tail, while Array.splice(xs, 1, undefined) removes nothing.

Array.unshift(array, ...elements)

Inserts elements at the front, in place.

Parameter
Type
Notes

array

array

the receiver; mutated

...elements

any

inserted in the order given

Returns the array's new length.

Throws if array is not an array.

Array.values(array)

Answers a copy of the elements.

Parameter
Type
Notes

array

array

the receiver

Returns a new array.

Throws if array is not an array.

Differs from JavaScript, which answers an iterator.

String

Thirty-two members: twenty-six for text, and six for encoding, hashing and signing. Every one takes the string as its first argument except String.from, which is the namespace's coercion function and takes the value to convert.

Nothing here coerces. String.concat('n: ', 1) throws where JavaScript would quietly answer 'n: 1' — write String.concat('n: ', String.from(1)) when that is what you mean.

The locale-dependent members (localeCompare, toLocaleLowerCase, toLocaleUpperCase) are deliberately absent: their answers vary with the host's ICU build, which makes a script's behaviour depend on where it runs.

Members that take a patternmatch, matchAll, search, split, replace, replaceAll — accept a regular-expression literal (m/…/flags) or a plain string. A string pattern is screened with safe-regex for catastrophic backtracking before it is compiled, and a fresh RegExp is built for every use because lastIndex is observable.

Encoding formats and hash algorithms are listed in the appendices.

String.at(str, index)

Answers the UTF-16 code unit at index, counting from the end when negative.

Parameter
Type
Notes

str

string

the receiver

index

number

must be an integer

Returns a one-character string, or undefined when index is out of range.

Throws if str is not a string, or index is not an integer.

String.charAt(str, index?)

Answers the UTF-16 code unit at index.

Parameter
Type
Notes

str

string

the receiver

index?

number

integer; defaults to 0. Negative values do not count from the end

Returns a one-character string, or '' when index is out of range.

Throws if str is not a string, or index is not an integer.

String.concat(str, ...strings)

Joins strings end to end.

Parameter
Type
Notes

str

string

the receiver

...strings

string

every argument must already be a string

Returns a new string.

Throws if any argument is not a string — unlike JavaScript, which coerces.

String.decode(str, format)

Decodes text that was encoded with String.encode.

Parameter
Type
Notes

str

string

the encoded text

format

string

one of the encoding formats; no default

Returns the decoded string, read back as UTF-8.

Throws if either argument is not a string, or format is not a supported format.

String.digest(str, algorithm?, format?)

Hashes a string.

Parameter
Type
Notes

str

string

hashed as UTF-8

algorithm?

string

one of the hash algorithms; defaults to 'SHA256'

format?

string

one of the encoding formats; defaults to 'base16'

Returns the digest, encoded in format. Base16 output is upper-case.

Throws if any argument is not a string, or the algorithm or format is unsupported.

String.encode(str, format)

Encodes a string's UTF-8 bytes.

Parameter
Type
Notes

str

string

the receiver

format

string

one of the encoding formats; no default

Returns the encoded string.

Throws if either argument is not a string, format is not supported, or format is 'Z85' and the input is not a whole number of four-byte groups.

String.endsWith(str, searchString, endPosition?)

Answers whether str ends with searchString.

Parameter
Type
Notes

str

string

the receiver

searchString

string

endPosition?

number

integer; treat the string as if it were this long. Defaults to its real length

Returns a boolean.

Throws if str or searchString is not a string, or endPosition is present and not an integer.

String.from(value?)

Converts a value to its string form — the one member that coerces, and the way to opt into coercion everywhere else.

Parameter
Type
Notes

value?

any

omitting it answers ''

Returns a string. Never throws.

Note. This member counts its arguments: String.from() is '', while String.from(undefined) is 'undefined'.

String.hmac(str, key, algorithm?, format?)

Computes a keyed hash (HMAC).

Parameter
Type
Notes

str

string

the message

key

string

the secret

algorithm?

string

one of the hash algorithms; defaults to 'SHA256'

format?

string

one of the encoding formats; defaults to 'base16'

Returns the MAC, encoded in format.

Throws if any argument is not a string, or the algorithm or format is unsupported.

String.includes(str, searchString, position?)

Answers whether searchString occurs in str.

Parameter
Type
Notes

str

string

the receiver

searchString

string

position?

number

integer; where to start looking. Defaults to 0

Returns a boolean.

Throws if str or searchString is not a string, or position is not an integer.

String.indexOf(str, searchString, position?)

Answers the first index at which searchString occurs.

Parameter
Type
Notes

str

string

the receiver

searchString

string

position?

number

integer; where to start looking. Defaults to 0

Returns the index, or -1.

Throws if str or searchString is not a string, or position is not an integer.

String.lastIndexOf(str, searchString, position?)

Answers the last index at which searchString occurs, searching backwards.

Parameter
Type
Notes

str

string

the receiver

searchString

string

position?

number

integer; the last index the match may start at. Defaults to the end of the string

Returns the index, or -1.

Throws if str or searchString is not a string, or position is present and not an integer.

Note. Unlike String.indexOf, an omitted position is not defaulted to 0 — this member searches backwards, so a 0 default would only ever find a match at the very start.

String.length(str)

Answers the number of UTF-16 code units — AFScript has no property read for it.

Parameter
Type
Notes

str

string

the receiver

Returns a number.

Throws if str is not a string.

Note. An astral character counts as two units. Array.length(Array.from('a😀b')) is 3, because Array.from splits by code point.

String.match(str, pattern)

Matches pattern once against str.

Parameter
Type
Notes

str

string

the receiver

pattern

regular expression or string

a string is screened by safe-regex, then compiled with no flags

Returns an array holding the whole match followed by each capture group, or null if the pattern does not match.

Throws if str is not a string, pattern is neither a string nor a regular expression, or a string pattern is unsafe or will not compile.

Differs from JavaScript, which answers a match object carrying index, input and groups. This answers a plain array of strings — those extra properties are not reachable in AFScript anyway.

String.matchAll(str, pattern)

Matches pattern against str as many times as it occurs.

Parameter
Type
Notes

str

string

the receiver

pattern

regular expression or string

the g flag is added when it is missing

Returns an array of match arrays, each holding the whole match followed by its capture groups. An empty array means no match.

Throws on the same conditions as String.match.

Differs from JavaScript, which refuses a non-global pattern outright. AFScript adds the flag to the pattern the call will use instead — the literal handed in is never modified.

String.padEnd(str, targetLength, padString?)

Pads the end of str until it is targetLength long.

Parameter
Type
Notes

str

string

the receiver

targetLength

number

must be an integer

padString?

string

repeated as needed and truncated to fit; defaults to ' '

Returns a new string; the original when it is already long enough.

Throws if str or padString is not a string, or targetLength is not an integer.

String.padStart(str, targetLength, padString?)

Pads the front of str until it is targetLength long.

Parameter
Type
Notes

str

string

the receiver

targetLength

number

must be an integer

padString?

string

repeated as needed and truncated to fit; defaults to ' '

Returns a new string.

Throws if str or padString is not a string, or targetLength is not an integer.

String.repeat(str, count)

Repeats str.

Parameter
Type
Notes

str

string

the receiver

count

number

integer, zero or greater

Returns a new string; '' when count is 0.

Throws if str is not a string, or count is negative or not an integer. The check is made here rather than in the host, so the message names String.repeat.

String.replace(str, pattern, replacement)

Replaces the first match of pattern — or every match, if pattern carries the g flag.

Parameter
Type
Notes

str

string

the receiver

pattern

string or regular expression

a plain string matches literally, once

replacement

string or function (match, ...groups, offset, str)

$&, $1… are honoured in a string replacement

Returns a new string.

Throws if str is not a string, pattern is falsy, or replacement is neither a string nor a function.

String.replaceAll(str, pattern, replacement)

Replaces every match of pattern.

Parameter
Type
Notes

str

string

the receiver

pattern

string or regular expression

a regular expression must carry the g flag

replacement

string or function (match, ...groups, offset, str)

Returns a new string.

Throws on the same conditions as String.replace.

String.search(str, pattern)

Finds where pattern first matches.

Parameter
Type
Notes

str

string

the receiver

pattern

regular expression or string

screened by safe-regex when it is a string

Returns the index of the first match, or -1.

Throws if str is not a string, pattern is neither a string nor a regular expression, or a string pattern is unsafe or will not compile.

String.sign(str, privateKeyPem, algorithm?, encoding?)

Signs a string with a PEM-encoded private key.

Parameter
Type
Notes

str

string

the payload

privateKeyPem

string

PEM private key

algorithm?

string

one of the hash algorithms; defaults to 'SHA256'

encoding?

string

one of the encoding formats; defaults to 'base64'

Returns the signature, encoded in encoding.

Throws if any argument is not a string, the algorithm or encoding is unsupported, or the host rejects the key.

Note. The argument order is not the mirror of String.verify: signing takes (algorithm, encoding), verifying takes (encoding, algorithm).

String.slice(str, indexStart, indexEnd?)

Answers the substring from indexStart up to but not including indexEnd.

Parameter
Type
Notes

str

string

the receiver

indexStart

number

integer, required; negative counts from the end

indexEnd?

number

integer; defaults to the end of the string

Returns a new string; '' when the range is empty.

Throws if str is not a string, or either index is present and not an integer.

String.split(str, separator, limit?)

Splits str into an array.

Parameter
Type
Notes

str

string

the receiver

separator

string or regular expression

'' splits into UTF-16 code units

limit?

number

integer, zero or greater; stop after this many pieces

Returns an array of strings.

Throws if str is not a string, separator is neither a string nor a regular expression, or limit is present and is not a non-negative integer.

String.startsWith(str, searchString, position?)

Answers whether str starts with searchString.

Parameter
Type
Notes

str

string

the receiver

searchString

string

position?

number

integer; start the comparison here. Defaults to 0

Returns a boolean.

Throws if str or searchString is not a string, or position is not an integer.

String.substring(str, indexStart, indexEnd?)

Answers the substring between two indices, swapping them if they are the wrong way round.

Parameter
Type
Notes

str

string

the receiver

indexStart

number

integer, required

indexEnd?

number

integer; defaults to the end of the string

Returns a new string.

Throws if str is not a string, or either index is present and not an integer.

Note. Negative indices clamp to 0 here, where String.slice counts them from the end.

String.toLowerCase(str)

Answers str in lower case.

Parameter
Type
Notes

str

string

the receiver

Returns a new string.

Throws if str is not a string.

String.toUpperCase(str)

Answers str in upper case.

Parameter
Type
Notes

str

string

the receiver

Returns a new string.

Throws if str is not a string.

String.trim(str)

Removes whitespace from both ends.

Parameter
Type
Notes

str

string

the receiver

Returns a new string.

Throws if str is not a string.

String.trimEnd(str)

Removes trailing whitespace.

Parameter
Type
Notes

str

string

the receiver

Returns a new string.

Throws if str is not a string.

String.trimStart(str)

Removes leading whitespace.

Parameter
Type
Notes

str

string

the receiver

Returns a new string.

Throws if str is not a string.

String.verify(str, publicKeyPem, signature, encoding?, algorithm?)

Checks a signature made by String.sign.

Parameter
Type
Notes

str

string

the payload that was signed

publicKeyPem

string

PEM public key

signature

string

as produced by String.sign

encoding?

string

how signature is encoded; defaults to 'base64'

algorithm?

string

one of the hash algorithms; defaults to 'SHA256'

Returns true when the signature is valid, false when it is not.

Throws if any argument is not a string, or the encoding or algorithm is unsupported.

Note. encoding comes before algorithm here and after it in String.sign. Passing String.sign's argument order to String.verify throws rather than answering false.

Object

Three members. All three accept any non-null object — including an array, whose keys are its indices as strings — and throw on anything else, so Object.keys('abc') is an error rather than ['0', '1', '2'].

Own properties only: AFScript refuses every inherited member read, so there is nothing else to see. There is no assign, freeze, fromEntries or hasOwn.

Object.entries(obj)

Answers the object's own key/value pairs.

Parameter
Type
Notes

obj

object or array

Returns an array of two-element [key, value] arrays, in insertion order.

Throws if obj is not an object, or is null.

Object.keys(obj)

Answers the object's own keys.

Parameter
Type
Notes

obj

object or array

Returns an array of strings.

Throws if obj is not an object, or is null.

Object.values(obj)

Answers the object's own values.

Parameter
Type
Notes

obj

object or array

Returns an array of values, in the same order as Object.keys.

Throws if obj is not an object, or is null.

JSON

JSON.parse(text)

Parses JSON text.

Parameter
Type
Notes

text

string

Returns the parsed value, or undefined if text is not valid JSON.

Never throws — a script that cares about the difference should test the answer.

Differs from JavaScript, which throws a SyntaxError. There is no reviver argument.

JSON.stringify(value, space?)

Renders a value as JSON text.

Parameter
Type
Notes

value

any

space?

number or string

indentation; anything else is ignored

Returns a JSON string, or undefined for a value JSON cannot represent (undefined, or a function).

Differs from JavaScript: there is no replacer argument, so space is the second argument rather than the third.

XML

XML.parse(xml)

Parses an XML document into plain arrays and objects.

Parameter
Type
Notes

xml

string

Returns an array of nodes in document order, or undefined if the document is malformed. Attributes are gathered under ':@' with an @_ prefix on each name; text is a #text node.

Throws only if xml is not a string.

Hardened: DOCTYPE declarations are stripped and entity processing is off, so XXE and billion-laughs inputs are inert. Processing instructions and the XML declaration are dropped, and the result is copied before it reaches the script. Parser errors are swallowed rather than reported — answering undefined tells a script everything it can act on, and a stack trace would disclose the host's paths. There is no XML.stringify.

Date

Two members. Both accept the same timeValue: the string 'now', an ISO-ish string, or an integer epoch in milliseconds. The accepted string forms are

  • YYYY-MM-DD

  • YYYY-MM-DD HH:MM or YYYY-MM-DDTHH:MM, optionally with a trailing Z

  • YYYY-MM-DD HH:MM:SS or YYYY-MM-DDTHH:MM:SS, optionally with a trailing Z

  • YYYY-MM-DD HH:MM:SS.mmm or YYYY-MM-DDTHH:MM:SS.mmm, optionally with a trailing Z

and every one of them is read as UTC. Anything else throws. There is no Date value type in AFScript: a timestamp is a string or a number, and these two members are how you move between them.

Date.datetime(timeValue?, ...modifiers)

Normalises a timestamp and applies modifiers to it, left to right, in UTC.

Parameter
Type
Notes

timeValue?

string or number

'now', an accepted date string, or an integer epoch. Omitted means now

...modifiers

string

applied in order; see the modifier grammar

Returns an ISO-8601 string — or a number, if the last modifier reached was 'epoch'.

Throws if timeValue is neither an accepted string nor an integer. An unrecognised modifier is silently ignored.

Note. Month and year arithmetic clamps to the last valid day of the target month rather than overflowing into the next one: 31 January plus one month is 29 February in a leap year, not 2 March.

Date.format(timeValue, mask, tz?)

Renders a timestamp through a dateformat-style mask.

Parameter
Type
Notes

timeValue

string or number

as for Date.datetime

mask

string

a token mask, or the name of a built-in mask

tz?

string

a timezone name, e.g. 'UTC'. Defaults to the host's local timezone

Returns a string.

Throws if mask is not a string, tz is present and not a string, or timeValue is not an accepted form.

Note. With no tz, output is rendered in whatever timezone the host runs in — the same script can answer differently on two machines. Pass 'UTC', or prefix the mask with UTC:, when the answer must be stable.

Mask tokens, shown for 2020-06-09T13:05:07.089Z in UTC:

Token
Answers
Token
Answers

d dd

9 09

h hh

1 01 (12-hour)

ddd dddd

Tue Tuesday

H HH

13 13 (24-hour)

m mm

6 06

M MM

5 05 (minutes)

mmm mmmm

Jun June

s ss

7 07

yy yyyy

20 2020

l L

089 08 (milliseconds)

S

th (ordinal suffix)

t tt T TT

p pm P PM

N

2 (ISO weekday)

Z o

UTC +0000

W

24 (ISO week)

Built-in mask names: default, shortDate, paddedShortDate, mediumDate, longDate, fullDate, shortTime, mediumTime, longTime, isoDate, isoTime, isoDateTime, isoUtcDateTime, expiresHeaderFormat.

Math

Eight constants and thirty-seven functions. All but Math.secureRandom and Math.secureRandomInt are the host's own Math intrinsics, passed through unwrapped — they behave exactly as they do in JavaScript, including its coercion rules, and answer NaN for arguments they cannot use rather than throwing. Their entries are written in short form.

Math.random() is the platform PRNG: fast, seeded per realm, and not a security primitive. When unpredictability matters, use Math.secureRandom() or Math.secureRandomInt().

Math.E

Euler's number, the base of the natural logarithm.

Math.LN10

The natural logarithm of 10.

Math.LN2

The natural logarithm of 2.

Math.LOG10E

The base-10 logarithm of E.

Math.LOG2E

The base-2 logarithm of E.

Math.PI

The ratio of a circle's circumference to its diameter.

Math.SQRT1_2

The square root of ½.

Math.SQRT2

The square root of 2.

Math.abs(x)

Answers the absolute value of x.

Math.acos(x)

Answers the arc cosine of x, in radians. NaN outside [-1, 1].

Math.acosh(x)

Answers the hyperbolic arc cosine of x. NaN below 1.

Math.asin(x)

Answers the arc sine of x, in radians. NaN outside [-1, 1].

Math.asinh(x)

Answers the hyperbolic arc sine of x.

Math.atan(x)

Answers the arc tangent of x, in radians, between -π/2 and π/2.

Math.atan2(y, x)

Answers the angle in radians between the positive x-axis and the point (x, y). Note that y comes first.

Math.atanh(x)

Answers the hyperbolic arc tangent of x. NaN outside (-1, 1).

Math.cbrt(x)

Answers the cube root of x.

Math.ceil(x)

Answers the smallest integer greater than or equal to x.

Math.clz32(x)

Answers the number of leading zero bits in the 32-bit binary form of x.

Math.cos(x)

Answers the cosine of x, which is in radians.

Math.cosh(x)

Answers the hyperbolic cosine of x.

Math.exp(x)

Answers E raised to the power x.

Math.expm1(x)

Answers Math.exp(x) - 1, accurately for small x.

Math.floor(x)

Answers the largest integer less than or equal to x.

Math.fround(x)

Answers the nearest 32-bit single-precision float to x.

Math.hypot(...values)

Answers the square root of the sum of the squares of its arguments.

Math.imul(a, b)

Answers the result of 32-bit integer multiplication, with C-like wrapping.

Math.log(x)

Answers the natural logarithm of x. NaN for negative x, -Infinity for 0.

Math.log10(x)

Answers the base-10 logarithm of x.

Math.log1p(x)

Answers Math.log(1 + x), accurately for small x.

Math.log2(x)

Answers the base-2 logarithm of x.

Math.max(...values)

Answers the largest of its arguments.

Note. The largest element of an array is found by spreading it:

Math.min(...values)

Answers the smallest of its arguments.

Math.pow(base, exponent)

Answers base raised to the power exponent — the same as the ** operator.

Math.random()

Answers a pseudo-random float in [0, 1) from the platform PRNG.

Not a security primitive — see Math.secureRandom.

Math.round(x)

Answers x rounded to the nearest integer; a half rounds towards +Infinity.

Math.secureRandom()

Answers a float in [0, 1) drawn from the platform CSPRNG.

Returns a number in the same interval Math.random() uses — never 1 — so it drops straight into the usual index idiom. Fifty-three bits of randomness, the most a double can carry.

Math.secureRandomInt(min, max)

Answers an integer in [min, max], inclusive at both ends, drawn from the platform CSPRNG by rejection sampling so that no value is favoured.

Parameter
Type
Notes

min

number

a safe integer; may be negative

max

number

a safe integer, not less than min

Returns a number.

Throws if either argument is not a safe integer, if min > max, or if max - min + 1 is not itself a safe integer.

Math.sign(x)

Answers 1, 0 or -1 according to the sign of x.

Math.sin(x)

Answers the sine of x, which is in radians.

Math.sinh(x)

Answers the hyperbolic sine of x.

Math.sqrt(x)

Answers the square root of x. NaN for negative x.

Math.tan(x)

Answers the tangent of x, which is in radians.

Math.tanh(x

Answers the hyperbolic tangent of x.

Math.trunc(x)

Answers the integer part of x, dropping any fractional digits.

Number

Eight constants and six functions, all of them the host's Number intrinsics passed through unwrapped. There is no Number(...) conversion function — use Number.parseInt or Number.parseFloat — and no prototype members, so there is no toFixed.

Number.NaN and the global NaN are the same value; so are Number.POSITIVE_INFINITY and the global Infinity.

Number.EPSILON

The difference between 1 and the smallest float greater than 1 — the usual tolerance for comparing two floats.

Number.MAX_SAFE_INTEGER

The largest integer that can be represented exactly, 2⁵³ − 1.

Number.MAX_VALUE

The largest representable positive number.

Number.MIN_SAFE_INTEGER

The smallest integer that can be represented exactly, −(2⁵³ − 1).

Number.MIN_VALUE

The smallest representable positive number.

Number.NaN

Not-a-Number. Equal to nothing, including itself — test for it with Number.isNaN.

Number.NEGATIVE_INFINITY

Negative infinity.

Number.POSITIVE_INFINITY

Positive infinity.

Number.isFinite(value)

Answers whether value is a number and is neither infinite nor NaN. Does not coerce, so a numeric string answers false.

Number.isInteger(value)

Answers whether value is a number with no fractional part.

Number.isNaN(value)

Answers whether value is exactly NaN. Does not coerce, so a non-numeric string answers false.

Number.isSafeInteger(value)

Answers whether value is an integer that can be represented exactly.

Number.parseFloat(string)

Reads a decimal number from the front of a string.

Returns the number, or NaN if the string does not start with one. Trailing text is ignored.

Number.parseInt(string, radix?)

Reads an integer from the front of a string.

Parameter
Type
Notes

string

string

leading whitespace is skipped

radix?

number

2 to 36; always pass it when the input may be user-supplied

Returns the integer, or NaN.

Util

Two members, both backed by the platform CSPRNG.

Util.randomId(format, nbytes?)

Answers a random identifier.

Parameter
Type
Notes

format

string

one of the encoding formats

nbytes?

number

how many random bytes to draw; a positive integer, defaults to 32

Returns the encoded bytes as a string. The string is longer than nbytes — base64 answers about 4 characters per 3 bytes, base16 exactly 2 per byte.

Throws if format is unsupported, nbytes is not a positive integer, or format is 'Z85' and nbytes is not divisible by 4.

Util.uuidv4()

Answers a random (version 4) UUID.

Returns a 36-character lower-case string in the canonical 8-4-4-4-12 form.

Logger

Six members, one per level, from most to least severe: fatal, error, warn, info, debug, trace. They are a script's only output — there is no console, and the interpreter never writes to the host's own.

Each takes any number of values, converts each to a string (undefined and null become 'undefined' and 'null'), joins them with a single space, and emits the result at its level. A message below the configured logLevel is dropped. All six answer undefined.

Where the message goes is the host's business: the AFScript class emits a message event carrying { logLevel, message }.

Logger.fatal(...values)

Emits at level FATAL (1) — the level that is never filtered out.

Logger.error(...values)

Emits at level ERROR (2).

Logger.warn(...values)

Emits at level WARN (3).

Logger.info(...values)

Emits at level INFO (4).

Logger.debug(...values)

Emits at level DEBUG (5). Objects and arrays are worth wrapping in JSON.stringify — plain conversion answers [object Object], as it does in JavaScript.

Logger.trace(...values)

Emits at level TRACE (6), the noisiest level.

Global values

Infinity

Positive infinity — the same value as Number.POSITIVE_INFINITY.

NaN

Not-a-Number — the same value as Number.NaN. It compares equal to nothing, itself included.

These two are the only global values the standard library defines. Everything else a script sees in the global scope is a namespace object, or a symbol the host passed in.

Appendix: encoding formats

The same six formats are accepted by String.encode, String.decode, String.digest, String.hmac, String.sign, String.verify and Util.randomId. They are matched exactly — note the capitals on Z85.

Format

String.encode('abcd', …)

Notes

'base64'

YWJjZA==

RFC 4648 §4, padded with =

'base64url'

YWJjZA==

RFC 4648 §5 — - and _ in place of + and /, so the result is URL- and filename-safe

'base32'

MFRGGZA=

RFC 4648 §6, upper-case

'base32hex'

C5H66P0=

RFC 4648 §7 — the extended-hex alphabet, which sorts in the same order as the bytes it encodes

'base16'

61626364

Hexadecimal, upper-case

'Z85'

vpA.S

ZeroMQ Z85 — the most compact of the six, at 5 characters per 4 bytes

Z85 only accepts whole four-byte groups. String.encode('hello', 'Z85') throws, because five bytes is not a multiple of four; so does Util.randomId('Z85', 6). Decoding it needs a whole number of five-character groups. Hash digests are always a multiple of four bytes, so String.digest and String.hmac can use 'Z85' with any algorithm.

Appendix: hash algorithms

Accepted by String.digest, String.hmac, String.sign and String.verify, matched exactly:

Algorithm
Digest size
Notes

'MD5'

128 bits

Broken. Checksums only, never signatures or passwords

'RMD160'

160 bits

RIPEMD-160

'SHA1'

160 bits

Collision-broken. Legacy interoperability only

'SHA224'

224 bits

'SHA256'

256 bits

The default everywhere, and the right choice unless something external dictates otherwise

'SHA384'

384 bits

'SHA512'

512 bits

Defaults: String.digest and String.hmac answer SHA256 in base16; String.sign and String.verify use SHA256 and base64.

Neither hashing nor signing is a password primitive — there is no key-derivation function here, and String.digest is not one.

Appendix: Date.datetime modifiers

Date.datetime applies its modifiers left to right, in UTC, to an immutable value. A modifier it does not recognise is silently ignored.

Modifier
Effect

'+N days' / '-N days'

Adds or subtracts whole days. N is 1 to 6 digits

'+N hours' / '-N hours'

Hours

'+N minutes' / '-N minutes'

Minutes

'+N seconds' / '-N seconds'

Seconds. May carry milliseconds as '+1.500 seconds' — exactly three digits

'+N months' / '-N months'

Months, clamping to the last valid day of the target month

'+N years' / '-N years'

Years, clamping the same way

'start of day'

Truncates to 00:00:00.000

'start of month'

Truncates to the first of the month

'start of year'

Truncates to 1 January

'isostring'

Terminal. Answers an ISO-8601 string immediately. This is the default anyway

'epoch'

Terminal. Answers the epoch in milliseconds, as a number, immediately

The sign is required on the arithmetic modifiers — '1 days' is not recognised, and being unrecognised, does nothing. A terminal modifier ends the chain, so anything after 'epoch' is never applied.

Last updated