In GitLab CI, add a job that installs curl and the Safeguard CLI, runs safeguard scan on the working directory with an output folder, and saves that folder with artifacts paths. The scan exits zero when it ran, so the job will not fail on findings by itself.
The job
Safeguard's CI/CD documentation describes a job called safeguard-scan in the test stage, on an Ubuntu 24.04 image. It installs curl, installs the CLI, runs scan on the current directory and stores the results folder as an artifact. A version of that, with the pinning you should add:
safeguard-scan:
stage: test
image: ubuntu:24.04
variables:
SAFEGUARD_VERSION: "<version from the download page>"
before_script:
- apt-get update && apt-get install -y curl ca-certificates
- curl -fsSL https://cli.safeguard.sh/install | bash
script:
- safeguard scan --dir . --output safeguard-results
artifacts:
when: always
expire_in: 2 weeks
paths:
- safeguard-results/
The output folder holds the SBOM and per component result files such as vuln.json and license.json, according to the CLI documentation.
The scanner prerequisite
The documentation says safeguard scan runs syft and grype, and a fresh CI runner does not have them. Install both onto the PATH before the scan step, or point the TOOLS_DIR variable at a folder that contains them. If your scan step errors out about missing scanners, this is the first place to look. It also exits with code 2 in the case where results were written but the vulnerability scan did not run, so do not ignore a 2.
Retention and when artifacts upload
GitLab's artifacts keywords matter here. The expire_in keyword sets how long artifacts are kept and overrides the instance default, which can differ on GitLab.com. The documentation page on report artifacts also notes that artifacts under a reports key upload whether or not the job succeeded. For plain paths artifacts, set when to always if you want results from failed jobs. That is how you keep evidence from a run that broke for another reason.
Who can see the output
Job artifacts are visible to people with access to the pipeline. The SBOM lists every dependency you ship, so treat it as internal. Shorten the expiry if you do not need the history, or keep long term copies in the platform instead.
Gating
The CLI does not fail the job on severity. Use platform policy gates, or read the vuln.json file yourself in a later step. The pattern is in the gate post.
Making the job optional at first
When you add a new scanner job, set allow_failure on it for the first week or two. That lets the team see output without a red pipeline blocking unrelated work. Remove the setting once the noise is understood. Remember that the CLI does not fail on findings anyway, so the only red you will see early is from setup problems such as a missing tool, which is useful to surface.
Use rules to limit the job to merge requests and the default branch, so it does not run on every push to every feature branch.
Quick check
Which GitLab keyword saves the scan folder?
The artifacts keyword with paths listing the output folder. Add expire_in to control retention.
Does a failed scan fail the pipeline?
Only if the command itself fails. Exit code 1 means the command failed, and 2 means results were written but the vulnerability scan did not run.
Where Safeguard fits
If you connect the GitLab project as an integration, the platform scans it and evaluates policies without any CI job. The job above is for teams that want results inside the pipeline as well.
Sources
Last reviewed: 19 October 2026.