Gate CI/CD builds on APIsec scan results
Run an APIsec scan from your pipeline and fail the build when serious vulnerabilities exceed the limits you set. This shifts API security testing left: every push or merge request is scanned, and a build cannot ship while CRITICAL or HIGH findings breach your thresholds.
APIsec publishes a maintained Docker image — a scan-and-gate script — that your pipeline runs. In one step it triggers a scan of an application instance, waits for the scan to finish, counts the CRITICAL and HIGH findings, and exits non-zero when either count is over the limit. Your CI platform reads the exit code and passes or fails the build accordingly.
This guide covers the fastest path (generate a snippet in the console), the input reference, ready-to-use pipeline examples, and the exact pass/fail rules.
Prerequisites
-
An onboarded APIsec application. You need its Application ID and Instance ID — the instance is the environment the scan runs against.
-
An API credential authorized to run and read scans. Either an API Token (Personal Access Token) or an OAuth Client Credential, with both of these scopes:
- Initiate Scan (
scan:init) - Get Scan Details (
scan:get)
Create either one from the API Credentials page.
- Initiate Scan (
-
A CI runner that can run
docker run— for example, a GitHub-hostedubuntu-latestrunner. If you run the script directly without Docker, the runner needsbash,curl,jq,mktemp, anddateinstead. -
Your API credential stored as a CI secret (GitHub Secrets, GitLab masked variables, or Jenkins credentials). Never commit it to your repository.
For CI/CD, prefer short-lived OAuth Client Credentials over a long-lived API Token. A short-lived credential limits exposure if a pipeline log or secret store leaks. Both options and their scopes are documented on the API Credentials page.
For an authenticated scan, you also need a scan auth ID — the scanWithAuthId UUID for the credential the scan should use. Leave APISEC_AUTH_ID unset or empty for an unauthenticated scan. See Configure an authenticated scan.
Generate a pipeline snippet in the console
The quickest way to start is to let the console generate a snippet pre-filled with your Application ID and Instance ID.
- In the console, open your application's detail page.
- Choose the CI/CD action. The CI/CD Integrations dialog opens.
- Select your platform tile — GitHub Actions, GitLab, or Jenkins — and click View.
- Copy the generated Integration Script into your pipeline configuration.
- Add your API credential as a CI secret named
APISEC_TOKEN(see Add your credential as a secret).
On cloud tenants the generated snippet does not include APISEC_HOST. Add it yourself before running the pipeline — without it the container exits 1 with missing --host or APISEC_HOST. Set it to your tenant's API base URL.
The Harness tile is listed but does not generate a snippet yet. For Harness, adapt the plain docker run example.
If you prefer to build the step by hand, use the pipeline examples below and set the input variables yourself.
Add your credential as a secret
The image reads your credential from the APISEC_TOKEN environment variable. Store the value in your platform's secret manager and inject it into the job — do not place it on the command line, where it can land in process listings or logs.
- GitHub Actions: add a repository or environment secret, then reference it as
${{ secrets.APISEC_TOKEN }}. - GitLab CI/CD: add a masked (and optionally protected) CI/CD variable named
APISEC_TOKEN. - Jenkins: store it as a Secret text credential and bind it with
withCredentials.
Configure thresholds
You set two limits. The build fails when either is exceeded:
APISEC_MAX_CRITICAL— the most CRITICAL findings allowed before the build fails. Set0to fail on any CRITICAL finding.APISEC_MAX_HIGH— the most HIGH findings allowed before the build fails.
The comparison is strictly greater-than. With APISEC_MAX_HIGH=5, a scan that reports exactly 5 HIGH findings passes; 6 fails. Only CRITICAL and HIGH findings are gated — there are no Medium or Low controls.
The gate counts vulnerabilities only. A scan result carries findings in two separate
groups, vulnerabilities and issues; the threshold check reads the first and ignores
the second. A build can therefore pass while the scan still reports issues, so review the
full scan result in the console rather than treating a green build as "nothing found".
A common starting point is APISEC_MAX_CRITICAL=0 and APISEC_MAX_HIGH=5: block every CRITICAL, and allow a small, deliberate HIGH budget while your team works findings down.
Input reference
The image reads its configuration from environment variables. Each variable has an equivalent CLI flag, shown in parentheses. There is no config file.
| Variable (flag) | Required | Description |
|---|---|---|
APISEC_TOKEN (--token) | Yes | API bearer token or PAT. Pass it as a secret, not on the command line. The Bearer prefix is optional. |
APISEC_HOST (--host) | Yes | API base URL, for example https://cloud.apisecapps.com. |
APISEC_APP_ID (--app-id) | Yes | Application ID to scan. |
APISEC_INSTANCE_ID (--instance-id) | Yes | Instance (environment) ID to scan. |
APISEC_MAX_CRITICAL (--max-critical) | Yes | Maximum CRITICAL findings allowed before failing. 0 fails on any. |
APISEC_MAX_HIGH (--max-high) | Yes | Maximum HIGH findings allowed before failing. |
APISEC_AUTH_ID (--auth-id) | No | scanWithAuthId UUID for an authenticated scan. Leave unset or empty for an unauthenticated scan. The literal string null (any casing) is also treated as empty, which is convenient for CI systems that interpolate absent values as "null". |
APISEC_POLL_INTERVAL (--poll-interval) | No | Seconds between scan-status polls. Default 30. |
APISEC_TIMEOUT (--timeout) | No | Maximum seconds to wait for the scan to finish. Default 7200 (2 hours). |
The API host in these examples is https://cloud.apisecapps.com. Self-hosted and regional deployments differ — set APISEC_HOST to your own tenant's API base URL.
Pipeline examples
Each example injects secrets with -e and relies on the image entrypoint.
The examples use the image docker.io/apisec/apisec-scan-and-gate:latest. Pin a specific tag or digest for reproducible builds — see Pin the image version.
GitHub Actions
Save as .github/workflows/apisec-scan-gate.yml. Store APISEC_TOKEN as a secret; store the IDs as repository variables.
name: APIsec scan and gate
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
apisec-gate:
runs-on: ubuntu-latest
steps:
- name: Run APIsec scan and enforce thresholds
env:
APISEC_TOKEN: ${{ secrets.APISEC_TOKEN }}
APISEC_HOST: https://cloud.apisecapps.com
APISEC_APP_ID: ${{ vars.APISEC_APP_ID }}
APISEC_INSTANCE_ID: ${{ vars.APISEC_INSTANCE_ID }}
APISEC_AUTH_ID: ${{ vars.APISEC_AUTH_ID }}
APISEC_MAX_CRITICAL: "0"
APISEC_MAX_HIGH: "5"
IMAGE: docker.io/apisec/apisec-scan-and-gate:latest
run: |
docker run --rm \
-e APISEC_TOKEN -e APISEC_HOST -e APISEC_APP_ID -e APISEC_INSTANCE_ID \
-e APISEC_AUTH_ID -e APISEC_MAX_CRITICAL -e APISEC_MAX_HIGH \
"$IMAGE"
GitLab CI/CD
Save as .gitlab-ci.yml. Set APISEC_TOKEN, APISEC_APP_ID, APISEC_INSTANCE_ID, and APISEC_AUTH_ID as masked CI/CD variables. This example runs Docker-in-Docker.
stages:
- test
apisec-scan-gate:
stage: test
image: docker:24-cli
services:
- name: docker:24-dind
alias: docker
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
APISEC_HOST: "https://cloud.apisecapps.com"
APISEC_MAX_CRITICAL: "0"
APISEC_MAX_HIGH: "5"
IMAGE: "docker.io/apisec/apisec-scan-and-gate:latest"
script:
- |
docker run --rm \
-e APISEC_TOKEN -e APISEC_HOST -e APISEC_APP_ID -e APISEC_INSTANCE_ID \
-e APISEC_AUTH_ID -e APISEC_MAX_CRITICAL -e APISEC_MAX_HIGH \
"$IMAGE"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
If your runners already expose Docker to jobs, drop the services: and DOCKER_HOST lines and keep the script block.
Jenkins
A declarative pipeline. Bind the token with the Credentials plugin.
pipeline {
agent { label 'docker' }
environment {
IMAGE = 'docker.io/apisec/apisec-scan-and-gate:latest'
APISEC_HOST = 'https://cloud.apisecapps.com'
APISEC_APP_ID = '...'
APISEC_INSTANCE_ID = '...'
APISEC_AUTH_ID = '...'
APISEC_MAX_CRITICAL = '0'
APISEC_MAX_HIGH = '5'
}
stages {
stage('APIsec gate') {
steps {
withCredentials([string(credentialsId: 'apisec-token-secret-text', variable: 'APISEC_TOKEN')]) {
sh '''
docker run --rm \
-e APISEC_TOKEN -e APISEC_HOST -e APISEC_APP_ID -e APISEC_INSTANCE_ID \
-e APISEC_AUTH_ID -e APISEC_MAX_CRITICAL -e APISEC_MAX_HIGH \
"$IMAGE"
'''
}
}
}
}
}
Plain docker run
Any runner with Docker can run the image directly. Set APISEC_TOKEN in the environment beforehand so it is not written on the command line.
docker run --rm \
-e APISEC_TOKEN \
-e APISEC_HOST=https://cloud.apisecapps.com \
-e APISEC_APP_ID -e APISEC_INSTANCE_ID -e APISEC_AUTH_ID \
-e APISEC_MAX_CRITICAL=0 -e APISEC_MAX_HIGH=5 \
docker.io/apisec/apisec-scan-and-gate:latest
Understand pass/fail behavior
Your CI platform decides the build outcome purely from the exit code.
Exit 0 — the build passes when all of these hold:
- The scan completes, and
- CRITICAL count ≤
APISEC_MAX_CRITICAL, and - HIGH count ≤
APISEC_MAX_HIGH.
Exit 1 — the build fails when any of these occur:
- Either threshold is exceeded.
- An HTTP or usage error occurs (for example, a bad credential or a missing input).
- The scan does not finish within
APISEC_TIMEOUT. - The scan ends in any terminal status other than
Complete.
Scan statuses
A scan is either running or finished. There are four values:
| Status | Meaning | Gate result |
|---|---|---|
Processing | Still running. The script keeps polling. | — |
Complete | Finished successfully. | The only status that can pass |
Failed | The scan run failed, or it timed out internally. | Fails the build |
Aborted | The scan was stopped before finishing, usually by a user. | Fails the build |
Only Complete passes the gate; Failed and Aborted fail it. The script recognizes a
fixed set of terminal statuses. A status outside that set is treated as still running and
polled until APISEC_TIMEOUT expires, which then fails the build — so an unexpected status
shows up as a timeout rather than an immediate failure. Matching is case-insensitive.
When a scan does not complete, the API also returns a statusReason describing why —
for example User Requested Abort. The script
prints it, and it is the first thing to read when diagnosing a red build.
Scan status casing differs by surface. The API returns Complete, Failed, Aborted,
and Processing in title case. The scan-history API filter accepts the lowercase tokens
completed, failed, aborted, and running. The console displays Completed. Match
the casing to whichever surface you are calling.
Script output
The script prints progress and a summary to stderr. Every line carries an
apisec-scan-and-gate: prefix:
apisec-scan-and-gate: scanId=0198c4a1-7b2e-7f31-9d64-2a5e8c1f4b70
apisec-scan-and-gate: terminal status=Complete
apisec-scan-and-gate: vulnerabilities CRITICAL=0 HIGH=3 (limits CRITICAL<=0 HIGH<=5)
apisec-scan-and-gate: exiting with status 0 (within thresholds)
This output is for your logs. The pass/fail decision comes from the exit code alone, so you do not need to parse it.
Track CI/CD scans in Scan History
Scans launched this way are recorded in the console's Scan History with a Trigger Source of CI_CD. Use that column to see which findings came from a pipeline run.
The column shows the raw value, not a friendly label. The three possible values are
MANUAL, CI_CD, and SCHEDULED.
Pin the image version
The image is published as :latest and with immutable, timestamp-based version tags. For reproducible pipelines and predictable rollback, pin a specific version tag (or a digest) rather than :latest. Pinning is also better supply-chain hygiene: a build then runs the exact image you reviewed.
Alternative scan initiation
You can also initiate a CI/CD scan by scan profile through the API, which lets you reuse a saved scan configuration. See Scan Profiles for what a profile controls.
The specific API endpoint and payload for launching a scan by profile are not documented here yet. Use the Docker image described above as the supported path until the endpoint is confirmed.
Related
- API Credentials — create the API Token or OAuth Client Credential the pipeline uses.
- Scan Profiles — save reusable scan configurations.
- Configure an authenticated scan — set up the credential behind
APISEC_AUTH_ID. - Run an unauthenticated scan — the manual equivalent of an unauthenticated CI/CD scan.
- Schedule scans — run scans on a schedule instead of on every build.
- Integrations — connect APIsec to gateways, issue trackers, and notifications.