Safeguard's scan command runs syft and grype under the hood, and a fresh CI runner has neither. Install both onto the PATH before the scan step, or set the TOOLS_DIR environment variable to a folder that contains them. Otherwise the run can write files without matching vulnerabilities.
What the documentation says
The CI/CD page states that safeguard scan requires syft and grype, and that clean runners do not include them. It tells you to put them on the PATH or in a directory named by TOOLS_DIR. The CLI system configuration page adds that TOOLS_DIR is searched first for scanner binaries, and names syft, grype and trivy as the tools it looks for. It also says the binary can sit next to the safeguard binary.
The interactive terminal downloads scanners on first use, which needs internet access. In CI you usually want something more controlled.
Three ways to provide them
Bake them into your runner image. This is the most repeatable approach. Builds do not depend on a download at job time, and you control the versions.
Install them in the job. Fast to set up, slower per run, and it adds another outbound download to trust. Pin versions and check checksums as you would for any tool.
Cache a tools folder. Download once, cache the folder between runs with your CI system's cache feature, and point TOOLS_DIR at it. Make the cache key include the tool versions so updates are deliberate.
A job that sets the directory
env:
TOOLS_DIR: ${{ github.workspace }}/.tools
steps:
- run: mkdir -p "$TOOLS_DIR"
# put pinned syft and grype binaries into $TOOLS_DIR here
- run: curl -fsSL https://cli.safeguard.sh/install | bash
- run: safeguard scan --dir . --output out/
The comment line is deliberate. Where you get the binaries from is your supply chain decision, so choose a source you can verify.
Air gapped runners
For machines without internet, the documentation describes loading a vulnerability database downloaded elsewhere with the update command and an import archive, and placing the scanner binaries where TOOLS_DIR points. It also lists variables that turn off update checks and self update. The air gapped deployment guide has more.
How to tell it went wrong
Read the exit code. Code 2 means results were written but the vulnerability scan did not run, which is the signature of this problem. See the exit codes reference. If you only check for 1, a run with missing scanners looks fine.
Keep the tools current
Vulnerability scanners depend on their data as much as their binaries. The CLI has an update command that refreshes the local vulnerability databases, and a stale database means missed findings. On long lived agents, schedule that refresh. On ephemeral runners, make sure the cached database is not older than you can tolerate, and put its age in your job log.
Quick check
Does detect need syft and grype too?
The documentation ties the requirement to scan. Detect is documented as finding secrets and misconfigurations.
Where do I get syft and grype?
From their own projects. Pin a version and verify it. Safeguard's documentation tells you where to put them, not where to download them.
Sources
- Safeguard docs: CI/CD Integration
- Safeguard docs: CLI system configuration
- Safeguard docs: CLI usage
Last reviewed: 19 October 2026.