Skip to content

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.
1. Upload & Start POST /api/jobs 2. Wait for Result GET /api/jobs/:id/wait 3. Quality Gate GET /api/jobs/:id/gate 4. SARIF Export PR Annotations
Automated workflow for running security scans, checking quality gates, and publishing SARIF results in CI/CD.
  1. Create a Read and write API token for a service account with the Analyst role. See API tokens.
  2. Store the instance address as CERBERON_URL and the token as CERBERON_TOKEN in your CI secrets.
  3. Make sure your runners can reach the instance over HTTPS.

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.

Terminal window
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 high
cerberon 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-source confirms that you are authorised to review the source. Code scans are refused without it.
  • --authorised confirms that you are authorised to test the target. Required for dast, va and pentest.
  • --ref scans a branch, a tag (tag:<name>) or a commit. Only for a Git address.
  • --wait waits 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_KEY to 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.

Save this as cerberon-scan.sh in your repository. It needs curl, jq and tar.

#!/bin/sh
set -eu
AUTH="Authorization: Bearer $CERBERON_TOKEN"
# Upload the source
tar --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 finish
until [ "$(curl -sSf -H "$AUTH" "$CERBERON_URL/api/jobs/$JOB/wait?timeout=120" | jq -r .done)" = "true" ]; do :; done
# Download SARIF
REPORT=$(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 gate
GATE=$(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" ]
name: Cerberon
on:
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.sarif

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:

Terminal window
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:

Terminal window
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.

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.