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.
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
- 01Create the product the pipeline reports into
Note its ID, or use its exact name.
- 02Create a service account
Team settings → Service accounts, with the operator role — for example, one called ci.
- 03Issue a token for it
At most 90 days, with the permissions below. It is shown once.
- 04Store 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 permission | Why |
|---|---|
| scan.execute, scan.read, finding.read | Required. Start the scan, follow it, read what it found. |
| product.read | Needed when the product is named rather than given by ID. |
| credential.read | Needed for the secrets gate, which is on by default. |
| scan.cancel | Optional. A cancelled pipeline then cancels its scan instead of leaving it running. |
| report.read | Optional, 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
| Source | What 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 URL | The 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
| Default | What blocks the build |
|---|---|
| --fail-on-severity high | Fails 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 new | Fails 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
| Code | Meaning |
|---|---|
| 0 | The gate passed, or an incremental scan found nothing new to scan. |
| 1 | The gate failed: blocking findings, blocking secrets, or both. |
| 2 | The scan failed, was cancelled, or was refused — the team’s scan limit, a missing credential. |
| 3 | Configuration, token or permission error. |
| 4 | Timed out waiting (--timeout, 7200 seconds by default). |
| 5 | ScanSuite 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
| Option | Output |
|---|---|
| --junit FILE | JUnit XML, shown as test failures in GitLab and Jenkins. |
| --sarif FILE | SARIF 2.1.0, for GitHub code scanning, Azure DevOps and IDEs. |
| --summary-json FILE | The scan, the gate result, the findings and the secrets. |
| --report-zip FILE | The 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
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-waitwhen 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