The Safeguard CLI is the workhorse of the platform. Everything the IDE extension, CI gate, and Kubernetes admission controller do eventually shells out to similar logic — and the CLI gives you direct access without a web UI in the way. If you can only learn one Safeguard tool, learn this one.
How Do I Install the CLI?
Use the official install script for macOS and Linux, and the Scoop manifest on Windows. Both verify the binary against a Sigstore transparency log entry so you can prove the artifact came from the official build pipeline.
curl -sSfL https://get.safeguard.sh/install.sh | sh -s -- --version v2.4.0
On Windows PowerShell:
scoop bucket add safeguard https://github.com/safeguard-sh/scoop-bucket
scoop install safeguard
The binary lands in ~/.safeguard/bin/sg on Unix and %USERPROFILE%\scoop\apps\safeguard\current\sg.exe on Windows. Add it to your PATH if the installer didn't already. Run sg version to confirm:
sg version 2.4.0 (2026-01-14, commit a9f2c1e)
attestation: verified (rekor log index 9284712)
The attestation: verified line is the one you want. If it says unverified, your binary didn't pass signature verification and you should not use it.
Step 1: Authenticate
Run sg login. This prints a device code and opens your browser to confirm. For CI or headless hosts, use a project-scoped token:
export SAFEGUARD_TOKEN="sgp_live_..."
sg whoami
Project tokens are preferred over user tokens for automation because they're scoped to a single project and can be rotated without disrupting humans. Generate them in the portal under Project → Settings → API Tokens.
Step 2: Point the CLI at Your Project
Change into the root of your repo and run sg init. This creates a .safeguard/config.yaml:
project: my-service
ecosystems:
- npm
- pypi
policy: default-strict
sbom:
format: cyclonedx-json
output: .safeguard/sbom.cdx.json
ignore: .safeguard/ignore.yaml
scan:
paths:
- "."
exclude:
- "**/node_modules"
- "**/.venv"
- "**/vendor"
Commit this file. Every Safeguard tool — IDE, CLI, CI gate — reads it, so your team gets consistent behavior regardless of where they're running scans.
Step 3: Run Your First Scan
sg scan
You'll see output like:
Scanning my-service (npm, pypi)
resolved 1,284 dependencies across 2 ecosystems
applying policy: default-strict
findings:
critical: 1
high: 4
medium: 12
low: 7
top issues:
[CRITICAL] CVE-2024-21538 — cross-spawn@7.0.3 — RCE via crafted regex
[HIGH] CVE-2024-37891 — urllib3@2.2.1 — cleartext transmission
sbom: wrote .safeguard/sbom.cdx.json (1,284 components)
exit: 2 (findings above policy threshold)
The exit code matters for CI — 0 is clean, 2 is policy violations, 3 is scan error. Wire it in exactly like a test suite.
Step 4: Inspect a Specific Finding
sg scan finding CVE-2024-21538 --explain
You get the advisory, a reachability analysis (if Safeguard can trace import paths), the first fixed version, and a recommended remediation command. If the AI fix engine can auto-resolve the finding, you'll see a --apply hint at the bottom.
Step 5: Generate an SBOM on Demand
sg scan already writes an SBOM, but if you only want the SBOM with no findings output:
sg sbom generate --format cyclonedx-json --output sbom.cdx.json
sg sbom generate --format spdx-json --output sbom.spdx.json
Both formats are fully spec-compliant. You can attach them to a release, upload to the Safeguard portal, or hand them to a customer who asked for your SBOM during procurement.
Step 6: Compare Two SBOMs
For PR review:
sg sbom diff .safeguard/sbom.cdx.json base.cdx.json --format table
You'll see components added, removed, and version-changed, along with any risk scoring Safeguard has for new components. This is the single best way to catch a transitive dependency introduced by a PR that the author didn't realize they were pulling in.
How Do I Scan Without Writing Files?
Use --stdout-only and --no-write:
sg scan --stdout-only --no-write --format json | jq '.findings[] | select(.severity=="critical")'
This keeps the workspace clean and is the preferred mode for CI runners where you don't want a post-scan git diff to show .safeguard/ artifacts. The --format json output is stable across versions — pipe it into whatever downstream tooling you want.
How Do I Manage Ignores?
Ignores go in .safeguard/ignore.yaml. They must be in-repo and code-reviewed — no hidden allowlists.
rules:
- id: CVE-2024-21538
ecosystem: npm
package: cross-spawn
version: "7.0.3"
reason: "Not reachable from our code path — SEC-1482"
expires: "2026-04-01"
reviewer: "shadab@example.com"
- id: CVE-2024-37891
ecosystem: pypi
package: urllib3
reason: "Internal-only service, no TLS downgrade path"
expires: "2026-03-15"
reviewer: "security-team"
The CLI validates ignore files on every scan. Missing expires, unknown reviewers, or expired rules will fail the scan. This prevents the "we'll deal with it later" ignore from living forever.
How Do I Wire the CLI Into CI?
The minimal GitLab or GitHub setup is two lines — install and scan:
scan:
image: alpine:3.19
script:
- curl -sSfL https://get.safeguard.sh/install.sh | sh -s -- --version v2.4.0
- export PATH="$HOME/.safeguard/bin:$PATH"
- sg scan --fail-on high --sbom-upload
variables:
SAFEGUARD_TOKEN: $SAFEGUARD_TOKEN
--fail-on high overrides the policy threshold for this pipeline only — useful when you want your scheduled nightly build to be stricter than PR builds. --sbom-upload sends the generated SBOM to the Safeguard portal so it's linked to the commit and available for dashboarding and regulatory reporting.
For ephemeral CI runners, enable the remote advisory cache so you don't re-download feeds every build:
sg config set cache.remote https://cache.safeguard.sh
sg config set cache.ttl 3600
How Do I Scan Languages the CLI Doesn't Natively Know?
Use sg scan --input with a CycloneDX SBOM you generated elsewhere:
syft dir:. -o cyclonedx-json > sbom.cdx.json
sg scan --input sbom.cdx.json
This is handy for exotic ecosystems — Erlang's Hex, Dart's pub, Swift Package Manager — where you already have a tool producing decent SBOMs. Safeguard will enrich the SBOM with its advisory data, reachability analysis where possible, and policy evaluation.
How Do I Debug a Scan That's Behaving Oddly?
Run with SG_LOG=debug and inspect the resolved dependency graph:
SG_LOG=debug sg scan --trace-resolution > scan.log 2>&1
The trace includes every package-manager call, every advisory fetch, and the resolution decisions. For support tickets, run sg doctor — it collects version info, network reachability to Safeguard endpoints, cache status, and policy state into a single redacted bundle you can attach to an issue.
How Safeguard.sh Helps
The CLI is the single source of truth for how Safeguard evaluates your dependencies. Every other surface — IDE, GitHub Action, admission controller — uses the same scan engine, same policy file, same ignore rules, same SBOM generator. That means what you see locally is what your CI sees, which is what your admission controller sees. No more "works on my laptop, fails in CI" surprises. You can script the CLI in any automation you already have, pipe its JSON output into a dashboard, and rely on its exit codes to gate deploys. Once the CLI is working, every other Safeguard integration becomes a ten-minute configuration exercise rather than a new tool to learn.