Reference

CI/CD and automation

Run SAST, DAST and infrastructure scans from GitLab, GitHub, Jenkins, Azure DevOps, Bitbucket or a script with a team API token, test the token with --check, and fail the build on what they find.

View as Markdown

scansuite-ci starts a ScanSuite scan from a CI job, a script or a terminal, waits for it, and fails the job when the scan finds confirmed vulnerabilities or secrets. It runs static analysis of the code (the default), dynamic scans of a running web application, and infrastructure scans of hosts, networks and container images. The scan runs on the ScanSuite server, so the CI runner needs only Python 3.8 or newer, or the client's container image appsec4u/scansuite-ci:1. This page covers client version 1.4.5.

ScanSuite Teams

The client talks to the team API, so it needs ScanSuite Teams.

The steps below set up one pipeline. GitLab CI step by step walks through the same setup in GitLab with screenshots and tested recipes.

Agents in CI

Running a coding agent in the pipeline instead of the client? Follow No user in the loop in Connect your AI agent: the agent takes the token from the CI secret store, never from a file it writes.

Before you start

You needHow to check
A ScanSuite Teams server the runner reaches over HTTPScurl -fsS <YOUR_SCANSUITE_URL>/ci/version answers with JSON that names the client version the server publishes.
The Team admin role in the team the pipeline scans forThe Teams page says Your role in this team: Team admin. A team admin issues the token; the rest can be done by anyone who can edit the pipeline.
A product for the resultsIt is listed on the Products page. Or let the first run create it (--create-product, see Products).
For the *-ai bundles and AI scanners: an AI provider for the teamAfter setup, --list-scanners marks mlsast NOT AVAILABLE with "No AI provider is set up for this team" when there is none. Set one up on the Teams page, AI tab; see AI providers and cost.
For the *-classic bundles, the DAST profiles and the classic infrastructure scanners: the scanner images on the serverOn the server, in the installation directory: grep SCANSUITE_SCANNERS .env prints static, dynamic or all. A default installation prints none: a system administrator adds them with ./scansuite update --static-only, --dynamic-only or --all-scanners.
For DAST and infrastructure scans: the targets allowed for the teamTeams page, Scanning tab, Web targets and Infrastructure targets. Check whether targets are allowed tests a list of them.

Setup

The pipeline signs in with the token of a service account, not a person's password. A service account belongs to the team and keeps working when people leave.

  1. 01
    Create a service account

    Teams in the sidebar, then the People tab, Automation and API tokens card. Under New service account type a name such as ci, set Role to Operator (the default, Reader, cannot start scans) and select Create. Check: the account appears in the card with the role Operator and the badge Active.

  2. 02
    Note the team name

    The line at the top of the same card reads: This team's name in pipelines and API addresses (SCANSUITE_TEAM), followed by the name in bold. Use that name, not the team's display name. The first team of an installation is default, whatever it was renamed to.

  3. 03
    Issue a token

    Select New token on the service account. Keep the CI pipeline preset, set Valid for (days) and select Create token. Check: the Your new token dialog opens with the token, the team name and two test commands.

Copy the token from the Your new token dialog and keep it only in your CI system's secret store. It is shown once; if you lose it, revoke it on the card and issue another. Never paste the token into a chat with an assistant, a ticket or the repository.

The Your new token dialog shows, in order: the token with a Copy button; the row This team's name in pipelines and API addresses (SCANSUITE_TEAM) with its own Copy button; and Test the connection with two ready-to-run commands, With the pipeline client (Docker) and With curl, that already contain this server's address, the team and the token. Both commands contain the token, so running them as they are leaves it in your shell history. Prefer the test below, which reads the token without echoing it.

FieldWhat it doesDefaultWhen to change it
Token nameA label shown in the card and in the audit log.empty (required)Name it after the pipeline that uses it, such as gitlab-backend, so it can be revoked alone.
Valid for (days)How long the token works.301 to 90. The job log warns from 14 days before it expires.
What can it do?CI pipeline, Reporting, or Custom (permissions picked one by one).CI pipelineReporting for read-only dashboards. Custom when a script must also triage findings or read assets.

A token can never do more than its service account's role allows. A service account is Reader or Operator.

Test the connection

This test proves that the server answers, takes the token for this team, and that the token can run pipeline scans. It starts nothing.

Run this test in your own terminal: step 2 asks you to paste the token.

  1. 01
    Set the server and team
    Shell
    export SCANSUITE_URL=<YOUR_SCANSUITE_URL> SCANSUITE_TEAM=<YOUR_TEAM>
  2. 02
    Read the token without echoing it (paste it, then press Enter)
    Shell
    read -rs SCANSUITE_TOKEN && export SCANSUITE_TOKEN
  3. 03
    Run the check
    Shell
    docker run --rm -e SCANSUITE_URL -e SCANSUITE_TEAM -e SCANSUITE_TOKEN appsec4u/scansuite-ci:1 --check
Expected output
[scansuite] Connected to https://scansuite.example.com: the server accepts the API token for team default (role operator)
[scansuite] The token may: credential.read, finding.read, product.read, product.write, report.read, scan.cancel, scan.execute, scan.read
[scansuite] The token expires in 30 day(s) (2026-11-18T09:12:44Z)
[scansuite] The token can run pipeline scans

Without Docker, run python3 scansuite-ci.py --check with the client from Getting the client. A token without the permissions a pipeline needs prints WARNING: The token cannot run pipeline scans: it lacks … instead of the last line; issue a new token with the CI pipeline preset. A refused token ends with exit code 3 and the reason:

The message saysFix
the API token has expiredIssue a new token.
the API token was revokedIssue a new token.
the token's service account is deactivatedActivate it on the Teams page, People tab.
the token's team is not active on this serverA system administrator activates the team in System Settings.
the API token belongs to team X, not YSet SCANSUITE_TEAM to the name the message gives: the team name in addresses, not the display name.
this server does not know the API tokenThe token was copied incompletely, or SCANSUITE_URL points at another ScanSuite server.
the token did not reach ScanSuiteA proxy or gateway between the runner and the server removes the Authorization header.
Set SCANSUITE_TOKEN (masked CI variable) …The variable is empty or is not a ScanSuite token (they start with sst_).

Add the variables to your CI system

PlaceholderVariableWhere the value comes fromHow to store it
<YOUR_SCANSUITE_URL>SCANSUITE_URLThe address you open ScanSuite at, for example https://scansuite.example.com.Plain variable
<YOUR_TEAM>SCANSUITE_TEAMThe team name from setup step 2 (also in the new-token dialog).Plain variable
<YOUR_PRODUCT>SCANSUITE_PRODUCTThe product's exact name on the Products page. Or SCANSUITE_PRODUCT_ID with its number.Plain variable
(the token)SCANSUITE_TOKENThe token from setup step 3.Secret: masked, never printed

Add SCANSUITE_TOKEN to the CI system as a masked or secret variable yourself.

Let an agent add the pipeline job
Add a ScanSuite scan to this repository's CI pipeline by following https://scansuite.eu/documentation/reference/cicd. Work out which CI system the repository uses, use the image appsec4u/scansuite-ci:1, and use the merge-request and default-branch bundles the page recommends. Do not put a token anywhere in the repository and do not ask me for it: tell me which secret and variables to add, and stop at every step marked "A person does this step".

Paste it into Claude Code or another coding agent. See Working with AI agents.

Token permissions

Before anything starts, the client checks that the token has every permission the run needs and names any that is missing (exit code 3).

PermissionNeeded for
product.read · See productsAlways: finds the product.
scan.execute · Start scansAlways: starts the scan.
scan.read · See scans and resultsAlways: follows the scan.
finding.read · See findingsAlways: reads the findings for the gate.
credential.read · See discovered credentialsThe secrets gate (--fail-on-secrets new or all, the default). Not needed with --fail-on-secrets none.
scan.cancel · Stop scansCancelling the scan when the job is cancelled or --cancel-on-timeout fires.
report.read · Download reports--report-zip.
product.write · Create and edit products--create-product.
PresetPermissionsUse it for
CI pipelineAll eight permissions above.Pipelines and scripts that scan.
Reportingproduct.read, scan.read, finding.read, report.read, asset.read.Dashboards and exports that only read.
CustomPicked one by one.Scripts that also triage findings (Change findings and write proof-of-concept exploits) or read assets (See assets).

Getting the client

Download it from your ScanSuite server, which serves the version that matches it, and check its checksum:

bash
curl -fsSO <YOUR_SCANSUITE_URL>/ci/scansuite-ci.py
curl -fsS <YOUR_SCANSUITE_URL>/ci/scansuite-ci.py.sha256 | sha256sum -c -
python3 scansuite-ci.py --version

On a server that still has its first, self-signed certificate, curl stops with SSL certificate problem: add -k to these two lines until a real certificate is in place (SSL/TLS Setup).

Expected: scansuite-ci.py: OK, then scansuite-ci 1.4.5 (or the version your server publishes). Or use the container image appsec4u/scansuite-ci:1 (the latest 1.x), where the client runs as scansuite-ci and Git is installed; clear its entrypoint when the CI system runs its own shell in the container. At start-up the client says when the server offers a newer version.

Pipelines by CI system

Each platform runs the same client with the same options. The client reads the repository, branch, commit and merge or pull request target from GitLab CI, GitHub Actions, Jenkins, Azure DevOps and Bitbucket Pipelines.

GitLab CI

Include the template your ScanSuite server publishes. It adds scansuite-merge-request (quick-classic on the changed files of every merge request) and scansuite-default-branch (standard-ai on the default branch), and a hidden .scansuite job to extend for your own jobs.

.gitlab-ci.yml
include:
  - remote: '<YOUR_SCANSUITE_URL>/ci/scansuite.gitlab-ci.yml'

Variables, recipes (DAST, images, nightly, release, monorepo) and GitLab-specific troubleshooting are in GitLab CI step by step.

GitHub Actions

Copy the github/ folder of the scansuite-ci repository into your repository as .github/actions/scansuite, and add the token as the repository secret SCANSUITE_TOKEN. Findings then appear under Security → Code scanning.

.github/workflows/scansuite.yml
name: ScanSuite
on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read
  security-events: write          # SARIF upload

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }  # --changed-only needs the history
      - uses: ./.github/actions/scansuite
        with:
          url: <YOUR_SCANSUITE_URL>
          team: <YOUR_TEAM>
          product: <YOUR_PRODUCT>
          token: ${{ secrets.SCANSUITE_TOKEN }}
          profile: ${{ github.event_name == 'pull_request' && 'quick-classic' || 'standard-ai' }}
          changed-only: ${{ github.event_name == 'pull_request' }}
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: scansuite.sarif }
InputDefaultWhat it does
urlrequiredSCANSUITE_URL.
teamrequiredSCANSUITE_TEAM.
tokenrequiredThe token; pass a secret.
product / product-idemptyThe product's exact name, or its number.
profilestandard-aiThe bundle (see Static analysis).
changed-onlyfalsetrue scans only the files the pull request changes.
basethe pull request baseBranch or commit changed-only compares with.
fail-on-severityhighLowest severity that fails the job.
maxemptyAllowed findings per severity, such as high=0,medium=10.
block-classemptyClasses that fail the job at any severity.
min-confidenceanyreachable: only findings confirmed reachable fail the job.
strict-tlsfalsetrue stops on a self-signed or unknown-CA certificate.
ca-bundleemptyPath in the workspace to your CA certificate.
sarifscansuite.sarifSARIF report path, for upload-sarif.
summary-jsonscansuite.jsonJSON summary path.

The action has no input for --fail-on-secrets, --scan-type, targets, --create-product or other client options. For those, and for DAST and infrastructure scans, run the image as a container job and pass any option:

.github/workflows/scansuite.yml
jobs:
  scan:
    runs-on: ubuntu-latest
    container: appsec4u/scansuite-ci:1
    env:
      SCANSUITE_URL: <YOUR_SCANSUITE_URL>
      SCANSUITE_TEAM: <YOUR_TEAM>
      SCANSUITE_PRODUCT: <YOUR_PRODUCT>
      SCANSUITE_TOKEN: ${{ secrets.SCANSUITE_TOKEN }}
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - run: scansuite-ci --profile quick-classic --changed-only --sarif scansuite.sarif

Jenkins

Add the jenkins folder of the scansuite-ci repository as a Global Pipeline Library (Manage Jenkins → System → Global Pipeline Libraries, for example named scansuite), and store the token as a Secret text credential. The step needs the Docker Pipeline plugin. It publishes the JUnit report and archives the JSON summary.

Jenkinsfile
@Library('scansuite') _
pipeline {
  agent any
  stages {
    stage('ScanSuite') {
      steps {
        scansuiteScan(
          url: '<YOUR_SCANSUITE_URL>', team: '<YOUR_TEAM>', product: '<YOUR_PRODUCT>',
          credentialsId: 'scansuite-ci-token',
          profile: env.CHANGE_ID ? 'quick-classic' : 'standard-ai',
          changedOnly: env.CHANGE_ID != null,
          unstableOnGate: true)
      }
    }
  }
}
OptionDefaultWhat it does
url, teamrequiredSCANSUITE_URL and SCANSUITE_TEAM.
product / productIdemptyThe product's exact name, or its number.
credentialsIdscansuite-ci-tokenThe Secret text credential that holds the token.
profilestandard-aiThe bundle.
changedOnlyfalseScan only the files the change request changes.
basethe change request targetBranch or commit changedOnly compares with.
failOnSeverityhighLowest severity that fails the build.
maxemptyAllowed findings per severity.
blockClassemptyClasses that fail the build at any severity.
minConfidenceanyreachable: only findings confirmed reachable fail the build.
strictTlsfalseStop on a self-signed or unknown-CA certificate.
caBundleemptyPath to your CA certificate.
unstableOnGatefalsetrue marks the build UNSTABLE instead of failed when the gate fails (exit code 1). Other codes still fail it.
imageappsec4u/scansuite-ci:1The client image.
argsemptyMore client options, such as --fail-on-secrets none.

Without the library, use a Docker agent with the image, clear its entrypoint (args '--entrypoint='), map the credential with SCANSUITE_TOKEN = credentials('scansuite-ci-token'), and run scansuite-ci in an sh step.

Azure DevOps

Add SCANSUITE_TOKEN as a secret pipeline variable. Azure DevOps does not pass secrets to scripts on its own, so map it under env. The Microsoft-hosted Ubuntu agents have Python 3 and Git.

azure-pipelines.yml
trigger: [main]
pr: [main]
pool: { vmImage: ubuntu-latest }

variables:
  SCANSUITE_URL: <YOUR_SCANSUITE_URL>
  SCANSUITE_TEAM: <YOUR_TEAM>
  SCANSUITE_PRODUCT: <YOUR_PRODUCT>

steps:
  - checkout: self
    fetchDepth: 0
  - script: |
      set -e
      curl -fsSO "$SCANSUITE_URL/ci/scansuite-ci.py"
      curl -fsS "$SCANSUITE_URL/ci/scansuite-ci.py.sha256" | sha256sum -c -
      if [ "$BUILD_REASON" = "PullRequest" ]; then ARGS="--profile quick-classic --changed-only"; else ARGS="--profile standard-ai"; fi
      python3 scansuite-ci.py $ARGS --junit scansuite-junit.xml --sarif scansuite.sarif
    displayName: ScanSuite
    env:
      SCANSUITE_TOKEN: $(SCANSUITE_TOKEN)
  - task: PublishTestResults@2
    condition: always()
    inputs: { testResultsFormat: JUnit, testResultsFiles: scansuite-junit.xml, testRunTitle: ScanSuite }

Pull request builds (branch policy → build validation) report the target branch, and --changed-only compares with it. A server with a self-signed certificate also needs curl -k for the download.

Bitbucket Pipelines

Add SCANSUITE_TOKEN as a secured repository variable and the others as plain ones. Test results under test-results/ are picked up on their own.

bitbucket-pipelines.yml
image: python:3.12
clone:
  depth: full

pipelines:
  pull-requests:
    '**':
      - step:
          name: ScanSuite (changed files)
          script:
            - curl -fsSO "$SCANSUITE_URL/ci/scansuite-ci.py"
            - curl -fsS "$SCANSUITE_URL/ci/scansuite-ci.py.sha256" | sha256sum -c -
            - python3 scansuite-ci.py --profile quick-classic --changed-only --junit test-results/scansuite.xml
  branches:
    main:
      - step:
          name: ScanSuite
          script:
            - curl -fsSO "$SCANSUITE_URL/ci/scansuite-ci.py"
            - curl -fsS "$SCANSUITE_URL/ci/scansuite-ci.py.sha256" | sha256sum -c -
            - python3 scansuite-ci.py --profile standard-ai --junit test-results/scansuite.xml

Any other CI system, a script or a laptop

Elsewhere (CircleCI, TeamCity, Drone, a cron job) give the client what it cannot detect: the base for --changed-only, and a unique --idempotency-key per run so a retried step does not pick up the previous run's scan.

bash
docker run --rm -v "$PWD:/src" -w /src \
    -e SCANSUITE_URL -e SCANSUITE_TEAM -e SCANSUITE_TOKEN -e SCANSUITE_PRODUCT \
    appsec4u/scansuite-ci:1 \
    --profile quick-classic --changed-only --base origin/main --idempotency-key "build-$BUILD_NUMBER" \
    --junit scansuite-junit.xml

On a laptop, keep the token in a file readable only by you and pass --token-file ~/.scansuite-token instead of the variable. Without Git on the machine, the client archives the directory tree with default exclusions, and --changed-only falls back to a full scan.

Check the first run

The job log starts with the team and product it reports to and the scan's reference, and ends with the verdict:

The last lines of a run
[scansuite] Quality gate passed

or [scansuite] Quality gate FAILED: N blocking finding(s), M blocking secret(s), after one BLOCKING line per finding or secret that failed it, followed by [scansuite] Exit code 1: the quality gate failed …. The scan also appears in ScanSuite under Scan History. If the job ends with another exit code, see Exit codes.

Products

Every scan reports into one product of your team: give its exact name with --product-name (SCANSUITE_PRODUCT) or its number with --product-id (SCANSUITE_PRODUCT_ID), not both. A name that matches no product stops the run with exit code 3.

With --create-product (SCANSUITE_CREATE_PRODUCT=1) the first run creates the product and logs Created product …; later runs find it. The CI pipeline preset allows it. When the team works with DefectDojo, the product is created there as well; if DefectDojo cannot be reached, it is still created in ScanSuite and the run warns. For one product per service of a monorepo, give each job its folder and product: --source-dir services/payments --product-name payments --create-product.

What to run, and when

TriggerScan
Merge or pull request--profile quick-ai --changed-only, or quick-classic --changed-only without AI: the changed files, in minutes.
Merge to the default branch--profile standard-ai, or standard-classic without AI.
Nightly or weekly--profile full-ai, or full-classic without AI. Use the CI system's own schedule.
After a deployment to staging--scan-type dast against the deployed application.
Release, or a change to the hosts--scan-type infra against the servers or the image the release runs on.

Static analysis

Six bundles, three without AI and three with it. --profile or SCANSUITE_PROFILE picks one; standard-ai is the default.

--profileScannersUse it for
quick-classicRule-based Semgrep and secrets. Minutes.Merge requests, without AI.
standard-classicSemgrep with the full local ruleset, secrets and dependency checks.The default branch, without AI.
full-classicEvery classic scanner: Semgrep (full, quick and the team's own rules), the native engine of each language in the repository, KICS for IaC, secrets and dependency checks.Nightly and release, without AI.
quick-aiAI Native SAST without reachability or Git history, and secrets with AI verification.Merge requests, with AI.
standard-ai (default)AI Native SAST with reachability, secrets with AI verification, and dependency checks with reachability.The default branch.
full-aistandard-ai plus security architecture analysis, the cross-file hunt and Git history. The slowest and the costliest in AI.Nightly and release.

The AI bundles report only the secrets AI verification confirmed; when verification could not run for some, the client warns with their number. full-ai analyses Git history, so fetch the full history in the pipeline (GitLab GIT_DEPTH: 0, GitHub fetch-depth: 0). The 1.x names still work: quick is quick-classic, standard is standard-ai, deep is full-ai.

Scanners and options

--scanners replaces a bundle's scanners, --add-scanners adds to them, and --options replaces its options; --no-ai-verification drops the AI options. An option is sent only with its scanner. --list-scanners prints what your server offers, marks what can scan selected files, what runs alone and what is NOT AVAILABLE for your team, with the reason; choosing an unavailable scanner stops the run with exit code 3 before a scan starts.

Scanner IDIn the consoleOptions and notes
mlsastAI Native SASTOptions mlsast_reachability (Verify findings reachability), mlsast_security_architecture (Analyze security architecture), mlsast_boundary_hunt (Cross-file hunt), mlsast_git_history (Analyze Git history). Needs an AI provider.
secretsSecrets DetectionOption secrets_ai (Verify secrets with AI).
dep_checksDependency Check (SCA)Option dep_checks_reachability (Verify findings reachability). dep_checks_ai is still accepted and does nothing.
sast_quickQuick (Semgrep)Can scan selected files.
sast_fullFull (Semgrep with the full ruleset, plus the main language's engine)Can scan selected files.
sast_customCustom Rules (the team's Semgrep rules)Can scan selected files.
iacs_kicsIaCS Analysis (KICS)Can scan selected files.
snykSnyk Scan (Online)Needs a Snyk key on the Teams page, Integrations tab.
python, java, csharp, cpp, php, javascript, typescript, ruby, go, kotlin, swiftThe native engine of one languageUsually chosen through --lang or full-classic.

--lang names the main language instead of the bundle's (multilanguage for all detected languages), and --git-history adds mlsast_git_history (the archive then includes .git).

Only what a merge request changed

--changed-only (SCANSUITE_CHANGED_ONLY=1) scans the files changed since the merge base with the target branch. Merge and pull request pipelines report the target branch themselves; elsewhere give it with --base. When a shallow clone lacks the target branch, the client fetches it. Only scanners that can scan selected files run (mlsast, sast_quick, sast_full, sast_custom, iacs_kics): secrets and dependency checks need the whole repository and are skipped, with a warning when a dependency manifest changed. When nothing changed the job passes; when no base can be found a full scan runs, with a warning. It cannot be combined with --mode or --scope.

Upload or clone

By default (--source auto) the runner uploads its checkout as an archive: only the files Git tracks, from the CI workspace or --source-dir, minus --exclude patterns. The server scans exactly the commit the pipeline runs on, private repositories need no extra access, and findings link to the files in your repository at that commit.

With --source git (chosen automatically when --git-url or --mode incremental is given) the server clones the repository instead, at the branch head when the scan starts. Private repositories are cloned over SSH with the team's key: Teams page, Scanning tab, Team SSH key. Pipeline scans always use the team key, never a person's own key (see Git repository authentication). The URL must not contain credentials. --mode incremental scans only what changed since the previous scan of the branch and passes when nothing did.

--mode custom-scope --scope PATTERN (repeatable, or --scope-file FILE) scans only the files the patterns match; scanners that cannot be limited to files are left out, with a note.

Web application and infrastructure scans

Give the targets with --target (repeatable, or separated by commas, spaces or ;) or SCANSUITE_TARGETS. The team's target policy must allow each one, unless Allow is set to Any target; a refused target stops the run with exit code 3 before anything starts, and the message names it. The rules for targets are on Web App Scanning. The secrets gate does not apply to these scans.

A DAST scan of a staging deployment
scansuite-ci --scan-type dast --target https://staging.example.com \
  --profile standard --header "Authorization: Bearer $STAGING_TOKEN" --junit scansuite-dast.xml
--profile with --scan-type dastScanners
quickdast_quick, tech_discovery
standard (default)dast_quick, nuclei, tech_discovery, dirbust
deepdast_balanced, nuclei, tech_discovery, dirbust, mldast
DAST scanner IDIn the consoleNotes
dast_quickQuick
dast_balancedBalanced
zap_fullDeep ScanIntrusive.
nucleiNuclei
nuclei_customCustom Rules (the team's Nuclei templates)
api_scanAPI Scan
acunetixAcunetixNeeds Acunetix on the Teams page, Scanning tab, Scanner engines.
nessus_webNessus WebNeeds Nessus in System Settings.
tech_discoveryTech Discovery
dirbustHidden Paths
crawlSecrets (crawls pages for leaked keys)
mldastAI DASTNeeds an AI provider.
ai_pentestAutonomous AI assessment (AI Pentest)Needs an AI provider. Guide it with --instructions.

To scan behind a login, pass --header "Name: value" (several separated by |) or --cookie "key=value" (several separated by ;). Keep such values in CI secrets, not in the job file.

Infrastructure scans have no profiles: name the scanners with --scanners.

Infrastructure scanner IDIn the consoleNotes
openvasOpenVASNeeds OpenVAS on the Teams page, Scanning tab, Scanner engines.
nessusNessusNeeds Nessus in System Settings.
hosts_prescanDiscovery pre-scanFinds live hosts before the vulnerability scanners.
nucleiNuclei
nuclei_customCustom Rules
hosts_bruterBruteforcerTries weak and leaked credentials on known services.
ai_pentestAutonomous AI assessment (AI Pentest)Needs an AI provider. Guide it with --instructions.
hosts_scanNetwork DiscoveryRuns alone.
hosts_patchingLocal Patching ChecksRuns alone. Uses the team's server account (Teams page, Scanning tab).
osintDomains OSINTRuns alone. Option osint_brute (Bruteforce hostnames).
docker_image_scanDocker Image ScanRuns alone. The target is an image reference.
Infrastructure scans
# Two hosts, the top 1000 TCP and UDP ports, including hosts that do not answer ping
scansuite-ci --scan-type infra --scanners openvas,nuclei \
  --target 10.0.4.10,10.0.4.11 --ports "Top 1000 TCP/UDP" --no-ping --fail-on-severity critical

# A container image
scansuite-ci --scan-type infra --scanners docker_image_scan \
  --target registry.example.com/shop/api:1.4.2
--ports or SCANSUITE_PORTSPorts scanned
All TCPEvery TCP port. The default.
Top 1000 TCP/UDPThe 1000 most common TCP and UDP ports.
All TCP/UDP (slowest)Every TCP and UDP port.
PCI External ScanThe port set of a PCI external scan.
PCI Internal ScanThe port set of a PCI internal scan.
A port list, such as 22,80,443Exactly these ports, separated by commas, semicolons, spaces or |.

In GitLab, set SCANSUITE_PORTS rather than passing --ports in SCANSUITE_EXTRA_ARGS: that variable is split on spaces, so Top 1000 TCP/UDP would break apart.

When the build fails

Findings from this scan that are Open or In Progress count. Findings AI verification marked unreachable do not, unless you add --include-unreachable. What the classic scanner tools report counts only when the team stores it: Teams page, Scanning tab, Scanner findings in Vulnerabilities (on by default).

OptionWhat fails the build
--fail-on-severity highFindings of this severity or above fail the build: critical, high (default), medium, low, info, or none to turn it off.
--max high=0,medium=10How many findings of each severity are allowed. A severity named here uses its limit instead of --fail-on-severity.
--block-class sql_injection,command_injectionFindings of these classes fail the build at any severity. The class names are the ones findings carry in ScanSuite and in the JSON summary.
--min-confidence reachableOnly findings confirmed reachable, or with a known exploit, fail the build. The default any also counts findings that were not assessed.
--fail-on-secrets newSecrets this run added to the product that are verified, or were never judged, fail the build (the default). all counts every such secret of the product; none turns it off. Static analysis only. Needs credential.read.
--include-unreachableFindings AI verification marked unreachable count too.
  • A target that does not answer is a warning in the scan log, not a failure: the scan ends Finished and the gate decides on what the other targets returned.
  • What two scanners of one scan report for the same issue on the same service is one finding: it blocks once, at the higher severity.
  • If one of the scanners did not complete, the build fails with exit code 2 even when nothing else blocks it; findings from the scanners that did complete still fail it with exit code 1.
  • The first scan of a product sees every secret as new, so it fails on secrets already committed. Triage them in ScanSuite, or start with --fail-on-secrets none.

Exit codes

CodeMeaningWhat to do
0Passed, or there was nothing to scan.—
1The gate failed: findings or secrets over the limits.Read the BLOCKING lines; fix, or triage in ScanSuite.
2The scan failed, was cancelled, or was refused by the server (for example the team's scan limit), or one of its scanners did not complete.Open the scan in ScanSuite; the log names the scanner.
3A configuration, token, permission or target error, or a TLS certificate that failed verification.The line before it says what to change.
4Timed out (--timeout, 7200 seconds by default).Raise --timeout, or the CI job's own time limit.
5ScanSuite was unreachable or answered with an error.Check the runner's network path to SCANSUITE_URL.

A failed run ends with a line naming its exit code and what it means, for example [scansuite] Exit code 3: configuration error: option, token, permission, target or certificate. Some CI systems, GitLab among them, show every failed job as exit code 1; that line tells you which failure it was. --soft-fail turns codes 2, 4 and 5 into 0 with a warning, so a ScanSuite outage does not block a release; codes 1 and 3 still fail the job.

Reports

OptionOutput
--junit FILEJUnit XML, shown as test failures in GitLab, Jenkins and Azure DevOps.
--sarif FILESARIF 2.1.0, for GitHub code scanning, Azure DevOps and IDEs.
--summary-json FILEThe scan, the gate result, the findings and the secrets (never their values).
--report-zip FILEThe full report archive. Needs report.read.

The job log lists every blocking finding and secret with the rule that blocked it; --max-print N limits how many (50 by default).

Waiting and cancelling

The client follows the scan until it ends, checking every 20 seconds for up to two hours. When the CI job is cancelled, the client cancels its scan (the token needs Stop scans, which the CI pipeline preset includes). A request repeated after a network error picks up the scan already started instead of starting another, a busy server is retried, and separate CI runs never share a scan.

TLS certificates

With a self-signed certificate, or one from a CA the runner does not know, the client warns and goes on without verifying it. Give the CA certificate with --ca-bundle to verify it, or add --strict-tls to stop with exit code 3 instead. A CA-signed certificate for another host, or an expired one, always stops the run.

All options

Every option the client takes. Boolean variables accept 1, true or yes. A variable is read only when the option is not given.

Server and product

OptionVariableDefaultWhat it doesWhen to change it
--urlSCANSUITE_URL—The ScanSuite address.Always set.
--teamSCANSUITE_TEAM—The team name in addresses, from the Automation and API tokens card.Always set.
--token-file FILESCANSUITE_TOKENthe variableReads the token from a file instead of the variable.On a laptop or a host where the token is a file.
--product-nameSCANSUITE_PRODUCT—The product's exact name. Cannot be combined with --product-id.Always set one of the two.
--product-idSCANSUITE_PRODUCT_ID—The product's number.When names change.
--create-productSCANSUITE_CREATE_PRODUCToffCreates the --product-name product when the team has none (and in DefectDojo when the team uses it). Needs product.write.New repositories and monorepo services.
--ca-bundle FILESCANSUITE_CA_BUNDLE—Verifies the server's certificate against your CA.A private CA.
--strict-tlsSCANSUITE_STRICT_TLSoffStops (exit 3) on a certificate it cannot verify, instead of warning.When an unverified connection is not acceptable.
--insecure—offTurns certificate verification off from the start.Testing only.
--check——Tests the connection and the token, prints what the token may do, and exits. Starts nothing.After issuing a token, and when a pipeline starts failing with exit code 3.
--list-scanners——Prints the scanners and options the server offers for each scan type, what is not available for the team and why, and the bundles; then exits.Before choosing --scanners or --options.
--no-version-check—offDoes not ask the server whether a newer client exists.Air-gapped runners with a pinned client.
--version——Prints the client version and exits.—

What to scan

OptionVariableDefaultWhat it doesWhen to change it
--scan-typeSCANSUITE_SCAN_TYPEsastsast (source code), dast (web applications) or infra (hosts, networks, container images).DAST and infrastructure jobs.
--targetSCANSUITE_TARGETS—DAST: URLs. Infra: hosts, addresses, ranges or image references. Refused for sast.Always for dast and infra.
--header——DAST: request headers, "Name: value", several separated by |.Scanning behind a login.
--cookie——DAST: cookies, "key=value", several separated by ;.Scanning behind a login.
--portsSCANSUITE_PORTSAll TCPInfra: the ports (see the table above).Faster or PCI scans.
--no-ping—off (ping on)Infra: also scans hosts that do not answer ping.Most Internet-facing hosts.
--instructions TEXT——DAST and infra: guidance for the AI pentest scanner (ai_pentest), such as what to focus on and what is off limits.With ai_pentest.

Static analysis: source and selection

OptionVariableDefaultWhat it doesWhen to change it
--profileSCANSUITE_PROFILEstandard-ai (dast: standard)The bundle. Not used for infra.Per trigger, see What to run.
--scanners—the bundle'sScanner IDs, replacing the bundle's. Required for infra.One scanner, or infra scans.
--add-scanners——Scanner IDs added to the bundle's.Adding snyk or iacs_kics.
--options—the bundle'sOption IDs, replacing the bundle's. --options "" turns them all off.Choosing AI features one by one.
--no-ai-verification—offDrops the AI verification and reachability options.Saving AI cost.
--lang—the bundle's, else multilanguageThe main language.A repository with one language the bundle does not pick.
--git-history—offAdds Git history analysis to AI Native SAST; the archive includes .git.Hunting secrets and risky changes in history (fetch the full history).
--changed-onlySCANSUITE_CHANGED_ONLYoffScans only the files changed against --base.Merge and pull requests.
--baseSCANSUITE_BASEthe merge or pull request targetBranch or commit --changed-only compares with.Outside merge request pipelines.
--mode—onceonce, incremental (Git only: what changed since the last scan of the branch) or custom-scope.Large repositories cloned by the server.
--scope / --scope-file——Patterns for --mode custom-scope; repeatable.Scanning one folder.
--sourceSCANSUITE_SOURCEautozip uploads the checkout; git lets the server clone; auto picks git when --git-url or --mode incremental is given.Large repositories.
--source-dir—the CI workspaceThe directory to archive.Monorepo services.
--exclude GLOB—.git, node_modules and other build folders when not in GitLeaves paths out of the archive; repeatable.Generated or vendored code.
--no-git-ls-files—offArchives the directory tree instead of the files Git tracks.Scanning untracked or generated files.
--max-archive-mb—1024Refuses a larger archive (exit 3).Very large repositories; or use --source git.
--git-urlSCANSUITE_GIT_URLthe CI repositoryThe URL the server clones (HTTPS without credentials, or SSH).With --source git.
--branchSCANSUITE_BRANCHthe pipeline branchThe branch the server clones.Scanning another branch.
--repository-url—from the CI systemThe browsable repository address used in finding links.When the CI system reports none.

--changed-only, --mode, --scope and --git-url are for --scan-type sast only, and --changed-only cannot be combined with --mode or --scope (exit 3).

Waiting

OptionVariableDefaultWhat it doesWhen to change it
--timeoutSCANSUITE_TIMEOUT7200Seconds to wait before exit code 4.Long full-ai scans; also raise the CI job's own limit.
--poll-interval—20Seconds between status checks.Rarely.
--cancel-on-timeout—offCancels the scan when --timeout expires.When a timed-out scan should not keep running.
--no-cancel-on-interrupt—offLeaves the scan running when the CI job is cancelled.When the scan should finish anyway.
--no-wait—offStarts the scan and exits 0 without gating.Fire-and-forget scans after a merge.
--idempotency-keySCANSUITE_IDEMPOTENCY_KEYderived from the CI jobA retried start with the same key returns the same scan.Unknown CI systems, or Jenkins jobs recreated with the same build numbers.

Quality gate

OptionVariableDefaultWhat it doesWhen to change it
--fail-on-severitySCANSUITE_FAIL_ON_SEVERITYhighLowest blocking severity: critical, high, medium, low, info, none.Stricter release gates, or none while rolling out.
--maxSCANSUITE_MAX—Allowed findings per severity, such as high=0,medium=10.A budget instead of zero tolerance.
--block-classSCANSUITE_BLOCK_CLASS—Classes that block at any severity.Classes you never accept, such as sql_injection.
--min-confidenceSCANSUITE_MIN_CONFIDENCEanyreachable: only findings confirmed reachable or with a known exploit block.Release gates on what attackers can reach.
--include-unreachable— (pass it as an option)offFindings marked unreachable block too.When you do not trust the reachability verdict.
--fail-on-secretsSCANSUITE_FAIL_ON_SECRETSnewnew, all or none.all on releases; none for the first scan of an old repository.
--soft-fail—offCodes 2, 4 and 5 become 0 with a warning.When an outage must not block releases.

Output

OptionVariableDefaultWhat it doesWhen to change it
--summary-json FILE——JSON with the scan, gate result, findings and secrets (no values).Dashboards and scripts.
--junit FILE——JUnit XML report.GitLab, Jenkins, Azure DevOps test views.
--sarif FILE——SARIF 2.1.0 report.GitHub code scanning, Azure DevOps.
--report-zip FILE——Downloads the full report archive. Needs report.read.Keeping the evidence with a release.
--print-logs-on-failure—offPrints the end of the scan log when the scan fails.Debugging exit code 2.
--max-print—50How many blocking items the job log lists.Very noisy first scans.

Last reviewed 2026-10-19