Reference

Pipeline scans and the build gate

Starting a scan from a build job with a team API token, and failing the build on what it finds.

scansuite-ci.py runs a static analysis scan from a build job, waits for it, and fails the job when the scan confirms vulnerabilities or secrets. Copy the script into the repository, set three variables, and add one line to the pipeline. It signs in with a team API token rather than a password, and needs nothing installed beyond Python 3.8 or newer.

ScanSuite Teams

The pipeline client talks to the team API, so it needs ScanSuite Teams. The edition without teams drives pipelines with the older scripts described in CI/CD integration.

One-time setup

  1. 01
    Create the product the pipeline reports into

    Note its ID, or use its exact name.

  2. 02
    Create a service account

    Team settings → Service accounts, with the operator role — for example, one called ci.

  3. 03
    Issue a token for it

    At most 90 days, with the permissions below. It is shown once.

  4. 04
    Store the token in the CI system

    As a masked or secret variable named SCANSUITE_TOKEN. SCANSUITE_URL and SCANSUITE_TEAM (the team slug) are ordinary variables.

Token permissionWhy
scan.execute, scan.read, finding.readRequired. Start the scan, follow it, read what it found.
product.readNeeded when the product is named rather than given by ID.
credential.readNeeded for the secrets gate, which is on by default.
scan.cancelOptional. A cancelled pipeline then cancels its scan instead of leaving it running.
report.readOptional, for --report-zip.

The script checks its own permissions before it starts anything, and says which one is missing rather than failing halfway. Service accounts and tokens are covered in Teams and roles.

Where the code comes from

SourceWhat happens
--source zip (default)The runner archives the checkout and uploads it. In a Git checkout only tracked files are sent (git ls-files), so build output stays out; add --exclude GLOB for more. Use this for private repositories: nothing leaves the runner but the archive.
--source git --git-url URLThe server clones the repository itself. The URL and branch default to what the CI system reports. HTTPS URLs must not contain credentials — both the script and the server refuse them, because they would be stored with the scan and shown in finding links. Private repositories use SSH with the team’s repository credential.

With --source git the server scans the branch head at the moment it clones, which may be newer than the commit that started the pipeline. --source zip scans exactly what the runner has.

--mode incremental (Git only) scans what changed since the last compatible scan; when nothing changed the script says so and exits 0. --mode custom-scope --scope PATTERN scans selected files only.

The quality gate

DefaultWhat blocks the build
--fail-on-severity highFails the build on findings of high severity or above from this scan. Findings AI verification marked unreachable are ignored, unless you add --include-unreachable. Use none to turn the severity gate off.
--fail-on-secrets newFails the build on secrets this run added to the product. Use all to count every secret the product has, or none to turn the gate off.

A secret found while AI verification was off is treated as real, because nothing has judged it. Expect the secrets gate to be stricter, not looser, when verification is disabled.

Exit codes

CodeMeaning
0The gate passed, or an incremental scan found nothing new to scan.
1The gate failed: blocking findings, blocking secrets, or both.
2The scan failed, was cancelled, or was refused — the team’s scan limit, a missing credential.
3Configuration, token or permission error.
4Timed out waiting (--timeout, 7200 seconds by default).
5ScanSuite was unreachable or answered with an error.

With --soft-fail, codes 2, 4 and 5 become 0 with a warning, so a ScanSuite outage does not block a release. A failed gate (1) and a misconfiguration (3) still fail the job.

Reports

OptionOutput
--junit FILEJUnit XML, shown as test failures in GitLab and Jenkins.
--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.
--report-zip FILEThe full report archive.

A secret’s value is never returned by the API and never appears in any of these files: the pipeline sees the detector, the place and a short fingerprint that tells two secrets apart.

A GitLab job

yaml
scansuite:
  stage: test
  image: python:3.12-slim
  variables:
    SCANSUITE_URL: https://scansuite.example.com
    SCANSUITE_TEAM: appsec
    SCANSUITE_PRODUCT: my-service      # or SCANSUITE_PRODUCT_ID
  script:
    - python scansuite-ci.py --junit scansuite-junit.xml --summary-json scansuite.json
  artifacts:
    when: always
    reports:
      junit: scansuite-junit.xml
    paths: [scansuite.json]

SCANSUITE_TOKEN comes from the project's masked variables. The python:*-slim images have no git; without it the script walks the directory tree with its default exclusions instead of listing tracked files.

Retries and interruptions

  • Retrying a failed job does not start a second scan: the script recognises the job and picks up the scan it already started.
  • A blip in the network or a busy server is retried on its own.
  • Cancelling the pipeline cancels the scan, if the token has scan.cancel.
  • Add --no-wait when the pipeline should start a scan and move on without waiting for the result.

Run one full scan before switching a pipeline to --mode incremental: incremental scans compare against the last compatible scan, so the first one has to exist.

Last reviewed 2026-09-20