> For the complete documentation index, see [llms.txt](https://docs.xygeni.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.xygeni.io/xygeni-products/dast-security/dast-scanner.md).

# DAST Scanner

## Table of Contents

1. [Purpose](#purpose)
2. [Installation](#installation)
3. [Quick Start](#quick_start)
4. [Usage](#usage)
5. [Built-in Scan Profiles](#profiles)
6. [Authentication](#authentication)
7. [CI/CD Integration](#cicd)
8. [Command Reference](#command_reference)
9. [Exit Codes](#exit_codes)

### Purpose <a href="#purpose" id="purpose"></a>

The **DAST Scanner** (`xy-dast`) performs automated **dynamic security testing** of running web applications and REST APIs. It tests applications from the outside -- simulating real-world attacks against live endpoints -- to identify vulnerabilities that are only exploitable at runtime.

The scanner supports different application types:

* **Traditional** server-rendered web apps (PHP, JSP, ASP.NET)
* **SPA** JavaScript-heavy Single Page Applications (React, Angular, Vue)
* **REST API** with OpenAPI/Swagger specifications or Postman collections
* **GraphQL** APIs (schema import or introspection)
* **SOAP** web services (WSDL import)
* **CMS** platforms (WordPress, Drupal, Joomla)

You choose *what* the target is and *how hard* to scan it on **two independent axes**: a **tech-stack profile** (`--profile`) and a **scan intensity** (`--intensity`). They compose freely — e.g. a deep scan of an SPA is `--profile spa --intensity deep`. See [Built-in Scan Profiles](#profiles).

### Installation <a href="#installation" id="installation"></a>

The DAST scanner is distributed as a Docker image (`xygeni/xy-dast`). Install a lightweight wrapper directly from the image — no separate download is needed. The wrapper is a small, signed script that delegates to `docker compose run`, so once installed you invoke `xy-dast` as if it were a native command.

#### One-command install (recommended)

From 6.14.0 there is an installer that does all of the below for you: it finds Docker, pulls the image, **verifies its signature**, creates the install directory, and pins the wrapper to the exact image digest it verified. On a host with only Podman it stops and tells you to install Docker, rather than installing a wrapper that could not run.

{% tabs %}
{% tab title="Linux / macOS" %}

```bash
curl -fsSL https://get.xygeni.io/latest/dast/get-dast.sh | sh

# To pass options through the pipe, separate them with `-s --`:
curl -fsSL https://get.xygeni.io/latest/dast/get-dast.sh | sh -s -- -d /usr/local/bin --add-to-path
```

Installs into the first writable of `$HOME/.local/bin` or a `$HOME/bin` already on your `PATH`, falling back to `$HOME/.local/bin`.
{% endtab %}

{% tab title="Windows" %}

```powershell
iwr -useb https://get.xygeni.io/latest/dast/get-dast.ps1 | iex

# `iex` cannot pass parameters. To use any, download the script and run the file:
iwr -useb https://get.xygeni.io/latest/dast/get-dast.ps1 -OutFile get-dast.ps1
.\get-dast.ps1 -InstallDir C:\Tools\bin -AddToPath
```

Installs into `$HOME\.local\bin` unless `-InstallDir` says otherwise.
{% endtab %}
{% endtabs %}

| Linux / macOS             | Windows                       | Effect                                                        |
| ------------------------- | ----------------------------- | ------------------------------------------------------------- |
| `-d <dir>`                | `-InstallDir <dir>`           | Install somewhere other than the default                      |
| `-i <tag\|sha256:digest>` | `-Image <tag\|sha256:digest>` | Pin a version instead of `latest`                             |
| `--add-to-path`           | `-AddToPath`                  | Add the install directory to your shell profile / user `PATH` |
| `--prune`                 | `-Prune`                      | Also delete `xy-dast` wrappers found elsewhere on `PATH`      |
| `--allow-unverified`      | `-AllowUnverified`            | Proceed when no verifier is available                         |

To check the installer itself before running it, compare it against the digest published in the `xygeni/xygeni` repository:

{% tabs %}
{% tab title="Linux / macOS" %}

```bash
curl -fsSLO https://get.xygeni.io/latest/dast/get-dast.sh
h=$(curl -fsS https://raw.githubusercontent.com/xygeni/xygeni/main/checksum/latest/get-dast.sh.sha256)
echo "$h get-dast.sh" | sha256sum -c
sh ./get-dast.sh
```

{% endtab %}

{% tab title="Windows" %}

```powershell
iwr -useb https://get.xygeni.io/latest/dast/get-dast.ps1 -OutFile get-dast.ps1
$h = (iwr -useb https://raw.githubusercontent.com/xygeni/xygeni/main/checksum/latest/get-dast.ps1.sha256).Content.Trim()
(Get-FileHash .\get-dast.ps1 -Algorithm SHA256).Hash -eq $h.ToUpper()
.\get-dast.ps1
```

{% endtab %}
{% endtabs %}

The digest lives in a different place from the script it vouches for: an attacker would have to compromise both `get.xygeni.io` and the GitHub repository to pass this check.

A signature that **fails** verification always stops the install. If no verifier is available at all (no `cosign`, no Docker-run fallback), the install reports that it could not verify and continues only when you pass `--allow-unverified`.

#### Keeping it up to date

```bash
xy-dast update                       # pull, verify, re-pin to the latest
xy-dast update --version 6.14.0      # or a specific tag / sha256: digest
```

`update` verifies the new image exactly as the installer does, so it fails rather than re-pin to something it could not check. `--allow-unverified` covers the case where no verifier is available; a signature that *fails* is always fatal.

`update` moves the wrapper and the image together. That matters: the two speak a versioned calling contract, and an image newer than its wrapper refuses to run rather than mis-resolve your local file paths — which used to surface as a confusing "file not found" for a file that was plainly there.

#### Manual install

The step-by-step route below works with any released image, and is the one to use where the installer cannot reach the internet.

**Requirements**

* **Docker Engine 20.10+** (or Docker Desktop) with Compose v2 — i.e. the `docker compose ...` subcommand. The legacy `docker-compose` v1 binary is not supported, and Podman is not a substitute: the wrapper drives `docker compose`, which Podman does not provide.
* A directory on your `PATH` to drop the wrapper into. This guide uses `~/.local/bin` (Linux/macOS) and `%USERPROFILE%\.local\bin` (Windows).

#### Step 1 — Create the install directory and ensure it is on your `PATH`

This is the most common source of "command not found: xy-dast" issues. The install directory **must exist before the install command** (Docker creates it as `root` if it does not, which then fails to write), and it **must be on your `PATH`** for the short `xy-dast` command to work.

{% hint style="warning" %}
On **Windows** and **macOS** the `~/.local/bin` directory is **not** on the default `PATH`. On most Linux distributions it *is* added by `~/.profile`, but **only if the directory exists at login** — if you create it now in an existing shell, you still need to add it to `PATH` for the current session (or open a new login shell after creating it).
{% endhint %}

{% tabs %}
{% tab title="Linux" %}

```bash
# 1. Create the directory (idempotent)
mkdir -p ~/.local/bin

# 2. Make sure it is on PATH for the current shell
case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) export PATH="$HOME/.local/bin:$PATH" ;; esac

# 3. Persist for future shells (only needed once per shell rc)
grep -q '\.local/bin' ~/.bashrc 2>/dev/null \
  || echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
# zsh users: replace ~/.bashrc with ~/.zshrc
```

Verify:

```bash
echo "$PATH" | tr ':' '\n' | grep -F "$HOME/.local/bin"   # should print the path
```

{% endtab %}

{% tab title="macOS" %}

```bash
# 1. Create the directory
mkdir -p ~/.local/bin

# 2. Add to PATH for the current shell
export PATH="$HOME/.local/bin:$PATH"

# 3. Persist for future shells (zsh is the default since macOS Catalina)
grep -q '\.local/bin' ~/.zshrc 2>/dev/null \
  || echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
# bash users: replace ~/.zshrc with ~/.bash_profile
```

Verify:

```bash
echo "$PATH" | tr ':' '\n' | grep -F "$HOME/.local/bin"
```

{% endtab %}

{% tab title="Windows (PowerShell)" %}

```powershell
# 1. Create the directory
New-Item -ItemType Directory -Force -Path "$HOME\.local\bin" | Out-Null

# 2. Add to PATH for the current shell
$env:PATH = "$HOME\.local\bin;$env:PATH"

# 3. Persist for future shells (User scope, no admin needed)
$userPath = [Environment]::GetEnvironmentVariable('PATH', 'User')
if ($userPath -notlike "*$HOME\.local\bin*") {
  [Environment]::SetEnvironmentVariable('PATH', "$HOME\.local\bin;$userPath", 'User')
}
```

Verify (open a **new** PowerShell window after persisting):

```powershell
$env:PATH -split ';' | Select-String '\.local\\bin'
```

{% hint style="info" %}
The Windows wrapper is a PowerShell script (`xy-dast.ps1`) signed with an Authenticode certificate. If your execution policy blocks running scripts, run `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once.
{% endhint %}
{% endtab %}
{% endtabs %}

#### Step 2 — Install the wrapper from the Docker image

The image's `install` subcommand drops two files into the mounted directory: the `xy-dast` wrapper script itself and a sidecar `xy-dast-compose.yml` that holds the image reference, environment forwarding, and runtime parameters.

{% tabs %}
{% tab title="Linux / macOS" %}

```bash
docker run --rm -v ~/.local/bin:/mnt/install xygeni/xy-dast install
```

{% endtab %}

{% tab title="Windows (PowerShell)" %}

```powershell
docker run --rm -v "${HOME}\.local\bin:/mnt/install" xygeni/xy-dast install --powershell
```

`--powershell` produces the signed `.ps1` wrapper (and matching sidecar) instead of the bash one.
{% endtab %}
{% endtabs %}

Verify:

```bash
xy-dast --version
```

If you get `command not found` (or `not recognized as ... cmdlet`), revisit Step 1 — the directory is almost certainly not on your `PATH` yet.

#### Quick install vs. secure install

The plain `install` command above is a **quick install**: convenient for desktop and ad-hoc use. The wrapper itself is signed at release time, but the image reference written into `xy-dast-compose.yml` is a mutable tag (e.g. `xygeni/xy-dast:6.7.0`).

For production environments — and any setting that needs defence-in-depth against registry-side supply-chain attacks — use the **secure install** flow, which pins the image to its immutable digest and verifies the cosign (Sigstore keyless) signature before installing:

```bash
TAG=xygeni/xy-dast:6.7.0     # or :latest

# 1. Pull the image so the digest is in your local image cache.
docker pull "$TAG"

# 2. Verify the cosign signature, signed by the xy-dast GitHub Actions identity.
cosign verify \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity-regexp 'github\.com/xygeni/xy-dast' \
  "$TAG"

# 3. Resolve the immutable digest the registry served us.
DIGEST=$(docker image inspect "$TAG" --format '{{index .RepoDigests 0}}')

# 4. Install. --image rewrites only the `image:` line in xy-dast-compose.yml
#    to pin the resolved digest — wrapper bytes stay byte-identical.
docker run --rm -v ~/.local/bin:/mnt/install "$TAG" install --image "$DIGEST"
```

To upgrade later, repeat the four-step flow with the new version — or run `xy-dast update`, which pulls, verifies and re-pins the wrapper and the image together.

#### How the wrapper works

* It is a pre-built, byte-stable script signed at release time (Authenticode for `.ps1`); `install` copies it byte-exact so the signature is preserved.
* It delegates to `docker compose -f xy-dast-compose.yml run --rm xy-dast …`. Compose handles env forwarding (`XYGENI_TOKEN`, `XYGENI_URL`, `XYGENI_DASHBOARD_URL`, `XYGENI_DAST_DIR`), `network_mode: host`, and `shm_size: 2gb`.
* When you pass `-o <file>`, the wrapper mounts the output directory into the container (read-write) so the report appears on your host filesystem.
* Local input files (`--openapi`, `--postman`, `--graphql`, `--wsdl`, `--url-list`, `--client-cert`, `--auth-config`, `--session-file`, and a `--profile` file) are read from a **base directory** — your current directory by default, or set explicitly with `--base-dir`. It is mounted read-only, so paths must live under it; relative paths resolve against it. This also reaches files referenced *inside* a profile or auth config. Use a `--base-dir` that contains all your inputs, or pass URLs instead.
* It looks for the sidecar at `<wrapper-dir>/xy-dast-compose.yml` by default. Override the location with the `XY_DAST_COMPOSE_FILE` environment variable. To pin a different image, edit the `image:` line in the sidecar or re-run `install --image <ref>`.

### Quick Start <a href="#quick_start" id="quick_start"></a>

#### Let the scanner write your configuration

From 6.14.0, if you would rather be asked than read the options, run the guided setup:

```bash
xy-dast interactive
```

It asks about your target and writes a profile YAML, a ready-to-run command line, or both. Only relevant questions are asked — answering "REST API" skips the crawling questions — and every question has a default, so pressing `Enter` throughout still produces a working configuration. It can also probe the target and propose the profile matching what it finds.

Where a question offers a list, move with the arrow keys or `Tab`, press `1`-`9` to take an option outright, and `Enter` to accept the highlighted one. Single keys carry the commands: `b` back, `s` skip, `d` defaults for everything remaining, `q` quit without writing, `?` help. `Esc` swaps the list for a typed prompt.

At a typed question the same commands are written with a colon — `:b`, `:s`, `:d`, `:q` — and `?` still asks for help. The keys in use are always shown under the question, so there is nothing to memorise.

{% hint style="info" %}
The wizard never asks you to type a password or token. Where a credential is needed it asks for the **name of the environment variable** holding it and writes `${env:VAR}` into the profile, so the generated files are safe to commit.
{% endhint %}

Scan a web application:

```bash
xy-dast scan -u https://example.com
```

Results are uploaded to the Xygeni platform by default. To save a local report instead, use `-o`:

```bash
xy-dast scan -u https://example.com -o report.json
```

Scan a REST API with an OpenAPI specification:

```bash
xy-dast scan -u https://api.example.com \
  -p openapi \
  --openapi https://api.example.com/v3/api-docs \
  --bearer-token env:API_TOKEN
```

Scan a REST API from a Postman collection (v2.x). Use a local file or a URL, and override collection variables with `--postman-vars`:

```bash
xy-dast scan -u https://api.example.com \
  --postman ./api.postman_collection.json \
  --postman-vars "baseUrl=https://api.example.com,apiKey=demo"
```

Scan a Single Page Application:

```bash
xy-dast scan -u https://app.example.com -p spa
```

Scan a GraphQL API:

```bash
# With schema URL
xy-dast scan -u https://api.example.com \
  -p graphql \
  --graphql https://api.example.com/graphql/schema

# With introspection (no schema needed)
xy-dast scan -u https://api.example.com -p graphql
```

Scan a SOAP web service:

```bash
xy-dast scan -u https://api.example.com \
  --wsdl https://api.example.com/service?wsdl
```

Scan a REST API with OAuth2 (client credentials). The scanner obtains a token from the token endpoint before the scan and sends it as a bearer token on every request:

```bash
xy-dast scan -u https://api.example.com \
  --oauth2-token-url https://idp.example.com/oauth/token \
  --oauth2-client-id env:OAUTH_CLIENT_ID \
  --oauth2-client-secret env:OAUTH_CLIENT_SECRET \
  --oauth2-scope api.read
```

The `password` and `refresh_token` grants and advanced options are configured via a [profile](/xygeni-products/dast-security/dast-scanner/dast-scanner-configuration.md).

Seed the scan from traffic you have already recorded with `--navigation`, so endpoints that crawling alone would miss are scanned too. The format is auto-detected: a recorded **browser navigation** — a **Selenium IDE** (`.side`) or a **Chrome DevTools Recorder** (`.json`) recording — is replayed in a real browser to generate the traffic; a recorded **HAR** (from browser DevTools, a proxy, or a test tool) is imported directly:

```bash
# A recorded browser navigation (replayed): Selenium IDE .side …
xy-dast scan -u https://app.example.com --navigation ./browse.side

# … or a Chrome DevTools Recorder export
xy-dast scan -u https://app.example.com --navigation ./recording.json

# A recorded HAR (imported; local file or URL)
xy-dast scan -u https://app.example.com --navigation ./recording.har
```

Use `--navigation-format selenium|chrome-devtools|har` to set the format explicitly when the extension is ambiguous (for example, a `.json` that is neither a DevTools recording nor a HAR).

A `.side` given to `--navigation` is **always** replayed and seeded — it is a navigation input, not a login method, so the profile `script.seedNavigation` setting does not apply to it (that setting gates a scripted *login* `.side`; see [Scripted Browser Login](/xygeni-products/dast-security/dast-scanner/dast-scanner-configuration.md)). The navigation script and an optional scripted login are separate inputs: when both are given, a `.side` navigation is replayed in the same authenticated browser so it can reach gated pages. Only requests on the target's host are kept. Add `--navigation-only` to scan **only** the recorded endpoints (skipping the crawl).

### Usage <a href="#usage" id="usage"></a>

The DAST Scanner is launched using the `xy-dast scan [options]` command.

To view all available options, use the `--help` flag:

```bash
xy-dast scan --help
```

The most important options are:

* **Target URL** (`-u` or `--url`) -- the base URL of the application to scan (required).
* **Tech-stack profile** (`-p` or `--profile`) -- what the target is: `traditional`, `spa`, `openapi`, `graphql`, `soap`, or `cms` (or a custom profile).
* **Scan intensity** (`--intensity`) -- how hard to scan: `quick`, `balanced` (default), `deep`, or `passive`. Overlays attack strength, durations, and CVE / deep-crawl on top of the tech profile.
* **OpenAPI spec** (`--openapi`) -- URL or file path to an OpenAPI/Swagger specification (for REST API scans).
* **Postman collection** (`-pm` or `--postman`) -- URL or file path to a Postman v2.x collection; `--postman-vars` overrides its variables (`key=value`, comma-separated).
* **Recorded traffic** (`--navigation`) -- seed the scan from a recorded browser navigation (`.side`, replayed) or a recorded HAR (imported); `--navigation-only` scans only the recorded endpoints.
* **Output file** (`-o` or `--output`) -- path for the JSON report. Use `-` for stdout.
* **Project name** (`-n` or `--project-name`) -- identifies the project in the Xygeni platform.
* **Upload** -- reports are uploaded to the Xygeni backend by default. Disable with `--no-upload`.
* **Filtering** -- use `--exclude-rules` to skip noisy rules, or `--risk-threshold` to set a minimum severity.

#### Custom Scan Settings

Override timing defaults directly from the command line:

```bash
xy-dast scan -u https://example.com \
  --spider-duration 15 \
  --ajax-spider-duration 10 \
  --active-scan-duration 30 \
  --timeout 90 \
  -o report.json
```

Durations accept `30s` / `5m` / `1h` (a bare number is minutes). A duration of **`0` means unlimited** (no timeout for that phase, or no overall cap for `--timeout`) — it never means "skip".

To disable a phase, use an explicit flag — `--passive-only` (alias `--skip-active-scan`), `--skip-spider`, or `--skip-ajax-spider`:

```bash
# Passive-only scan (no active attacks)
xy-dast scan -u https://example.com --passive-only -o report.json

# Spider + passive only (skip the active scan and the AJAX spider)
xy-dast scan -u https://example.com --passive-only --skip-ajax-spider -o report.json
```

For a production-safe scan packaged as a profile, use `--intensity passive` (crawl + passive analysis, no active attack payloads) with any tech base: `xy-dast scan -u https://app.example.com --profile spa --intensity passive`.

#### Filtering Results

```bash
# Exclude noisy rules by ID
xy-dast scan -u https://api.example.com \
  --exclude-rules 10094,10038 \
  -o report.json

# Only report findings at MEDIUM risk or above
xy-dast scan -u https://api.example.com \
  --risk-threshold MEDIUM \
  -o report.json

# Set attack strength
xy-dast scan -u https://example.com \
  --policy-strength LOW \
  -o report.json
```

#### Deep Crawl

The `--deep-crawl` option runs a headless browser-based crawler before the main scan. It discovers URLs that traditional spiders miss, especially in JavaScript-heavy applications where content is rendered dynamically.

```bash
xy-dast scan -u https://app.example.com --deep-crawl
```

Discovered URLs are fed as seed URLs into the scanner's spider, improving coverage.

You can tune the crawl depth and timeout:

```bash
xy-dast scan -u https://app.example.com \
  --deep-crawl \
  --crawl-depth 5 \
  --crawl-timeout 10m
```

The `deep` intensity (`--intensity deep`) enables deep crawl by default.

#### Vulnerability Check

The `--vuln-check` option runs a template-based vulnerability scanner after the main scan completes. It checks the discovered endpoints against thousands of known CVEs, misconfigurations, and exposures — complementing the active scanning with signature-based detection.

```bash
xy-dast scan -u https://app.example.com --vuln-check
```

Both features can be combined for maximum coverage:

```bash
xy-dast scan -u https://app.example.com -n my-app \
  --deep-crawl --vuln-check
```

Vulnerability check findings appear in the same report as regular scan findings, with detector IDs prefixed by `vuln/` (e.g., `vuln/CVE-2021-44228`). See [DAST Detectors](/xygeni-products/dast-security/dast-detectors.md) for details.

You can filter by severity and control the scan rate:

```bash
xy-dast scan -u https://app.example.com \
  --vuln-check \
  --vuln-check-severity critical,high \
  --vuln-check-timeout 5m
```

The `deep` intensity (`--intensity deep`) enables vulnerability check by default.

#### Out-of-Band Detection (OAST)

Some vulnerabilities never surface in the HTTP response — the proof is a *side effect*: the target opens a connection to a server you control. Blind SSRF, blind XXE, Log4Shell, blind SSTI and out-of-band XSS are detected this way. The scanner injects a payload and, if the target is vulnerable, it calls back to an **OAST server** (out-of-band application security testing). These rules are inert unless an OAST service is configured, so it is **off by default** — enable it explicitly:

```bash
# Same network: the scanner is its own OAST server (no external service)
xy-dast scan -u http://target:8080 --oast-service=callback -o report.json

# Public server (target needs outbound egress)
xy-dast scan -u https://app.example.com --oast-service=boast -o report.json

# Self-hosted server for an internal / air-gapped target
xy-dast scan -u https://app.internal \
  --oast-service=interactsh \
  --oast=https://oast.internal.example.com \
  --oast-token env:OAST_TOKEN \
  -o report.json
```

**Choosing a service.** An OAST callback only works if the target can reach the server *and* the scanner can observe the hit:

| Service      | Best for                                            | Reachability                                        |
| ------------ | --------------------------------------------------- | --------------------------------------------------- |
| `callback`   | Internal targets on the same network as the scanner | Target must route back to the **scanner** container |
| `boast`      | Public targets (zero-config public server)          | Target needs outbound internet egress               |
| `interactsh` | Public or **self-hosted**                           | Public egress, or a self-hosted server both reach   |

`callback` is the simplest and most reliable choice for internal targets and detects the HTTP-based out-of-band rules (blind SSRF, blind XXE). For a **self-hosted** OAST — the option for internal targets with no public egress that cannot route back to the scanner — run an [Interactsh](https://github.com/projectdiscovery/interactsh) server both the target and the scanner can reach, and point `--oast` at it. Tokens are read from the environment (`env:VAR` / `${env:VAR}`) and never logged. See [DAST Scanner Configuration](/xygeni-products/dast-security/dast-scanner/dast-scanner-configuration.md#out-of-band-detection-oast) for the profile block, per-rule detection notes, and self-hosting guidance.

#### Automatic Profile Selection

If you are unsure which profile fits your target, use `--auto-profile` to let the scanner probe the application and select the most appropriate profile:

```bash
xy-dast scan -u https://app.example.com --auto-profile
```

This detects the technology stack (e.g., React SPA, REST API with OpenAPI, or a WordPress/Drupal/Joomla site → `cms`) and selects the corresponding **tech base**. If `--profile` is also specified, it takes precedence. `--intensity` still applies on top of the auto-detected base — e.g. `--auto-profile --intensity deep`.

To see the available profiles:

```bash
xy-dast scan --list-profiles
```

#### Incremental Scans

Re-test only the endpoints that changed since a baseline — driven by Xygeni's API Security scan — for a large wall-time saving on pull-request pipelines, then merge with the previous report so the output is still a complete snapshot. Off by default; enable with `--incremental`. See [**Incremental DAST Scanning**](/xygeni-products/dast-security/dast-scanner/incremental-scanning.md) for the full workflow, the `--baseline-report` merge, and the full-scan fallback.

#### Rate Limiting

By default a scan runs as fast as the target accepts. Against production or rate-sensitive targets that can degrade the service, trip a WAF, or get the scanner's address banned — so a scan can be told how hard to push:

```bash
# Cap the whole scan at 5 requests per second
xy-dast scan -u https://example.com --rate-limit 5 -o report.json

# Or say it as a delay between requests, whichever is more natural
xy-dast scan -u https://example.com --request-delay 200 -o report.json

# Gentler still: one request at a time
xy-dast scan -u https://example.com --rate-limit 5 --scan-threads 1 -o report.json
```

The limit applies to **every phase** — active scan, crawl, deep crawl and vulnerability check alike. `--rate-limit` and `--request-delay` are two spellings of one setting (`--rate-limit 5` is `--request-delay 200`); set whichever fits, not both, and each phase uses the form it can honour exactly.

Every request is paced on a **single shared schedule**, so the figure you set is a ceiling for the scan as a whole rather than a per-phase or per-thread one. Sustained throughput sits below the ceiling — a scan spends time waiting on responses and between phases — while the peak in any one second stays at the rate you configured, which is what matters for a fragile target.

Two things worth knowing:

* **`--scan-threads` is the only lever the crawl has besides pacing.** The crawler exposes no per-request delay of its own, so concurrency bounds how many requests it can have in flight. Setting it also lowers the active scan's threads per host.
* **Pacing applies to browser-driven crawling too.** Under AJAX spidering, page resources are fetched through the same schedule, so a low rate slows page loads as well as navigation. On a JS-heavy application prefer bounding `--scan-threads` over setting a very low rate.

The vulnerability check can be exempted so a careful overall pace does not hold back the CVE sweep:

```bash
xy-dast scan -u https://example.com --rate-limit 5 --vuln-check-rate-limit 50 -o report.json
```

Only that flag exempts it. A profile's `vulnCheck.rateLimit` sets the sweep's speed when nothing was asked for on the command line, but never overrides an explicit `--rate-limit`, `--request-delay`, or a limit discovered adaptively — a file's default must not hold the CVE sweep at full speed against a target the rest of the scan is treading carefully around.

**Letting the target set the pace.** Where the target states its own limit, `--adaptive-rate-limit` uses that instead of a number you had to guess:

```bash
xy-dast scan -u https://example.com --adaptive-rate-limit -o report.json
```

* **Probes first** for advertised rate-limit headers (`RateLimit-*`, `X-RateLimit-*`) and adopts what it finds, keeping a safety margin below the stated ceiling so a shared counter or a misaligned window does not push the scan over it.
* **Backs off during the scan** when the target answers `429`, or `503` with `Retry-After`, waiting as long as it asked and easing back up once it stops complaining.
* **Slows before being blocked**, stretching the remaining quota to last until the limit resets rather than sprinting into a wall of rejections.

An explicit `--rate-limit` or `--request-delay` always wins: adaptive mode fills in a limit you did not state, it does not override one you did.

{% hint style="warning" %}
**A throttled request tests nothing.** Without a signal, a scan that was rate-limited into near-silence looks exactly like a scan of an application with nothing wrong with it. A throttled scan therefore warns when the target pushed back, and records three report properties:

`dast.ratelimit.paced` matters most in CI: those responses are all successes, so a target advertising a nearly-exhausted quota can hold a scan to a crawl while refusing nothing at all. Gate on **non-zero**, not on the precise value — the counts are lower bounds, since reporting is batched. On a scan with no limit at all the properties are **absent rather than zero**; absence means "not measured", not "never throttled".

```bash
jq -r '.metadata.reportProperties."dast.ratelimit.throttled" // 0' report.json
```

{% endhint %}

| Property                   | Meaning                                                                |
| -------------------------- | ---------------------------------------------------------------------- |
| `dast.ratelimit.throttled` | Responses the target **refused** (429, or 503 with `Retry-After`)      |
| `dast.ratelimit.paced`     | **Successful** responses whose rate-limit headers slowed the scan down |
| `dast.ratelimit.enforced`  | `false` when throttling was requested but never took effect            |

Two intensity overlays set pacing by default: **`deep`** throttles (10 req/s, 2 threads) because it is long-running and trades throughput for politeness, and **`passive`** turns on adaptive mode without imposing a fixed limit, so a target that never pushes back is still scanned at full speed. `quick` and `balanced` leave pacing unset. The equivalent profile block is `rateLimit` — see [Rate Limiting](/xygeni-products/dast-security/dast-scanner/dast-scanner-configuration.md#rate-limiting) in the configuration reference.

#### Identifying the Scan's Traffic

Every scan stamps one header on every request it sends, so the team that owns the application under test can tell the scanner's traffic apart in their own access logs:

```
X-Xygeni-DAST: scan-20260821-143052
```

The **presence** of the header identifies DAST traffic; the **value** identifies one particular scan and states when it started (`scan-<yyyyMMdd>-<HHmmss>`, in UTC). The value is stamped once per run, so every request belonging to a scan carries the same one, and it is recorded in the report as the `dast.request.marker` property — a report and the target's logs can therefore be lined up from either side.

Every phase that talks to the target sends it: the crawl, the active scan, the deep crawl, the vulnerability check, the pre-flight probes, and the browser that replays a recorded login or navigation.

To pick the traffic out of an access log:

```bash
# All DAST traffic, from any scan
grep 'X-Xygeni-DAST' access.log

# Just the scan that started 2026-08-21 14:30:52 UTC
grep 'X-Xygeni-DAST: scan-20260821-143052' access.log
```

Your log format has to record the header for this to work — most servers omit unknown request headers by default. In nginx it is available as `$http_x_xygeni_dast`, and in Apache as `%{X-Xygeni-DAST}i`.

Filtering by **source IP** is not an alternative the scanner can offer: the requests come from the host running the scanner, every phase shares that address, and in CI it is usually a shared NAT pool.

{% hint style="warning" %}
**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. Use it to *recognise* traffic — correlating a scan with your logs, tagging entries in a SIEM, explaining a traffic spike after the fact — and never to decide what *happens* to traffic. Suppressing alerts, dropping requests, allowlisting through a WAF, or skipping authorization for requests that carry it all build a filter an attacker can put themselves inside.

Where a scan genuinely needs different treatment, gate it on something the sender cannot choose — the scanner's **source address**, allowlisted for the duration of the engagement. If a header is also wanted, give the marker a value only you and the target know, with `--request-header "X-Xygeni-DAST: env:SCAN_SECRET"`, and rotate it as you would any credential.
{% endhint %}

#### Custom Request Headers

`--request-header` adds any other header to the same set — a partner id, a tracing header, or a token an API gateway in front of the target requires:

```bash
xy-dast scan -u https://example.com \
  --request-header "X-Trace-Id: nightly-42" \
  --request-header "X-Partner: env:PARTNER_ID" \
  -o report.json
```

* Repeatable. Both `"Name: Value"` and `Name=Value` are accepted.
* `env:VAR_NAME` reads the value from an environment variable, keeping a token out of the command line and the process list.
* Naming `X-Xygeni-DAST` changes the marker's value. Giving it an **empty** value — `--request-header "X-Xygeni-DAST:"` — suppresses the header entirely, for a target that rejects unknown headers.
* These are not authentication headers. They combine with every authentication method, including form login, and a header that collides with the one the authentication injects is ignored rather than overriding it. `Authorization`, `Proxy-Authorization` and `Cookie` are rejected outright: setting one would *replace* the scan's own credentials or session rather than add to them, leaving the scan running unauthenticated behind a report that looks clean. Use the [authentication options](#authentication) for those.

The equivalent profile block is `requestHeaders` — see [Identifying Scan Traffic](/xygeni-products/dast-security/dast-scanner/dast-scanner-configuration.md#identifying-scan-traffic) in the configuration reference.

### Built-in Scan Profiles <a href="#profiles" id="profiles"></a>

Profiles have **two independent axes** — pick a **tech base** with `--profile` and a **scan intensity** with `--intensity`, and combine them freely.

**Tech base** (`--profile`) — *what the target is*. Controls the crawl, endpoint import, and scan policy. Auto-selectable with `--auto-profile`.

| Tech base     | Best for                                 | Crawl                           | Endpoint import                    |
| ------------- | ---------------------------------------- | ------------------------------- | ---------------------------------- |
| `traditional` | Server-rendered apps (PHP, JSP, ASP.NET) | Spider + moderate browser crawl | —                                  |
| `spa`         | JS-heavy SPAs (React, Angular, Vue)      | Heavy browser (AJAX) crawl      | —                                  |
| `openapi`     | REST APIs with an OpenAPI spec           | Minimal spider, no browser      | OpenAPI spec (API-Scan policy)     |
| `graphql`     | GraphQL APIs                             | Minimal spider, no browser      | Schema import / introspection      |
| `soap`        | SOAP web services                        | Minimal spider, no browser      | WSDL                               |
| `cms`         | WordPress / Drupal / Joomla              | Server-rendered crawl           | — (CVE checking on, CMS templates) |

**Scan intensity** (`--intensity`) — *how hard*. Overlays attack strength, thresholds, phase durations, and CVE / deep-crawl on top of the tech base.

| Intensity            | Attack strength             | CVE check | Deep crawl | Active scan                                       | Pacing                   |
| -------------------- | --------------------------- | --------- | ---------- | ------------------------------------------------- | ------------------------ |
| `quick`              | Low                         | off       | off        | on (fast, no browser crawl)                       | unset                    |
| `balanced` (default) | Medium                      | off       | off        | on                                                | unset                    |
| `deep`               | Insane (extended durations) | **on**    | **on**     | on                                                | 10 req/s, 2 threads      |
| `passive`            | —                           | off       | off        | **skipped** (production-safe, no attack payloads) | adaptive, no fixed limit |

**Composition recipes:**

| Goal                                      | Command                                |
| ----------------------------------------- | -------------------------------------- |
| Deep scan of a REST API                   | `--profile openapi --intensity deep`   |
| Fast CI gate on an SPA                    | `--profile spa --intensity quick`      |
| Production-safe scan (no attack payloads) | `--profile <tech> --intensity passive` |
| Auto-detect the stack, deep intensity     | `--auto-profile --intensity deep`      |

{% hint style="info" %}
If no `--intensity` is given, `balanced` is used. Passing an intensity name directly to `--profile` (e.g. `--profile deep`) is accepted as shorthand for the `traditional` base at that intensity.
{% endhint %}

List all available profiles (including custom ones):

```bash
xy-dast scan --list-profiles
```

{% hint style="info" %}
Custom profiles can be placed in `$XYGENI_DAST_DIR/profiles/`, `./profiles/`, or `./conf/profiles/`. See [DAST Scanner Configuration](/xygeni-products/dast-security/dast-scanner/dast-scanner-configuration.md) for the full profile schema and examples.
{% endhint %}

### Authentication <a href="#authentication" id="authentication"></a>

The DAST scanner supports authenticated scanning to test areas of the application behind login.

#### Form-Based Login

```bash
xy-dast scan -u https://app.example.com \
  --login-url https://app.example.com/login \
  --username testuser \
  --password testpass \
  -o report.json
```

#### Bearer Token

Read the token from an environment variable to avoid exposing secrets in command history:

```bash
export API_TOKEN=your-secret-token
xy-dast scan -u https://api.example.com \
  --bearer-token env:API_TOKEN \
  -o report.json
```

#### API Key / Custom Header

For APIs that authenticate via a custom header (e.g., `X-API-Key`):

```bash
xy-dast scan -u https://api.example.com \
  --api-key-header X-API-Key \
  --api-key-value env:MY_API_KEY \
  -o report.json
```

#### HTTP Basic Authentication

For targets protected with HTTP Basic auth (RFC 7617):

```bash
xy-dast scan -u https://app.example.com \
  --basic-username admin \
  --basic-password env:BASIC_PASS \
  -o report.json
```

#### Client Certificate (mTLS)

For targets that require mutual TLS, supply a PKCS#12 (`.p12` / `.pfx`) certificate. The password is read from an environment variable and is redacted from any log output:

```bash
export CERT_PASSWORD=cert-secret
xy-dast scan -u https://mtls.example.com \
  --client-cert /path/to/client.p12 \
  --client-cert-password env:CERT_PASSWORD \
  -o report.json
```

mTLS is orthogonal to the other authentication methods — combine it with `--bearer-token`, `--api-key-*`, `--basic-*`, or form login when the target requires both transport-level and application-level auth. The certificate path can also be set via the `clientCertificate` block in a profile YAML.

#### OAuth2

The scanner obtains a token from the OAuth2 token endpoint before the scan and injects it as a bearer token on every request:

```bash
xy-dast scan -u https://api.example.com \
  --oauth2-token-url https://idp.example.com/oauth/token \
  --oauth2-client-id env:OAUTH_CLIENT_ID \
  --oauth2-client-secret env:OAUTH_CLIENT_SECRET \
  --oauth2-scope api.read \
  -o report.json
```

The `password` and `refresh_token` grants (and provider-specific options) are configured via the `authentication` block in a profile YAML — see [DAST Scanner Configuration](/xygeni-products/dast-security/dast-scanner/dast-scanner-configuration.md).

For long scans where a short-lived token would expire before the scan finishes, add `--token-refresh` so the scanner renews the token during the scan and keeps authenticated coverage:

```bash
xy-dast scan -u https://api.example.com \
  --oauth2-token-url https://idp.example.com/oauth/token \
  --oauth2-client-id env:OAUTH_CLIENT_ID \
  --oauth2-client-secret env:OAUTH_CLIENT_SECRET \
  --token-refresh \
  -o report.json
```

The `refresh_token` grant and finer token-renewal settings are configured via the profile `tokenLifecycle` block — see [DAST Scanner Configuration](/xygeni-products/dast-security/dast-scanner/dast-scanner-configuration.md).

#### Pre-authenticated Session Import (SSO)

For OIDC/SAML SSO targets (Okta, Microsoft Entra ID, …), authenticate out-of-band and supply the resulting session cookies/headers — the scanner sends them on every request:

```bash
xy-dast scan -u https://app.example.com \
  --session-cookie "SESSION=env:SESSION_ID" \
  --session-file session.json \
  -o report.json
```

`--session-file` also accepts a **Playwright `storageState.json`** directly — the artifact produced by `context.storage_state()` in an existing Playwright login script. Its cookies are imported, and a JWT found in local storage is turned into an `Authorization: Bearer` header, so a session captured by a Playwright test can drive an authenticated scan with no conversion:

```bash
xy-dast scan -u https://app.example.com \
  --session-file storageState.json \
  -o report.json
```

Opaque (non-JWT) local-storage tokens are **not** auto-mapped — the target header is ambiguous — so inject those explicitly with `--session-cookie` or a header.

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

For logins that simple form/bearer auth cannot express (identifier-first or multi-page flows, JavaScript-gated logins, SSO), provide a recorded **Selenium IDE (`.side`)** script via an auth-config file. The scanner drives a real browser through the login and reuses the captured session for the scan:

```bash
xy-dast scan -u https://app.example.com \
  --auth-config auth.yaml \
  -o report.json
```

```yaml
# auth.yaml
authentication:
  method: script
  script:
    engine: selenium            # Selenium today; other frameworks (e.g. Playwright) planned
    file: auth/login.side
    vars: { USERNAME: "${env:DAST_USER}", PASSWORD: "${env:DAST_PASS}" }
```

See [DAST Scanner Configuration](/xygeni-products/dast-security/dast-scanner/dast-scanner-configuration.md) for capture/extraction options.

{% hint style="warning" %}
Always use `env:VAR_NAME` syntax for secrets (tokens, passwords, API keys, certificate passwords) rather than passing values directly on the command line, to prevent accidental exposure in shell history and process listings.
{% endhint %}

### CI/CD Integration <a href="#cicd" id="cicd"></a>

Use `--quiet` for minimal output and `--fail-on` to gate builds on vulnerability severity:

```bash
xy-dast scan -u https://example.com \
  --quiet \
  --fail-on high \
  -o report.json

# Exit code 128 means alerts were found at or above the threshold
if [ $? -eq 128 ]; then
  echo "Security vulnerabilities found!"
  exit 1
fi
```

The `--quiet` flag outputs a single summary line:

```
Scan completed in 5m 23s | 42 URLs | 15 alerts (2 critical, 5 high, 4 low, 4 info)
```

Pipe JSON to stdout for downstream processing:

```bash
xy-dast scan -u https://example.com --quiet --output - | jq '.vulnerabilities | length'
```

#### SARIF output for GitHub Code Scanning

`--format sarif` produces SARIF v2.1.0 output that GitHub Code Scanning, Azure DevOps, the VS Code SARIF Viewer, and most CI/CD security dashboards ingest natively. Findings appear in the repo's **Security → Code scanning** tab alongside SAST/SCA results.

```yaml
# .github/workflows/dast.yml
- name: Run xy-dast scan
  run: |
    xy-dast scan -u https://staging.example.com \
      --format sarif \
      --branch ${{ github.ref_name }} \
      --no-upload \
      -o dast.sarif

- name: Upload SARIF to GitHub Code Scanning
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: dast.sarif
    category: dast/xygeni-dast
```

{% hint style="info" %}
DAST findings reference HTTP URLs rather than source-tree files, so they appear in the Security tab but **do not** produce inline PR annotations. This is a known limitation of all DAST tooling that emits SARIF.
{% endhint %}

The `--format` flag only affects the file written by `-o`. The Xygeni backend upload payload (when `--no-upload` is omitted) is always Xygeni JSON regardless of `--format`.

#### Saving raw scan artifacts

Use `--keep-details` to save the underlying scanner output and the generated automation plan alongside the report. This is the easiest way to debug a scan that does not produce expected findings:

```bash
xy-dast scan -u https://example.com -o report.json --keep-details
# Creates: report.json, report.scan.json, report.plan.yml
```

#### GitHub Actions Example

```yaml
- name: DAST Scan
  run: |
    xy-dast scan \
      -u ${{ vars.APP_URL }} \
      --profile spa \
      --fail-on high \
      --quiet \
      -n ${{ github.repository }} \
      -o dast-report.json
  env:
    XYGENI_TOKEN: ${{ secrets.XYGENI_TOKEN }}

- name: Upload DAST Report
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: dast-report
    path: dast-report.json
```

#### GitLab CI Example

```yaml
dast_scan:
  image: xygeni/xy-dast:latest
  stage: test
  script:
    - xy-dast scan
        -u $APP_URL
        --profile spa
        --fail-on high
        --quiet
        -n $CI_PROJECT_NAME
        -o dast-report.json
  artifacts:
    paths:
      - dast-report.json
    when: always
  variables:
    XYGENI_TOKEN: $XYGENI_TOKEN
```

### Command Reference <a href="#command_reference" id="command_reference"></a>

```
Usage: xy-dast scan [OPTIONS] -u <url>

Target Options:
  -u, --url=<url>           Base URL of the target application (required)
  --context-name=<name>     Scanner context name
  --openapi=<url>           OpenAPI/Swagger specification URL
  --graphql=<url|file>      GraphQL schema URL or file (introspection if omitted)
  --wsdl=<url|file>         WSDL definition URL or file for SOAP web services
  -pm, --postman=<url|file> Postman collection (v2.x JSON) URL or file
  --postman-vars=<k=v,...>  Override Postman variables (comma-separated key=value)
  --navigation=<url|file>   Recorded navigation to seed the scan (.side and Chrome
                            DevTools Recorder .json replayed, .har imported)
  --navigation-format=<fmt> Format of --navigation: selenium|chrome-devtools|har
                            (default: auto-detect)
  --navigation-only         Scan only the recorded --navigation endpoints (skip crawl)
  --url-list=<file>         File with additional URLs
  --include=<patterns>      URL patterns to include (regex)
  --exclude=<patterns>      URL patterns to exclude (regex)

Scan Options:
  -p, --profile=<name>      Tech-stack profile: traditional, spa, openapi,
                            graphql, soap, cms, or custom
  --intensity=<name>        Scan intensity: quick, balanced (default), deep,
                            passive
  --auto-profile            Auto-detect target technology and select tech profile
  --list-profiles           List available profiles and exit
  --timeout=<duration>      Overall scan timeout (default: 60m)
  --spider-duration=<dur>   Override spider duration
  --ajax-spider-duration=<dur>  Override AJAX spider duration
  --active-scan-duration=<dur>  Override active scan duration
  --passive-only            Run only passive scan
  --lenient                 Continue scan despite OpenAPI validation errors
  --policy-strength=<level> Attack strength: LOW, MEDIUM (default), HIGH,
                            INSANE
  --exclude-rules=<ids>     Comma-separated rule IDs to exclude
  --risk-threshold=<level>  Minimum risk level: HIGH, MEDIUM, LOW, INFO
  --request-header=<h>      Extra request header sent by every phase, as
                            "Name: Value" (repeatable; supports env:VAR_NAME).
                            Every scan already sends X-Xygeni-DAST:
                            scan-<yyyyMMdd>-<HHmmss> (UTC); name it here to
                            change its value, or give it an empty value to
                            suppress it

Rate Limiting:
  --rate-limit=<rps>        Maximum requests per second across the scan
                            (default: unlimited)
  --request-delay=<ms>      Delay in milliseconds between requests
                            (default: none). Mutually derivable with
                            --rate-limit
  --scan-threads=<n>        Concurrent requests per host (default: tool
                            defaults)
  --adaptive-rate-limit     Infer the target's own rate limit and back off
                            when it throttles. Probes for advertised
                            rate-limit headers before scanning and honours
                            429/503 and Retry-After during the scan

Deep Crawl:
  --deep-crawl              Run deep crawl before scanning to discover
                            URLs with headless JS support
  --crawl-depth=<n>         Deep crawl max depth (default: 3)
  --crawl-timeout=<dur>     Deep crawl timeout (default: 5m)

Vulnerability Check:
  --vuln-check                   Run vulnerability check after scanning for
                                 CVE detection and known vulnerability checks
  --vuln-check-severity=<s>      Severity filter (default: critical,high,medium)
  --vuln-check-rate-limit=<n>    Requests per second (default: 50)
  --vuln-check-timeout=<dur>     Vulnerability check timeout (default: 15m)

Out-of-Band Detection (OAST):
  --oast-service=<kind>     Enable out-of-band detection: interactsh, boast,
                            callback, none (default: none; inferred as
                            interactsh when --oast is given)
  --oast=<url>              OAST server URL (Interactsh/BOAST) or callback
                            advertised address
  --oast-token=<value>      Self-hosted OAST server auth token (supports env:VAR_NAME)
  --oast-poll-seconds=<n>   Poll frequency for interactsh/boast
  --oast-callback-port=<n>  Advertised callback port (0 = random); callback only

Authentication:
  --login-url=<url>         Login form URL
  --username=<user>         Form authentication username
  --password=<pass>         Form authentication password
  --username-field=<name>   Username field name (default: username)
  --password-field=<name>   Password field name (default: password)
  --bearer-token=<token>    Bearer token (supports env:VAR_NAME)
  --api-key-header=<name>   Header name for API key auth (e.g., X-API-Key)
  --api-key-value=<value>   Header value for API key auth (supports env:VAR_NAME)
  --basic-username=<user>   Username for HTTP Basic authentication
  --basic-password=<pass>   Password for HTTP Basic auth (supports env:VAR_NAME)
  --client-cert=<file>      PKCS#12 client certificate for mTLS targets
  --client-cert-password=<pw> Certificate password (supports env:VAR_NAME)

Incremental Scan:
  --incremental             Scan only endpoints changed since a baseline
                            (seed the changed set, skip the crawl); falls
                            back to a full scan when none are available
  --changed-endpoints-file=<file>
                            Changed-endpoint manifest from
                            'xygeni apisecurity --incremental' (required)
  --baseline-report=<file>  Prior DAST report to carry unchanged-endpoint
                            findings forward from (tagged 'kept-untested')

Output Options:
  -o, --output=<file>       Output file (use '-' for stdout)
  --format=<json|sarif>     Output format for the file written by -o
                            (default: json). 'sarif' emits SARIF v2.1.0
                            for GitHub Code Scanning. The Xygeni upload
                            payload is always JSON.
  -n, --project-name=<name> Project name for report
  --pretty                  Pretty-print the report (JSON or SARIF)
  --work-dir=<dir>          Working directory for artifacts
  --no-upload               Disable report upload to Xygeni backend
  --branch=<name>           Branch name for report upload
  --keep-details            Keep raw scanner output (.scan.json) and
                            generated automation plan (.plan.yml)
  -q, --quiet               Suppress progress output, show only summary
  --fail-on=<severity>      Exit with code 128 if alerts at or above severity
                            (info, low, high, critical)

Global Options:
  -v, --verbose             Enable verbose output
  -nb, --no-banner          Suppress the startup banner
  -h, --help                Show help message
  -V, --version             Show version information
```

### Exit Codes <a href="#exit_codes" id="exit_codes"></a>

| Code | Description                                |
| ---- | ------------------------------------------ |
| 0    | Scan completed successfully                |
| 1    | General error                              |
| 2    | Scanner engine not found                   |
| 3    | Scanner engine execution failed            |
| 4    | Invalid input arguments                    |
| 128  | Alert threshold exceeded (see `--fail-on`) |

### Environment Variables

| Variable               | Description                                                                                                                                            | Default                             |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------- |
| `XYGENI_TOKEN`         | API access token (required for report upload)                                                                                                          | --                                  |
| `XYGENI_URL`           | Xygeni API endpoint                                                                                                                                    | `https://api.xygeni.io`             |
| `XYGENI_DASHBOARD_URL` | Xygeni dashboard URL                                                                                                                                   | `https://in.xygeni.io/dashboard`    |
| `XYGENI_DIR`           | Base directory for logs                                                                                                                                | Current directory                   |
| `XYGENI_DAST_DIR`      | Configuration directory (containing `conf/`)                                                                                                           | Script directory                    |
| `XY_DAST_COMPOSE_FILE` | Override the location of the wrapper's `xy-dast-compose.yml` sidecar (which holds the image reference, environment forwarding, and runtime parameters) | `<wrapper-dir>/xy-dast-compose.yml` |
| `PROXY_HOST`           | Proxy hostname                                                                                                                                         | --                                  |
| `PROXY_PORT`           | Proxy port                                                                                                                                             | `3128`                              |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.xygeni.io/xygeni-products/dast-security/dast-scanner.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
