Deployment: Invicti Platform on-demand, Invicti Platform on-premises
Custom vulnerability checks
If you need to validate highly specific response conditions, detect undisclosed or cutting-edge vulnerabilities, or enforce your organization's internal security standards, you can write your own checks in JavaScript and have Invicti run them as part of every scan. Results appear in the UI and flow to connected issue trackers, exactly like built-in checks.
This document explains how to write and use custom vulnerability checks in Invicti Platform.
Why this matters
Built-in checks cover well-known vulnerabilities across standard frameworks. Custom checks let you go beyond that - to validate highly specific response conditions, detect cutting-edge or undisclosed vulnerabilities before they appear in the standard database, or enforce your organization's internal security standards. You define the security logic, Invicti runs it on every scan.
Upload a custom security check
Upload your script through the Invicti Platform UI to make it available for use in scan profiles.
- Select Scans > Custom security checks from the left-side menu.
- Click New security check.
- Set Category to Target or Location depending on what your check needs to do:
- Target - runs once per scan. Use for one-time checks, such as testing whether a specific endpoint exists or probing how the server responds to a particular request.
- Location - triggered when a new endpoint or path is discovered during the scan. Use when your check needs to examine each discovered location - it has access to the response body, headers, and the request that triggered it.
- Under File, upload your
.jsscript file. - Enter a Name for the check.
- Set the Severity level.
- Optionally add a Title and Description.
- Save the check.


Add custom security checks to a scan profile
After uploading your checks, add them to a scan profile to control which scans run them.
- Select Scans > Custom profiles from the left-side menu.
- Click Add new profile.
- Enter a Name and optionally a Description for the profile.
- In Checks to include, type
Customin the filter to find your uploaded checks. - Select the checks you want to include using the checkboxes.
- Click Save.
Once saved, select this scan profile when launching a scan.


Examples
Target check: detect an accessible admin console
This Target check probes the /admin/ endpoint and raises a vulnerability alert if the admin console is accessible.
// create a custom HTTP job
let job = ax.http.job();
// set its URL to the scan target URL
job.setUrl(scriptArg.target.url);
// probe the /admin/ endpoint
job.request.uri = '/admin/';
// execute the request against the target
ax.http.execute(job).sync();
if (!job.error
&& job.response.status !== 404
&& job.response.body.indexOf('Admin Console</title>') != -1) {
scanState.addVuln({
location: scriptArg.target.root, // affects the server, so attach to /
http: job, // the HTTP request/response will show up in the UI
vulnerability_type: {
name: 'Admin console is accessible on target',
severity: 'high',
description: 'The admin endpoint should be restricted for internal use.',
impact: 'An attacker may discover it and use it to exploit the application.',
recommendation: 'Restrict the admin panel to the corporate VPN.',
}
});
}
Location check: detect a compressed archive at each discovered directory
This Location check runs on each discovered directory and raises a vulnerability alert if an archive.zip file is accessible at that location.
if (scriptArg.location.url.endsWith('/')) // verify the new location is a directory
{
// create a custom HTTP job
let job = ax.http.job();
// set its URL to the location to check
job.setUrl(scriptArg.location.url + 'archive.zip');
// execute the request
ax.http.execute(job).sync();
if (!job.error
&& job.response.status == 200) {
scanState.addVuln({
location: scriptArg.location, // affects this location
http: job, // the HTTP request/response will show up in the UI
vulnerability_type: {
name: 'Compressed archive found on server',
severity: 'high',
description: 'A private compressed archive was discovered at the target URL.',
impact: 'An attacker may download the archive and use the information inside.',
recommendation: 'Remove the archive from the site.',
}
});
}
}
Script API reference
This section is for developers writing custom security check scripts. It covers the interfaces, methods, and properties available inside a .js file when authoring a check. If you're uploading a ready-made script, you can skip this section.
Use the scripting engine API to read scan context, send requests, report vulnerabilities, add unlisted endpoints, and log debug output. For the complete API definition, refer to the native.d.ts type definitions file included with the scripting engine.
Read scan context data
Show details
Use scriptArg to read information about the current scan context - the target, the location being tested, and the HTTP exchange that triggered your script:
| Property | Description |
|---|---|
scriptArg.target | Information about the scan target. Properties: host (string), port (string), secure (boolean), ip (string array), url (string - full URL of the target), root (location object representing /). Example: scriptArg.target.url |
scriptArg.location | In Location checks: the path, name, and url of the location the script was invoked on. In Target checks: path returns '/' and name returns an empty string. Example: scriptArg.location.url |
scriptArg.http | The ax.http.Job object containing the HTTP request/response pair the script was invoked on. Examples: scriptArg.http.request.uri, scriptArg.http.response.body |
Send custom HTTP requests
Show details
Use ax.http.Job to send a custom HTTP request from within your script - useful when you need to probe the target for a specific condition or resource that wasn't discovered automatically.
let job = ax.http.job();
job.hostname = 'www.example.com';
job.request.uri = '/';
ax.http.execute(job).sync();
Connection settings:
| Property | Required | Default | Description |
|---|---|---|---|
hostname | Yes | - | Host or domain name to connect to. Example: www.example.com |
secure | No | false | Whether to use HTTPS/TLS. |
port | No | 80 (443 if secure is true) | TCP destination port as a string. Example: job.port = "8080" |
timeout | No | 30000 | Milliseconds before a pending connection is cancelled. |
retries | No | 3 | Number of times to retry a timed-out request. |
Request settings (set on ax.http.Job.Request):
| Property | Description |
|---|---|
uri | URI to request. Default: / |
method | HTTP method. Default: GET |
body | Request body |
addHeader(name, value) | Add a custom request header. Example: job.request.addHeader('X-Header', 'Value') |
setUrl(url) | Set the full URL for the request in one call instead of setting hostname, port, secure, and uri separately. Example: job.setUrl(scriptArg.target.url) |
Before processing the response, check that the connection succeeded:
if (!job.error) {
// Proceed with response processing
}
Read the server response
Show details
After sending a request, read what the server returned through ax.http.Job.Response to determine whether a vulnerability condition exists:
| Property | Description |
|---|---|
version | HTTP version. Example: "HTTP/1.1" |
status | Numeric HTTP status code. Example: 200 |
reason | String representation of the status code. Example: "OK" |
headers | Response headers. Use has(name) to check for a header, get(name) to retrieve its value, and toString() for a plain text representation of all headers. Header names aren't case-sensitive. |
body | Response body |
Example:
if (job.response.headers.has('Content-Type')) {
ax.log(ax.LogLevelInfo, job.response.headers.get('content-type'));
}
Report a vulnerability from your script
Show details
Use scanState.addVuln() to raise a vulnerability alert. Invicti treats it exactly like a built-in check result: it appears in the UI and can be sent to connected issue trackers.
Custom checks must fully describe the vulnerability inline using a vulnerability_type object. Grouping and deduplication is done by content hash, so the full definition matters.
scanState.addVuln({
location: scriptArg.location,
vulnerability_type: {
name: 'Vulnerability name',
severity: 'high',
description: 'Description of the vulnerability.'
}
});
Properties:
| Property | Required | Description |
|---|---|---|
location | Yes (if path not set) | An ax.state.Location object for where the vulnerability was found. Pass scriptArg.location to use the current invocation's location. |
path | Yes (if location not set) | A string path to the affected location. Must be absolute (starting with /) and within the scope of the current scan. If Invicti isn't aware of the path, it assigns the vulnerability to the root / location instead. |
vulnerability_type | Yes | Object describing the vulnerability. Must include name (string) and severity ('info', 'low', 'medium', 'high', or 'critical'). Optionally include description, impact, and recommendation (all strings). |
http | No | The HTTP request/response pair that triggered the alert. Pass scriptArg.http for the current invocation's pair, or a manually created ax.http.Job object. Displayed alongside the alert in the UI. |
Invicti deduplicates vulnerability alerts using a content hash of name and severity. Two scripts can report the same vulnerability as long as both fields match. If duplicates exist, the last reported instance wins on all remaining fields - so keep description, http, and other fields consistent across scripts that report the same vulnerability.
Add unlisted endpoints to the scan
Show details
If a target has pages or endpoints that aren't referenced anywhere on the site, use scanState.hintLinks() to tell Invicti about them. Invicti then includes them in Discovery, Analysis, and Testing - so they get tested just like any other discovered page.
scanState.hintLinks(['/hidden-endpoint.php', '/admin/config']);
URIs must be absolute (starting with /) and within the scope of the current scan. Out-of-scope URIs are ignored.
Write debug logs
Show details
Use ax.log() to write log entries while you develop and test your scripts:
ax.log(ax.LogLevelInfo, 'Something happened.');
level- Severity of the log entry. Accepted values:ax.LogLevelInfo(1),ax.LogLevelWarning(2),ax.LogLevelError(3).data- The value to log, usually a string or number.
To access the log files, enable target debugging before launching the scan. On the target's settings page, go to the Advanced tab and enable Debug scans for this target. After the scan completes, the log file location appears on the Events tab.


Encode and decode strings
Show details
Use these helper functions when you need to encode or decode string values while processing requests and responses:
HTML encoding:
ax.util.htmlEncode(input: string): string;
ax.util.htmlDecode(input: string): string;
Base64 encoding:
ax.util.base64Encode(input: string): string;
ax.util.base64Decode(input: string): string;
Example:
let testString = 'This is <b>bold</b> text';
let encoded = ax.util.htmlEncode(testString);
ax.log(ax.LogLevelInfo, `Encoded: ${encoded}`);
// Output: Encoded: This is <b>bold</b> text
let decoded = ax.util.htmlDecode(encoded);
ax.log(ax.LogLevelInfo, `Decoded: ${decoded}`);
// Output: Decoded: This is <b>bold</b> text
Parse XML responses
Show details
Use ax.struct.parseXml() to parse XML responses. The function returns a DOMDocument object regardless of whether the input is valid XML - always check the error property before processing:
let parsedXML = ax.struct.parseXml(responseBody);
if (parsedXML.error === true) {
// Unable to parse as XML
} else {
// Continue processing
}
Access tags and their content using getElementsByTagName(), .text, and getAttribute():
let outer = parsedXML.getElementsByTagName('outer')[0];
let inner = outer.getElementsByTagName('inner')[0];
let innerText = inner.text; // tag value
let attrValue = inner.getAttribute('my-attribute'); // attribute value
Every DOMNodeList has a length property, which lets you iterate over elements when the XML structure isn't known in advance.
Troubleshooting
My custom security check doesn't run during the scan
Check the following:
- The check is uploaded and visible under Scans > Custom security checks.
- The scan profile you selected includes the check.
- The file uses the
.jsextension.
The script runs but I can't find the log files
Log files are only generated when target debugging is enabled. Before launching the scan, open the target settings, go to the Advanced tab, and enable Debug scans for this target. After the scan completes, the log file path appears on the Events tab.
A vulnerability alert is assigned to / instead of the correct path
This happens when you pass a path value in addVuln() that Invicti isn't aware of. Either switch to location: scriptArg.location, or make sure the path was discovered during the scan or added via scanState.hintLinks() before the alert is issued.
Need help?
Invicti Support team is ready to provide you with technical help. Go to Help Center