CI/CD and quality gates
A pipeline step uploads the source, starts a scan, waits for it, checks the quality gate and downloads SARIF for code scanning annotations. There are two ways to do it:
| Option | Runs on | Use it when |
|---|---|---|
cerberon scan |
The Cerberon host | Your CI runner runs on the same host as Cerberon. |
| The scan script | Any runner with curl and jq |
Your runners are elsewhere and reach Cerberon over HTTPS. |
Prepare
Section titled “Prepare”- Create a Read and write API token for a service account with the Analyst role. See API tokens.
- Store the instance address as
CERBERON_URLand the token asCERBERON_TOKENin your CI secrets. - Make sure your runners can reach the instance over HTTPS.
The cerberon scan command
Section titled “The cerberon scan command”From version 0.1.7 the cerberon command on the Cerberon host includes a CI client. It runs inside the Cerberon image, uses the instance’s own address, and reads files from the directory you run it in.
export CERBERON_TOKEN="<token>"
# Upload the source, start a SAST scan and wait for it. Prints the scan ID.JOB=$(cerberon scan start --type sast --target @source.tar.gz --acknowledge-source --wait)
# Fail the build on the quality gate, then download SARIF.cerberon scan gate "$JOB" --fail-on highcerberon scan sarif "$JOB" --out results.sarif| Command | What it does |
|---|---|
scan start --type <type> --target <target> |
Starts a scan and prints its ID. See the scan types below. |
scan wait <id> |
Waits for the scan to finish. The default limit is 1800 seconds. Set it with --timeout. |
scan status <id> |
Prints the scan’s state. |
scan gate <id> |
Applies the quality gate and prints the reasons. Takes --fail-on, --fail-on-new and --max-age-days. |
scan sarif <id> |
Downloads the SARIF report, building it first if needed. Use --out to name the file. |
--type |
Scan | --target |
|---|---|---|
sast |
Static code analysis | A Git address, or @file for a source archive |
sca |
Dependencies | A Git address, or @file for a lockfile or archive |
sbom |
Dependencies of an existing SBOM | @file for a CycloneDX or SPDX document |
dast |
Web and API testing | A web address |
va |
Vulnerability assessment of a host, without exploitation | A host name or address, optionally with :port |
pentest |
Penetration test of a host | A host name or address, optionally with :port |
Useful options for scan start:
--acknowledge-sourceconfirms that you are authorised to review the source. Code scans are refused without it.--authorisedconfirms that you are authorised to test the target. Required fordast,vaandpentest.--refscans a branch, a tag (tag:<name>) or a commit. Only for a Git address.--waitwaits for the result in the same step.- A retried CI step does not start a second scan. The request carries an idempotency key built from the CI run ID. Set
CERBERON_IDEMPOTENCY_KEYto choose your own.
| Exit code | Meaning |
|---|---|
0 |
The scan finished, or the gate passed. |
1 |
The gate failed. |
2 |
An error, such as an unreachable server, a refused token, an unknown scan or a timeout. |
3 |
The scan did not complete: it failed, was blocked or stopped, or is still running. |
The scan script
Section titled “The scan script”Save this as cerberon-scan.sh in your repository. It needs curl, jq and tar.
#!/bin/shset -euAUTH="Authorization: Bearer $CERBERON_TOKEN"
# Upload the sourcetar --exclude=.git -czf source.tar.gz .ARTIFACT=$(curl -sSf -X POST "$CERBERON_URL/api/uploads" -H "$AUTH" \ -F "file=@source.tar.gz" | jq -r .artifact.id)
# Start the scan. The idempotency key stops a retried step from starting a second scan.JOB=$(curl -sSf -X POST "$CERBERON_URL/api/jobs" -H "$AUTH" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: ci-${CI_RUN_ID:-local}-$ARTIFACT" \ -d '{"capability":"sast", "target":{"kind":"artifact","artifactId":"'"$ARTIFACT"'"}, "consent":{"acknowledged":true,"mode":"rules-only"}, "config":{"sast":{"mode":"rules-only"}}}' | jq -r .job.id)echo "Cerberon scan $JOB"
# Wait for it to finishuntil [ "$(curl -sSf -H "$AUTH" "$CERBERON_URL/api/jobs/$JOB/wait?timeout=120" | jq -r .done)" = "true" ]; do :; done
# Download SARIFREPORT=$(curl -sSf -H "$AUTH" "$CERBERON_URL/api/jobs/$JOB" | jq -r '.report.id // empty')[ -n "$REPORT" ] || REPORT=$(curl -sSf -X POST -H "$AUTH" "$CERBERON_URL/api/jobs/$JOB/report" | jq -r .report.id)curl -sSf -H "$AUTH" "$CERBERON_URL/api/reports/$REPORT/download?format=sarif" -o cerberon.sarif
# Apply the quality gateGATE=$(curl -sSf -H "$AUTH" "$CERBERON_URL/api/jobs/$JOB/gate?fail_on_severity=high")echo "$GATE" | jq -r '.reasons[]'[ "$(echo "$GATE" | jq -r .pass)" = "true" ]Pipeline examples
Section titled “Pipeline examples”name: Cerberonon: pull_request: branches: [main]
jobs: sast: runs-on: ubuntu-latest permissions: contents: read security-events: write steps: - uses: actions/checkout@v4 - name: Scan with Cerberon env: CERBERON_URL: ${{ secrets.CERBERON_URL }} CERBERON_TOKEN: ${{ secrets.CERBERON_TOKEN }} CI_RUN_ID: ${{ github.run_id }} run: sh cerberon-scan.sh - name: Upload SARIF if: always() uses: github/codeql-action/upload-sarif@v3 with: sarif_file: cerberon.sarifcerberon_sast: stage: test image: alpine:3.20 before_script: - apk add --no-cache curl jq tar variables: CI_RUN_ID: $CI_PIPELINE_ID script: - sh cerberon-scan.sh artifacts: when: always paths: - cerberon.sarifInstalled command
Section titled “Installed command”On a server installed with Cerberon, the same commands ship as cerberon scan start|wait|status|gate|sarif. It runs the CI client inside the Core image, uses the instance’s own address, and needs CERBERON_TOKEN set to a write token. Code scans still need the explicit source acknowledgement:
CERBERON_TOKEN=... cerberon scan start --type sast --target https://github.com/acme/app.git --acknowledge-source --wait--type supports sast, sca, sbom, dast, va (a host vulnerability assessment) and pentest. Host and DAST scans also need the authorisation confirmation:
CERBERON_TOKEN=... cerberon scan start --type va --target 10.0.0.5 --authorised --wait--target @file reads a file from the directory you run the command in. The plain curl examples above remain the portable option for any runner.
Quality gate
Section titled “Quality gate”GET /api/jobs/:id/gate answers whether the build may continue. It counts the scan’s open findings after triage: findings marked accepted, fixed or false positive are not counted, and an adjusted severity replaces the engine’s. A scan that did not complete never passes.
| Parameter | Values | Default |
|---|---|---|
fail_on_severity |
critical, high, medium or low. Fail on any open finding at or above it. |
high |
fail_on_new |
true to count only findings new since the previous scan of the same asset |
false |
max_age_days |
Also fail when an open Critical finding was first seen more than this many days ago (1 to 36,500) | Not set |
The response contains pass (true or false), the reasons it failed, the policy applied, the counts by severity, and whether the scan was complete.
Per-project policy. An administrator can store a policy for a project under Settings > Scanning > Quality Gate: fail-on severity, fail-on-new and a maximum finding age. The stored policy is a floor: request parameters may tighten it (a lower severity threshold, fail-on-new on, a smaller age limit) but never loosen it. With no stored policy, the request parameters alone decide, as above. To set it through the API, send PUT /api/settings/gate-policy with project_id and a policy object holding the three parameters. See the API reference.