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:
$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-profilesTech Base × Intensity
Built-in profiles split along two axes you combine with --profile and --intensity:
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:
passiveskips the active scan entirely (crawl + passive analysis only) — safe for production/staging.deepturns 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-checkand--deep-crawl.When
--intensityis omitted,balanced(the tech base's own settings) is used.--auto-profilepicks the tech base;--intensitystill 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:
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.
FORM and JSON cannot log in without credentials to log in with. A profile declaring either without a users: block is refused before the scan starts, rather than running an unauthenticated scan that reports success.
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:
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).
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.
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).
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.
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.
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
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:
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
callbackis 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.interactshrequires an explicit--oastURL — there is no built-in default (a blank server leaves detection inert). Useboastfor 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:
Provision an
interactsh-serveron a host reachable by the target and the scanner (go install github.com/projectdiscovery/interactsh/cmd/interactsh-server@latest, or the published container image).Give it a domain. Interactsh correlates via a unique DNS subdomain per payload, so its domain (e.g.
oast.internal.example.com) needs anNSrecord 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.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.
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.
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:
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:
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
Do not pin vulnCheck.rateLimit unless that phase genuinely needs its own speed. It is the more specific setting, so it takes the scan-wide rateLimit — and --rate-limit — out of play for the vulnerability check. Because a tech profile is always in play, a pin in one silences the scan-wide limit for that phase in every scan, deep's own 10 req/s included, since an intensity overlay inherits the tech base's vulnCheck fields. A profile pin also yields to a rate given on the command line; only an explicit --vuln-check-rate-limit exempts the sweep.
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:
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
The marker identifies traffic, it does not authenticate it. The header name is published and its value is a plain timestamp, so anyone can send it. It is safe for correlation and tagging — lining a scan up with your logs, annotating a SIEM, explaining a traffic spike — but must never be used to suppress alerts, drop requests, allowlist through a WAF, or skip authorization. A filter built on the marker is one an attacker can put themselves inside.
Where a scan needs different treatment from the application or its WAF, gate it on the scanner's source address, allowlisted for the engagement, optionally combined with a marker value only you and the target know (--request-header "X-Xygeni-DAST: env:SCAN_SECRET").
Per-Rule Policy Overrides
The activeScan.rules list allows you to override threshold and strength for individual scan rules, or disable rules entirely:
Example: OWASP Juice Shop Profile
This complete example shows a custom profile for scanning the OWASP Juice Shop application:
Run with:
Profile Schema Reference
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

