Safeguard Docs
CI/CD Integration

CI/CD Integration

Scan every push and pull request from GitHub Actions, GitLab CI/CD, Bitbucket Pipelines, Jenkins, Azure Pipelines or any CI, and fail the build on vulnerabilities

CI/CD Integration

One step in your pipeline runs safeguard ci scan. Each push becomes a new version of its Safeguard project, named <branch>-<commit> (for example main-a1b2c3d), and the step can fail the build when the scan finds vulnerabilities at or above a severity you choose.

Supported CI/CD

To scan the container images your pipeline builds, per environment, see Container images.

In the app, the GitHub, GitLab, Bitbucket and Azure DevOps cards under Integrations → Source Control have a Set up CI/CD button, and the Git card has one for Jenkins and any other CI. It opens the setup with your branches and severity filled in; a project's Set up CI/CD does the same with that project's id.

There is no plugin, Action or Pipe to install. Every system runs the CLI in a plain shell step, on a Linux or macOS runner with bash and curl.

Setup in three steps

  1. Create a secret key under Settings → Developer with the scopes scans:trigger and scans:read. You can lock it to one project.
  2. Store it in your CI as a secret named SAFEGUARD_API_KEY. Each guide says where.
  3. Add the step from your CI's guide, and push.

The first run creates the project, so nothing else is needed to start.

Where the scan runs

ModeCommandNeedsScans
On Safeguard (default)safeguard ci scanthe repository connected as an integration, or public on github.com, gitlab.com or bitbucket.orgSAST, SBOM, dependency vulnerabilities
On the runnersafeguard ci scan --locala checkout in the job; no integrationSBOM, dependency vulnerabilities

With --local only the results leave the runner, so it also works for self-hosted and firewalled Git servers. It reads dependencies from lockfiles and manifests (package-lock.json, go.sum, requirements.txt, pom.xml, …).

Choose the branches

Every template scans pushes to main and pull requests into it. Change the branches in the file; wildcards work (release/*). Each guide shows the exact setting. On a pull request the source branch is scanned and its head commit names the version.

Fail the build

FlagFails the step when
--waitthe scan fails or takes longer than --timeout (default 30m)
--fail-on <severity>with --wait: a dependency vulnerability at or above critical, high, medium or low
--fail-on-gate <gate>with --wait: a policy gate (its name, id, or all) is not Passed or Warning

SAST findings are scanned and shown in Safeguard but are not part of --fail-on yet. Drop --fail-on to report without failing.

Other CI systems

Any CI that runs a shell step works. Pass the repository, branch and commit when your CI is not one of the five above (CircleCI is detected too):

curl -fsSL https://cli.safeguard.sh/install | bash
SAFEGUARD_API_KEY=... safeguard ci scan --wait --fail-on critical \
  --repository-url "$REPO_URL" --branch "$BRANCH" --commit "$COMMIT"

Pin a CLI version with SAFEGUARD_VERSION=<version> on the install line so a release does not change a build under you.

Troubleshooting

MessageWhat to do
AUTHENTICATION_REQUIRED or INVALID_SECRET_KEYSAFEGUARD_API_KEY is missing, or the key is invalid, expired or revoked. Check the secret's name, and that the step can read it (Azure maps it through env).
MISSING_SCOPEThe key lacks scans:trigger or scans:read. Create one with both.
PROJECT_NOT_ALLOWEDThe key is locked to another project.
PROJECT_AMBIGUOUS or INTEGRATION_AMBIGUOUSMore than one project or integration fits the repository. Pass --project-id (or --integration-id on the first run).
PROJECT_NOT_RESCANNABLEThe project has no repository Safeguard can clone (for example, an upload project). Connect the repository under Integrations, or use --local.
SCAN_NOT_DISPATCHED / SCAN_NOT_STARTEDThe platform could not start the scan. Retry; if it persists, contact support with the build URL.
The step never runsThe branch is not in the template's branch list, or (pull requests from forks) the CI does not pass secrets to fork builds.

Scanning on the build machine instead

safeguard scan and safeguard detect run fully on the runner and write files you can keep as artifacts or upload to a code-scanning tab. They report without failing the build; gate on the files they write:

safeguard scan --dir . --output out/
jq -e '[.data.matches[].vulnerability.severity | ascii_downcase]
       | index("critical") | not' out/*/vuln.json

safeguard scan runs syft and grype: install both on the PATH (or point TOOLS_DIR at them) on a fresh runner.

CommandExit code
safeguard scan0 when the scan ran, whatever it found. 2 when results were written but the vulnerability scan did not run. 1 when the command failed.
safeguard detect0 when the scan ran, whatever it found. Non-zero only when the command failed.

On this page