Tutorials

Getting Started with Safeguard CLI: Your First Scan

Install the Safeguard CLI, authenticate, and run your first dependency and SBOM scan in under ten minutes. Covers config, output formats, and CI wiring.

Shadab Khan
Security Engineer
6 min read

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.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.