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

DAST Scanner Configuration

DAST Scanner Configuration

The DAST Scanner is configured through YAML scan profiles that control how the scanner behaves for different types of applications.

Profiles have two independent axes: a tech base (--profile) that describes what the target is, and a scan intensity (--intensity) that describes how hard to scan. They compose:

# tech base × intensity
xy-dast scan -u https://example.com --profile spa --intensity deep -o report.json

--profile selects the tech base (traditional, spa, openapi, graphql, soap, cms, or a custom profile); --intensity selects quick, balanced (default), deep, or passive. The intensity overlays attack strength, thresholds, phase durations, and CVE / deep-crawl enablement on top of the tech base, leaving its crawl and policy intact.

Profile Locations

Custom profiles can be placed in any of these directories:

Directory
Description

$XYGENI_DAST_DIR/profiles/

Installation-level profiles

./profiles/

Project-level profiles (current directory)

./conf/profiles/

Alternative project-level location

List all available profiles (built-in and custom):

xy-dast scan --list-profiles

Tech Base × Intensity

Built-in profiles split along two axes you combine with --profile and --intensity:

Axis
Option
Values
Controls

Tech base

--profile

traditional, spa, openapi, graphql, soap, cms

Scope, crawl, endpoint import, scan policy

Intensity

--intensity

quick, balanced (default), deep, passive

Attack strength, thresholds, durations, CVE / deep-crawl enablement

Any tech base combines with any intensity:

  • passive skips the active scan entirely (crawl + passive analysis only) — safe for production/staging.

  • deep turns on CVE checking and the deep crawler and raises strength/durations. CVE and deep crawl can also be toggled independently on any intensity with --vuln-check and --deep-crawl.

  • When --intensity is omitted, balanced (the tech base's own settings) is used. --auto-profile picks the tech base; --intensity still applies on top.

Profile Schema

A profile is a YAML file with the following sections:

Profile Inheritance

Use extends to inherit settings from a built-in or custom profile. Only the fields you specify are overridden; all other settings come from the parent.

This profile inherits all settings from openapi (minimal spidering, no AJAX spider, API-Scan policy) but overrides the active scan duration and strength.

For the intensity axis you rarely need extends at all: --intensity {quick|balanced|deep|passive} composes an intensity onto any tech base (built-in or custom) at scan time, so --profile openapi --intensity deep gives a deep API scan without a custom profile.

Authentication Configuration

FORM - HTML Form Login

JSON - Login Returning a Token

For a login endpoint that accepts JSON and answers with a token in the response body — the usual single-page-app shape. The scanner logs in, reads the token out of the reply, and sends it as the header you name on every subsequent request:

Field
Meaning

jsonAuthUrl

Login endpoint the credentials are posted to

jsonBody

Request body; {username} and {password} are filled from the users entry

tokenJsonPath

Dotted path to the token in the login response, e.g. authentication.token or data.accessToken

headerName

Header carrying the token on later requests (default Authorization)

headerPrefix

Text placed before the token — usually "Bearer ", including the trailing space

The token is re-read from the login response whenever the scanner re-authenticates, so a long scan does not quietly continue as an anonymous user once the first token expires.

Omit tokenJsonPath when the login sets a session cookie instead of returning a token: cookie session management is then used and no header is injected.

BEARER - Bearer Token

HEADER - Custom Header

BASIC - HTTP Basic Authentication

Credentials are Base64-encoded and sent as Authorization: Basic <encoded> on every request (RFC 7617).

OAUTH2 - OAuth2

The scanner obtains an access token from the OAuth2 token endpoint before the scan and sends it as Authorization: Bearer <token> on every request. Three non-interactive grants are supported.

Additional optional fields cover provider differences and the other grants:

Grant
Use case

CLIENT_CREDENTIALS

Machine-to-machine API access (no user).

PASSWORD

Scan as a user, using resource-owner credentials.

REFRESH_TOKEN

Scan an API behind an interactive (browser) login: perform the login once out-of-band, capture the refresh token, store it as a secret, and let the scanner mint access tokens in CI.

Token renewal for long scans. By default the token is fetched once. If it might expire before the scan finishes, add a tokenLifecycle block so the scanner renews it during the scan and keeps authenticated coverage:

On the CLI, --token-refresh enables this for the client-credentials flow (with --token-expiry-status to change the status code). Without it, the token is not renewed mid-scan — use token lifecycle, a longer-lived token, or a shorter scan.

If a client certificate (clientCertificate) is configured, it is reused for the token endpoint when the endpoint requires mTLS (RFC 8705).

Environment variables can be referenced with ${env:VARNAME} syntax anywhere in profile values. This is the recommended approach for sensitive values like tokens and passwords.

SCRIPT - Scripted Browser Login (multi-step / advanced)

For logins the declarative methods above cannot express — identifier-first flows, multi-page forms, JavaScript-gated logins, or apps that hand back the session token in a JS variable — provide a recorded browser-automation script. The scanner drives a real browser through the login, captures the resulting session (cookies, and tokens from response headers or browser storage), and injects it into the scan (active/passive scanning, the deep crawler, and the vulnerability check) — all authenticated.

The script format is Selenium IDE (.side) today; the configuration is engine-neutral so other automation frameworks (e.g. Playwright) can be added in future.

If the login is a single request that answers with the token in a JSON body, use JSON instead — it needs no browser and no recording, and re-reads the token whenever it re-authenticates.

How the session is captured, layered (lowest-config first): (1) extraction the script itself declares (store / executeScript into a variable); (2) explicit extract: rules above; (3) otherwise a best-effort scan of cookies and browser storage (a captured JWT becomes a bearer header).

Record the .side with the Selenium IDE browser extension. The actuation steps are replayed — navigation, field entry, clicks, checkboxes, waits, frame and window switches (selectFrame / selectWindow, so logins inside an iframe or a popup work), special keys recorded as ${KEY_ENTER}, ${KEY_TAB}, … and control flow (if / else if / else / end, while, times, do / repeat if) whose conditions are evaluated as JavaScript in the page — a ${VAR} reference is passed to the snippet as an argument, so write ${role} === 'admin' rather than quoting it. assert/verify test steps are ignored — the scanner authenticates, it does not run UI tests. Keep credentials out of the committed .side by using ${VAR} placeholders supplied via vars (with ${env:...}).

By default the login script is used only to authenticate. Set script.seedNavigation: true to also feed the pages it visits into the scan as navigation seeds — useful when the login flow already walks through gated areas you want covered. It defaults on for the spa tech base (whose JS-driven flows benefit most from seeds) and off otherwise; an explicit value always wins. This is separate from the standalone --navigation input, which seeds the scan from a recording independent of login.

When a recorded login does not complete

A scripted login that fails stops the scan, rather than continuing unauthenticated and reporting a misleadingly clean result for pages it never reached. The error names the step that failed, where the browser actually was, and two artifacts written next to the report:

The URL is usually the answer on its own — still on the login page means the flow never submitted, and the screenshot normally shows why.

What you see
Likely cause

Still on the login page, nothing visibly wrong

The submit click had no effect. A warning naming the element is logged when it never became clickable.

Still on the login page, an error visible in the screenshot

Credentials rejected, or the app requires MFA / a captcha.

A Skipping .side step N/M '<command>' warning

The recording needs a step the scanner does not implement, so what followed ran against the wrong state.

The step after a selectFrame cannot find its element

The frame locator did not match — check it against the recording.

Timeout on the first step

The start URL is not reachable from the scanner.

unusable control flow before any step runs

The recording's if/while/do blocks do not balance, or it uses forEach (unsupported).

condition … could not be evaluated in the page

A recorded if/while/repeat if condition threw. It is never assumed true or false, since either guess would run the wrong half of the recording.

The failure artifacts are written only when a step fails, to the report's output directory, readable only by the user that ran the scan. Every field value is blanked in the page before they are captured, so neither the screenshot nor the page snapshot carries what was typed — passwords, one-time codes, or a hidden field holding a SAMLResponse or CSRF token. The reported URL keeps only scheme, host and path, since mid-SSO the query and fragment carry the grant itself. What diagnosis needs — which page, which fields exist, any error the application displayed — is preserved.

Pre-authenticated Session Import (SSO / federated)

For OIDC/SAML SSO targets (Okta, Microsoft Entra ID, …), authenticate out-of-band and hand the scanner the resulting session artifacts — no scripted login required. Orthogonal to the header-based methods (combinable with bearer / API-key / mTLS); mutually exclusive with form login.

Imported cookies are sent as a single Cookie request header and the headers verbatim, on every scan request.

--session-file also accepts a Playwright storageState.json (the artifact from context.storage_state()): its cookies are imported and a JWT found in local storage becomes an Authorization: Bearer header, so a session captured by an existing Playwright login script needs no conversion.

Authentication Methods Reference

Method
Description
Required Fields

NONE

No authentication

--

FORM

HTML form login with username/password

loginUrl, usernameField, passwordField, users

JSON

JSON login; the token is read from the response body

jsonAuthUrl, jsonBody, tokenJsonPath, users

BEARER

Bearer token in Authorization header

headerName, headerValue, headerPrefix

HEADER

Custom header authentication

headerName, headerValue

BASIC

HTTP Basic authentication (RFC 7617)

users (username and password)

OAUTH2

OAuth2 token acquired pre-scan, injected as a bearer token

tokenUrl, clientId, clientSecret (+ grant-specific fields)

SCRIPT

Scripted browser login (Selenium .side) for multi-step / JS-gated auth

script.engine, script.file

Two further mechanisms are orthogonal to the method above and combine with it: session import (session.import / --session-cookie / --session-file) for pre-authenticated SSO sessions, and client certificate (clientCertificate / --client-cert) for mTLS.

Out-of-Band Detection (OAST)

Some vulnerabilities only reveal themselves out-of-band — the target opens a connection to a server you control rather than showing anything in its HTTP response. The scanner injects a payload and, if the target is vulnerable, it calls back to an OAST server (out-of-band application security testing). This is off by default (no surprise external callbacks); enable it with --oast-service / the oast: profile block. A bare --oast <url> infers interactsh.

CLI --oast-* flags take precedence over the profile block.

Choosing a service — the reachability constraint

An OAST callback only works if the target can reach the server and the scanner can observe the hit. Because the scanner runs in a container, pick the service to match how the target can reach back:

Service
How a hit is signalled
Use when

callback

Target connects back to the scanner's own advertised address (--oast/--oast-callback-port)

Target shares a network with the scanner (same Docker/host network)

boast

Target does OOB to a BOAST server; scanner polls it

Public target with outbound egress (zero-config public server)

interactsh

Target does OOB to an Interactsh server; scanner polls it

Public egress, or a self-hosted server both reach

  • callback is the simplest, most reliable option for internal targets — the scanner is its own OAST server, no external service. It detects the HTTP-based rules (blind SSRF, blind XXE).

  • Public Interactsh/BOAST are best-effort — they depend on a third-party server being reachable. Prefer callback (internal) or a self-hosted server for dependable results.

  • interactsh requires an explicit --oast URL — there is no built-in default (a blank server leaves detection inert). Use boast for a zero-config public server.

Per-rule detection

Rule

ID

Works with callback

Server Side Request Forgery (blind SSRF)

40046

✅

XML External Entity Attack (blind XXE)

90023

✅

Out-of-Band XSS

40031

✅ (a browser must view the injected payload)

Server Side Template Injection (blind)

90036

detected via timing¹

Log4Shell (CVE-2021-44228)

44228

❌ needs an external OAST²

Text4Shell (CVE-2022-42889)

40047

❌ needs an external OAST²

¹ Blind SSTI is detected by its time-based technique (no OAST server required). ² Log4Shell/Text4Shell payloads are JNDI/interpolation targets (ldap://…) the callback service can't carry; they need an interactsh/boast server. Log4Shell out-of-band is also covered independently by the vulnerability check.

Self-hosting an OAST server (internal / air-gapped targets)

For internal targets that cannot route back to the scanner and have no public egress, run your own OAST server that both the target and the scanner can reach. The recommended option is projectdiscovery/interactsh:

  1. Provision an interactsh-server on a host reachable by the target and the scanner (go install github.com/projectdiscovery/interactsh/cmd/interactsh-server@latest, or the published container image).

  2. Give it a domain. Interactsh correlates via a unique DNS subdomain per payload, so its domain (e.g. oast.internal.example.com) needs an NS record pointing at the server, and the server must be reachable on DNS (53) and HTTP/S (80/443). Protect the poll API with a token (-token). This is the server's own (local) DNS — not public internet DNS exfiltration.

  3. Point the scanner at it:

When --oast-service=interactsh is used with a server, the vulnerability check phase is pointed at the same server, so out-of-band detection is consistent across both engines.

Run the self-hosted OAST server alongside the target, not inside the scanner container. Ensure the interactsh-server version is protocol-compatible with the scanner's OAST client.

WebSocket Scanning

Applications that push data over WebSocket channels (ws:// / wss://) can leak information the ordinary HTTP scan never sees. When enabled, the scanner runs passive checks over every WebSocket message exchanged during the scan — no messages are injected or replayed, so it is safe against production-like targets.

Enable it with the webSocket: profile block or --websocket; --websocket-scripts name1,name2 overrides the profile's passiveScripts. It is on by default for the spa profile and off elsewhere.

WebSocket channels are captured only while the AJAX spider drives a real browser through the scanner — so this applies to SPA/AJAX scans, not the plain spider or an API-only (OpenAPI/Postman) import.

When no scripts are selected, a bundled set of disclosure checks runs: pii-disclosure, email-disclosure, base64-disclosure, application-error, debug-error-disclosure, and xml-comments-disclosure. A script named in the selection but not found is skipped with a warning — it never fails the scan.

Rate Limiting

By default a scan runs as fast as the target accepts. The rateLimit block paces it — scan-wide, across the active scan, the crawl, the deep crawl and the vulnerability check alike:

Field
Meaning

maxRequestsPerSecond

Ceiling on requests per second for the scan as a whole. CLI: --rate-limit

requestDelayMs

The same setting expressed as a delay between requests — 5 req/s is 200 ms. CLI: --request-delay

scanThreads

Concurrent requests per host. Also lowers the active scan's threads per host. CLI: --scan-threads

adaptive

Probe for advertised limits, honour 429 / 503 / Retry-After, keep a safety margin. CLI: --adaptive-rate-limit

Set one rate form, not both. requestDelayMs and maxRequestsPerSecond are two spellings of the same setting, and each phase derives whichever form it needs, so stating both only creates two numbers to keep in sync.

Every request is paced on one shared schedule, so the figure is a ceiling for the scan as a whole rather than a per-phase or per-thread one. scanThreads is the only lever the crawl has besides pacing, since the crawler exposes no per-request delay of its own.

Precedence. CLI values override the profile, field by field. The two rate forms override as a pair: passing either --rate-limit or --request-delay replaces both profile forms, so a profile's delay can never survive an explicit --rate-limit and quietly win for the phases that consume a delay.

Two intensity overlays set pacing by default:

Overlay
Pacing

deep

10 req/s, 2 threads — it is long-running, so it trades throughput for politeness

passive

adaptive only, no fixed limit — a target that never pushes back is still scanned at full speed

quick, balanced

unset

A throttled scan records what the target refused (dast.ratelimit.throttled), where advertised limits held it back (dast.ratelimit.paced), and whether throttling took effect at all (dast.ratelimit.enforced). See Rate Limiting in the scanner guide for using those in CI.

Identifying Scan Traffic

Every scan sends X-Xygeni-DAST on every request, so the owner of the target can tell DAST traffic apart in their own access logs. The presence of the header identifies scanner traffic; the value identifies one scan and states when it started:

The value is scan-<yyyyMMdd>-<HHmmss> in UTC, resolved once per run and reused by every phase — the crawl, the active scan, the deep crawl, the vulnerability check, the pre-flight probes and the browser that replays a recorded login. It is also written to the report as the dast.request.marker property, so a report and the target's logs can be lined up from either side. No configuration is needed to get it.

Additional headers are declared as a requestHeaders map, or with --request-header on the CLI:

Behaviour
Detail

Value syntax

${env:VAR} in YAML, env:VAR_NAME on the CLI, to keep a token out of the file and the process list

CLI syntax

"Name: Value" or Name=Value, repeatable

Changing the marker

Name X-Xygeni-DAST and give it your own value

Suppressing the marker

Give X-Xygeni-DAST an empty value, for a target that rejects unknown headers

Merging

A profile's headers merge per name with the profile it extends; the CLI overrides the profile only for the headers it names, leaving the rest in place

Collisions with authentication

A header matching the one the authentication injects is dropped with a warning rather than overriding it

Rejected names

Authorization, Proxy-Authorization and Cookie — these carry the scan's own credentials or session, so setting one would replace them rather than add to them. Use the authentication options instead

Rejected values

Values containing CR, LF or other control characters, which would be header injection

Per-Rule Policy Overrides

The activeScan.rules list allows you to override threshold and strength for individual scan rules, or disable rules entirely:

Setting threshold: "Off" disables a rule entirely. Valid threshold values are Off, Low, Medium, and High. Valid strength values are Low, Medium, High, and Insane.

Example: OWASP Juice Shop Profile

This complete example shows a custom profile for scanning the OWASP Juice Shop application:

Run with:

Profile Schema Reference

Section
Fields
Description

name, description

string

Profile identity

extends

string

Parent profile to inherit from

scope

includePatterns, excludePatterns

Regex lists for URL filtering

authentication

method, loginUrl, usernameField, passwordField, headerName, headerValue, headerPrefix; OAuth2: grantType, tokenUrl, clientId, clientSecret, scope, audience, username, password, refreshToken, clientAuthMethod, extraParams

Authentication settings

users

List of {name, username, password, default}

Credentials for form-based auth

session

method

Session management: COOKIE, HEADER, or SCRIPT

technology

language, database, framework, include

Technology context for scan optimization

spider

duration, depth, children

Traditional spider settings

ajaxSpider

duration, depth, browsers, skip

AJAX spider settings (for SPAs)

activeScan

duration, ruleDuration, policy, strength, threshold, rules

Active scan settings

passiveScan

waitDuration

Passive scan wait time

webSocket

enabled, passiveScripts

Passive WebSocket scanning (SPA/AJAX only); on by default for spa. See WebSocket Scanning

deepCrawl

enabled, depth, duration

Pre-scan deep crawling

vulnCheck

enabled, templates, severities, excludeTags, rateLimit, timeout

Post-scan vulnerability checking

clientCertificate

path, password

PKCS#12 client certificate for mTLS targets (orthogonal to other auth methods)

oast

service, server, token, pollSeconds, port

Out-of-band detection (OAST); off by default. See Out-of-Band Detection

rateLimit

maxRequestsPerSecond, requestDelayMs, scanThreads, adaptive

Pacing for every phase; unset by default. See Rate Limiting

requestHeaders

Map of Name: Value

Extra headers sent to the target by every phase. The X-Xygeni-DAST scan marker is always sent; name it here to change or suppress it. See Identifying Scan Traffic

filtering

excludeRules, riskThreshold

Result filtering

Last updated