Safeguard
Product•

SBOMs per Release in CI with Safeguard: Generate, Attest, Publish, Diff

Run safeguard ci scan on release branches, keep the SBOM files, verify Safeguard signatures with cosign, publish in Portal and compare versions.

Safeguard Security Team
41 min read

To get one SBOM per release with Safeguard, add a single shell step to your release pipeline that runs safeguard ci scan. Each run becomes a new version of the Safeguard project, gated by severity or by your policy gates. You then keep the files as build artifacts, publish the version in Portal as an immutable copy, and use Portal's version comparison to see what changed since the last release. Safeguard signs the attestations it produces with its own key, which you verify with cosign. There is no public transparency log, and there is no CLI command for signing yet, so the signature on your own SBOM file is still your step.

This post is the pipeline version of that story: copy-ready configuration taken from Safeguard's docs, the order to run it in, and an honest list of the parts the docs do not cover. Every command and YAML block below comes from docs.safeguard.sh as fetched on 25 October 2026, except where we say a line is ours. We did not run these snippets ourselves; read them as documented examples and test them in a branch first.

Key takeaways

  • SBOM generation and analysis (GA). safeguard ci scan in any CI builds the SBOM and matches it against vulnerability data. Each push becomes a project version named after its branch and commit, for example main-a1b2c3d.
  • CLI (GA). There is no plugin, Action or Pipe to install. Every CI system runs the CLI in a plain shell step, authorised by a secret key stored as SAFEGUARD_API_KEY.
  • Policies, gates and guardrails (GA). --fail-on <severity> and --fail-on-gate <gate> fail the job. Policy actions are Block, Warn and Notify, and approval goes through exceptions.
  • Attestation and signing (GA). Safeguard signs the attestations it produces with its own key held in a cloud key management service, verifiable with cosign. Signatures are not written to a public transparency log, and the CLI has no signing or verifying command today.
  • Portal, the SBOM publish and share product (GA). Publishing creates an immutable copy per product version, and the SBOM Repository compares two versions: added and removed components, version changes, new and resolved vulnerabilities.
  • Autonomous remediation (GA, ships off). When a release diff shows a new vulnerability with a fixed version, Griffin can open the fix pull request. Every category starts on Manual.
  • The standards behind all this are plain: a new build or release should come with a new SBOM, and the 2026 CISA minimum elements add an SBOM author signature and a generation context.

Why one SBOM per release, and not one per quarter

An SBOM describes one build of one piece of software. Ship a new release and the old SBOM describes something your customers no longer run. Yet many teams still generate SBOMs on request, by hand, from whatever is on main that afternoon, which gives a document that matches no release anyone can point to.

The NTIA minimum elements (2021)

The NTIA's 2021 report set seven data fields (supplier name, component name, version, other unique identifiers, dependency relationship, author of SBOM data and timestamp) plus a set of practices. Under Frequency, it says a new build or release needs a new SBOM, including builds that only pull in an updated dependency, and that a supplier who finds an error should issue a revised one. A pipeline is the only place that sees every build, so a pipeline is where SBOM generation belongs.

CISA's 2026 minimum elements

On 29 July 2026, CISA, with the NSA, the FBI and international partners, published the 2026 Minimum Elements for a Software Bill of Materials. CISA's page says it updates and replaces the NTIA 2021 minimum elements, after a public comment period on a 2025 draft that ran from 22 August to 3 October 2025.

Three changes in the 2026 document matter for a pipeline:

  • Frequency is restated, not relaxed. The 2026 text says each software version or update should have an associated SBOM, and that a new build or release should come with a new SBOM.
  • SBOM Author Signature is a new element. CISA defines it as a digital signature attributable to the SBOM author, giving assurance that the signer signed the content and that it was not changed afterwards.
  • SBOM Generation Context is a new element. It records the lifecycle phase when the SBOM was produced, with "before build", "build" and "after build" given as acceptable general labels. An SBOM made from source is "before build"; one made by analysing a binary is "after build".

The 2026 update also adds SBOM Tool Name and Tool Version, component hash values and algorithms, component license, and a Coverage element that says an SBOM should include all components, including transitive dependencies, with no minimum depth. It also adds an element on accommodating updates and corrections to SBOM data.

Each of these is easier to meet in CI than anywhere else. The pipeline knows the tool and version it ran, the phase it ran in, the commit it built, and the time.

EO 14028 and NIST SSDF PS.3.2

Executive Order 14028 of 12 May 2021, section 4(e)(vii), lists "providing a purchaser a Software Bill of Materials (SBOM) for each product" among the practices the secure software development guidance had to cover, either directly or by publishing it on a public website.

NIST SP 800-218, the Secure Software Development Framework version 1.1, turns that into task PS.3.2: collect, safeguard, maintain and share provenance data for all components of each software release, for example in an SBOM. The SSDF's own mapping table ties 4(e)(vii) to PS.3.2. Two of the implementation examples under PS.3.2 are directly about pipelines: protect the integrity of provenance data and give recipients a way to verify it, and update the provenance data every time any component is updated.

Read together, those two examples describe exactly the four steps in this post's title. Generate per release. Attest so recipients can check integrity. Publish so purchasers can get it. Diff so the update is visible.

The EU Cyber Resilience Act

The EU Cyber Resilience Act also expects manufacturers to keep an SBOM with their technical documentation. We covered its SBOM provisions in SBOM generation and analysis in Safeguard, so this post stays on pipeline mechanics.

Formats: SPDX and CycloneDX

SPDX is published as ISO/IEC 5962:2021, and the SPDX project lists versions 3.0 and 2.3. CycloneDX is standardised as ECMA-424, with 1.7 listed as the current version. Safeguard's docs say it exports both, among more than 30 formats.

What "generate, attest, publish and diff" means in a pipeline

Here is what each verb means and which part Safeguard performs.

StageWhat it meansWhat Safeguard does (label)What you still own
GenerateBuild an SBOM for exactly the commit being released, match it against vulnerability datasafeguard ci scan creates a project version per push; safeguard scan writes files on the runner (SBOM generation, GA)Choosing which branches count as releases; keeping the runner's tools available for on-runner scans
GateStop a release that breaks your rules--fail-on and --fail-on-gate with --wait; Block, Warn, Notify actions; exceptions with approvals (Policies, gates and guardrails, GA)Writing the policies; deciding who approves exceptions
AttestMake the SBOM and provenance verifiable by someone elseSafeguard signs the attestations it produces with its own key, verifiable with cosign, no public transparency log (Attestation and signing, GA)Signing your own release files with your own key; the CLI has no signing command yet
PublishGive customers the SBOM for the version they runPortal publishes an immutable copy per product version, with optional approval (Portal, GA)Creating the product, associating the right project version, pressing Publish or turning on auto publish
DiffShow what changed since the last releasePortal's SBOM Repository compares two versions of a product (Portal, GA)Reading the diff and acting on it; any in-pipeline file diff is your own script

Two things surprise people: the gate is evaluated by the platform, not by safeguard scan (which exits 0 whatever it finds), and Safeguard's signature on its own attestations is not a signature on the SBOM file you hand a customer.

Before you start

  1. A secret key with two scopes. In Safeguard, go to Settings, then Developer, and create a secret key with the scopes scans:trigger and scans:read. The docs say you can lock it to one project, which is a good default for a release pipeline that only ever scans one repository.
  2. A CI secret named SAFEGUARD_API_KEY. Each CI guide in the docs shows where. In GitHub Actions it goes under Settings, Secrets and variables, Actions. In GitLab it is a CI/CD variable with Mask variable on.
  3. A Linux or macOS runner with bash and curl. The CLI installs with one curl line. There is nothing else to install for a scan that runs on Safeguard.
  4. A decision on where the scan runs. By default safeguard ci scan asks Safeguard to scan a repository connected as an integration, or a public repository on github.com, gitlab.com or bitbucket.org. With --local, it scans a checkout in the job and only the results leave the runner. More on this in step 2.
  5. Policy outcomes switched on, if you want gates in CI. The Policies and Gates docs say --fail-on-gate fails a job when the gate fails, with policy outcomes on for your workspace.

The first run creates the project, so you do not need to create it first.

Step 1: decide what a release is to your pipeline

Safeguard names every CI scan as a version of the project, and the name is <branch>-<commit>. On a pull request, the head branch is scanned and the version is named after its head commit. That naming rule shapes how you set up release SBOMs.

There are three common patterns:

  • Release branches. You cut release/2.4, harden it, and ship from it. Point the pipeline at release/** and every push to a release branch produces a version such as release/2.4-9f3e1c2. The CI docs say wildcards work in the branch lists.
  • Main is the release. You ship from main continuously. Scan pushes to main, and each deploy has a version named main-<commit>.
  • Tags. You tag v2.4.0 and build from the tag. The CI docs show branch triggers; they do not document tag triggers for safeguard ci scan, so we will not invent one. For a repository connected as an integration, the Generate SBOM docs list an On Release trigger that scans when new tags are created, alongside On Push and Scheduled triggers. Use that if tags are your release unit, and check the version naming in your own project.

Container images follow a slightly different rule, covered later: with --environment prod, an image tagged 1.4.2 becomes the version prod-1.4.2.

Whichever you choose, write it down. The usual reason a team cannot produce "the SBOM for 2.4.0" is that nobody can say which commit 2.4.0 was.

Step 2: generate the release SBOM in CI

Here is the GitHub Actions workflow exactly as the docs give it, followed by the release version we built from the docs' own options table.

The documented GitHub Actions workflow

Save as .github/workflows/safeguard.yml:

name: Safeguard
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - name: Safeguard scan
        env:
          SAFEGUARD_API_KEY: ${{ secrets.SAFEGUARD_API_KEY }}
          SAFEGUARD_INSTALL_DIR: ${{ runner.temp }}/safeguard-bin
        run: |
          set -eu
          curl -fsSL https://cli.safeguard.sh/install | bash
          "$SAFEGUARD_INSTALL_DIR/safeguard" ci scan --wait --fail-on critical

The docs note that no checkout step is needed, because Safeguard clones the repository. After a push, the run shows the scan result and a link to it in Safeguard.

A release-branch variant, built from the documented options

The GitHub Actions guide has an options table: drop --fail-on critical to report only, change the threshold, add --fail-on-gate all to fail on a policy gate, add a checkout and --local to scan on the runner, add --project-id for a specific project, and add SAFEGUARD_VERSION to pin the CLI. Combining those for release branches gives this. The branch pattern and the combination are ours; each flag is from the docs:

name: Safeguard release SBOM
on:
  push:
    branches: ['release/**']
jobs:
  sbom:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Safeguard scan (on the runner)
        env:
          SAFEGUARD_API_KEY: ${{ secrets.SAFEGUARD_API_KEY }}
          SAFEGUARD_INSTALL_DIR: ${{ runner.temp }}/safeguard-bin
          SAFEGUARD_VERSION: "<version>"
        run: |
          set -eu
          curl -fsSL https://cli.safeguard.sh/install | bash
          "$SAFEGUARD_INSTALL_DIR/safeguard" ci scan --local --wait --fail-on critical --fail-on-gate all

Line by line:

  • branches: ['release/**'] scans every push to a release branch; the docs show [main, 'release/**'] as a wildcard example.
  • actions/checkout@v4 is needed only because of --local, which reads lockfiles and manifests in the checkout (the docs name package-lock.json, go.sum, requirements.txt and pom.xml) and uploads only the results.
  • SAFEGUARD_VERSION pins the CLI so a new CLI release does not change a build under you. Replace <version> with the one you tested.
  • --wait waits for the scan, up to --timeout (default 30 minutes). --fail-on critical fails on a dependency vulnerability at or above critical, and --fail-on-gate all fails when any policy gate is not Passed or Warning.

On Safeguard or on the runner?

The CI docs give a short table for the two modes. Here it is with what each means for a release SBOM:

ModeCommandNeedsScansRelease SBOM implication
On Safeguard (default)safeguard ci scanThe repository connected as an integration, or public on github.com, gitlab.com or bitbucket.orgSAST, SBOM, dependency vulnerabilitiesThe SBOM is built by Safeguard from a clone of the commit; no checkout in the job
On the runnersafeguard ci scan --localA checkout in the job; no integrationSBOM, dependency vulnerabilitiesOnly results leave the runner, so it works for self-hosted and firewalled Git servers

For a release SBOM, either is fine. Choose --local if your source cannot leave your network or your Git server is not reachable from Safeguard. Choose the default if you also want SAST findings recorded against the release version. Note the docs' caveat: SAST findings are scanned and shown in Safeguard, but they are not part of --fail-on yet.

GitLab CI/CD

The GitLab guide offers an included template and a version without it. The version without the template, as documented:

safeguard:
  stage: test
  image: alpine:3.20
  variables:
    SAFEGUARD_INSTALL_DIR: /usr/local/bin
  before_script:
    - apk add --no-cache bash curl ca-certificates
    - curl -fsSL https://cli.safeguard.sh/install | bash
  script:
    - safeguard ci scan --wait --fail-on critical
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

To make this a release job, change the rules to match your release branches. A rule such as if: $CI_COMMIT_BRANCH =~ /^release\/.*/ is standard GitLab syntax, not something from Safeguard's docs, so check it against your GitLab version. The included template exposes the same idea as a variable: SAFEGUARD_BRANCH_PATTERN, a regular expression whose docs example is /^(main|release\/.*)$/. The template also takes SAFEGUARD_FAIL_ON, SAFEGUARD_TIMEOUT, SAFEGUARD_PROJECT, SAFEGUARD_LOCAL and SAFEGUARD_VERSION.

One GitLab detail catches people: if the SAFEGUARD_API_KEY variable is protected, GitLab passes it only to protected branches. Protect your release branches, or leave the variable unprotected, depending on which is the policy you want.

Jenkins, Azure Pipelines, Bitbucket Pipelines and any other CI

The docs have guides for Jenkins, Azure Pipelines and Bitbucket Pipelines with downloadable templates, and a generic form for anything that runs a shell step. CircleCI is detected too. For a CI that is not one of the five, pass the repository, branch and commit yourself:

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"

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 other CI. The docs say it opens the setup with your branches and severity filled in. A project's own Set up CI/CD button does the same with that project's id, which is the cleanest way to make sure every release lands in the same project.

Step 3: gate the release

A gate turns the per release record into a decision. Safeguard gives you two levels.

Severity gates

--fail-on <severity> takes critical, high, medium or low, works only with --wait, and fails on a dependency vulnerability at or above that level. Dropping it to report only is a sensible first week setting.

Policy gates

--fail-on-gate <gate> takes a gate's name, id or all, also needs --wait, and fails when the gate is not Passed or Warning. The platform evaluates the gate; the CLI reports its decision.

The Policies and Gates docs set out the actions a policy can take on a gate:

Policy actionEffect on the gate
BlockThe gate fails
WarnThe gate passes, with a warning naming the policy
NotifyThe gate passes, and a notification is sent

The docs list the conditions a policy can use, including vulnerability severity, CVSS score, EPSS score, license, component age, maintenance status and SLSA level. CISA KEV status is available as a filter in Safeguard's vulnerability views, but it is not listed as a documented gate condition, so do not build a release gate that assumes "fail on KEV" until the docs say so.

When a release legitimately needs to ship with a known issue, the route is a policy exception. A developer requests it, selects the policy and finding, gives a business justification and an expiry date, and approvers decide. The docs say the decision is recorded in an audit trail. For the full gate design, see plain English security policies to release gates in Safeguard.

Exit codes you can rely on

safeguard ci scan --wait fails the step when the scan fails, times out, or meets a --fail-on or --fail-on-gate condition. The on-runner safeguard scan is different: it exits 0 when the scan ran, whatever it found, 1 when the command failed, and 2 when results were written but the vulnerability scan did not run, so a script never mistakes a skipped scan for a clean pass. It has no severity threshold flag.

Step 4: keep a file copy of each release SBOM

The platform version is the system of record. Many teams also want files attached to the release itself, for a customer download page, a regulator request or simply for the build log. The docs describe a second path for that: scanning on the build machine.

Scan on the runner and keep the output

The CI docs give this pattern for scanning on the build machine and gating on the written files:

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

What it does:

  • safeguard scan --dir . builds an SBOM for the checkout, matches it against vulnerability databases, and writes results as JSON files in a folder per scan under --output. The usage docs say results include vuln.json, license.json and collection.json, among others.
  • The jq line exits non-zero if any match has critical severity, which is how you gate on files, since safeguard scan itself does not fail on findings.

Two notes from the docs. safeguard scan runs two open source tools (an SBOM generator and a vulnerability matcher, named on the CLI usage page) that a fresh CI container will not have, so put both on the PATH or point TOOLS_DIR at them. And a command-line safeguard scan uploads nothing to the platform.

So a release job usually runs both: safeguard ci scan for the platform version and the gate, and safeguard scan --dir . --output out/ for files you keep with your CI's own artifact feature (in GitHub Actions, GitHub's upload artifact action, not a Safeguard step).

Validate any SBOM file before you ship it

If you attach an SBOM file to a release, whether exported from Safeguard or produced by your build tool, check it against its specification first. The documented commands:

safeguard validate sbom.json
safeguard validate ./sboms --recursive
safeguard validate sbom.json --strict -o report.json

safeguard validate checks CycloneDX (JSON and XML), SPDX (JSON, YAML, tag-value and RDF) and VEX (CSAF and OpenVEX) documents. --strict treats warnings as errors and --fail-fast stops at the first error. The docs do not spell out the exit codes for validate the way they do for scan, so test how your CI sees a failing file before you rely on it as a gate.

Where the SPDX or CycloneDX file comes from

The CLI usage docs describe safeguard scan output as JSON result files and do not name an SPDX or CycloneDX file in it. The documented route to a standards-format file is the app: open the project, go to Explore SBOM, and export as SPDX (JSON, XML, RDF), CycloneDX (JSON, XML), CSV, Excel, PDF or JSON. Portal offers the same formats. For a standards-format file with no human step, the honest options today are:

  • Use the SBOM your build tool already emits, run safeguard validate on it, and run safeguard scan --sbom sbom.json to match it against vulnerability data. The CLI detects SPDX or CycloneDX automatically, and --format auto|spdx|cyclonedx sets it explicitly.
  • Ask Safeguard which export route fits your plan. The Portal docs list "CI/CD pipelines (automated publishing)" as an integration, but they do not document a command for it, so we will not show one.

Step 5: attest and sign

This is the step most often described loosely, so we will be exact, using Safeguard's Attestation and Signing docs.

What Safeguard attests

The docs list five attestation types, all in the in-toto v1 format:

PredicateWhat it says
SLSA Provenance v1Build system, source, build steps, materials used
CycloneDX SBOMComponents present in the artifact
SPDX SBOMThe same, in SPDX format
Vulnerability scanA snapshot of known vulnerabilities at build time
Safeguard ReviewA Griffin-generated review of what changed, what was patched and what was blocked

The in-toto framework has three layers: a predicate with metadata about a subject artifact, a statement binding it to that subject, and an envelope for authentication and serialization. SLSA provenance v1 describes a build through buildDefinition and runDetails. Each Safeguard attestation, the docs say, carries the artifact's SHA-256 digest, a signature made with Safeguard's key and an in-toto predicate body, so any change to the artifact invalidates it.

How Safeguard signs, in its own words

Safeguard signs with one backend: its own key. The docs describe an ECDSA P-256 key held in a cloud key management service, where the private key never leaves it. Signatures are not written to a public transparency log, so there is no public log entry to look up. You verify with cosign against Safeguard's published public key.

The docs state the trade-off directly: a public transparency log gives an independent public record of every signature, while with key-based signing you rely on Safeguard to protect the key. Know this when you write your verification policy.

Verifying a Safeguard signature in a pipeline

The documented commands use --insecure-ignore-tlog because there is no log entry. For a file with a detached signature:

KEY=<Safeguard's published public key; the Attestation and Signing docs give its URL>
cosign verify-blob --key "$KEY" --insecure-ignore-tlog --signature <file>.sig <file>

The docs also show cosign verify and cosign verify-attestation --type slsaprovenance for container images signed by Safeguard. The flag name sounds alarming. Here it means only "do not look for a public log entry", which matches how Safeguard signs. It does not switch off the signature check against the key.

What the docs say you cannot do yet

Three sentences in the Attestation and Signing docs should shape your pipeline:

  • For container images and packages, there is no CLI command for signing yet.
  • For CI-time verification of vendor artifacts, there is no CLI command for verifying or listing attestations yet.
  • There is no public attestation API yet. Attestation records for your projects, products and components are shown on their Attestation tab in the Safeguard app.

In practice, that means the attestations Safeguard produces live in the platform, where you review them on the project's Attestation tab, and anything that signs a file you ship in your own pipeline is your own signing step with your own key. The Attestation docs show cosign as the example for adding attestation to your own packages.

Your SBOM file's signature is still yours

CISA's 2026 SBOM Author Signature element is about the SBOM author. If your organisation authors the SBOM you ship, customers expect your signature, made with your release key. A Safeguard signature on a Safeguard attestation says Safeguard produced that attestation; do not present it to customers as your signature.

For more on how attestation scores and SCAL levels work across your dependencies, see attestation and provenance in Safeguard.

Step 6: publish the release SBOM in Portal

"Portal" means two things at Safeguard: the free entry tier and the SBOM publish and share product. Here we mean the product. The Portal docs describe it as the place you publish, share and redistribute SBOMs. Publishing creates an immutable copy, and sharing distributes that copy as-is.

Set up the product once

  1. Go to Portal, My Products, and click New Product.
  2. Fill in Product Name and SKU, which are required, plus optional description, category, website and logo.
  3. Configure settings. The docs list three: Visibility (public catalog or private), Auto-publish SBOMs (automatically publish new versions) and Require Approval (an approval workflow for publishing).
  4. Click Create Product.

Projects generate SBOMs; products organise them for distribution. Your pipeline writes project versions, and customers see products.

Per release: add the version and associate the SBOM

  1. Open the product and click Add Version.
  2. Enter the version number (for example 2.4.0), release date, release notes and status (Current, Deprecated or End of Life; Pre-release is also a lifecycle state).
  3. Click Associate SBOM, select the ESSCM project, then choose the specific project version your release pipeline created, for example release/2.4-9f3e1c2.
  4. Save the version, check the required fields, click Publish and confirm.

The publishing lifecycle runs Draft, Review (when approval is on), Published and Archived. If you turned on Require Approval, a second person signs off before the copy goes out, which is a sensible control for a document a regulator may later read.

The SBOM Repository also has an ESSCM sync (Settings, Integrations: enable it, select projects, set the frequency). With Auto-publish SBOMs, that gets you close to hands-off publishing. Close, because the docs do not document a CLI or CI command that publishes a specific release version, so plan a short manual step or confirm the sync behaviour with Safeguard.

If a published SBOM is wrong, unpublish the version with a reason and publish a corrected one, which is CISA's "accommodation of updates" practice done with an audit trail. Publishing versus sharing is covered in publish versus share in Safeguard Portal.

Step 7: diff release against release

The last verb is the one most teams skip, and the one that gives a per release SBOM its value. A diff answers what a customer, an auditor or your incident responder will ask: what changed between the version I had and the one I have now?

Version comparison in Portal

The SBOM Repository docs describe the comparison:

  1. Select two versions of the same product.
  2. Click Compare.
  3. Read five lists: added components, removed components, version changes, new vulnerabilities and resolved vulnerabilities.

The repository also versions SBOMs from git tags or by hand, and filters by product, version, date or component contents.

How to read a release diff

The five lists are not equally important. A short reading order that works:

  1. New vulnerabilities first. These are the ones your release introduced or that were disclosed since the last release. Check whether your gate should have caught them, and whether an exception covers them.
  2. Version changes next. A major version bump in a widely used library is where behaviour changes and new transitive dependencies arrive.
  3. Added components. Each new component is a new supplier relationship and a new license. Check the Suppliers and Licenses tab on the project for the license.
  4. Removed components. Good news, usually. Confirm the removal was intended and not a build that silently dropped a feature.
  5. Resolved vulnerabilities. Worth putting in the release notes. Customers like reading that list.

If a new vulnerability has a fixed version available, that is where autonomous remediation (GA) fits: Griffin can open a tested fix pull request on the release branch or on main. It ships switched off, with every category set to Manual, so you decide what runs on its own.

A file diff in the pipeline (our own script)

If you keep out/ from step 4 for every release, you can diff the vulnerability lists between two releases in the pipeline. This is our own sketch, not from Safeguard's docs, and we have not run it. The .data.matches[] path follows the docs' own jq example; the .vulnerability.id field is an assumption, so check it against a real vuln.json from your runner first:

jq -r '.data.matches[].vulnerability.id' previous/*/vuln.json | sort -u > prev.txt
jq -r '.data.matches[].vulnerability.id' out/*/vuln.json | sort -u > curr.txt
echo "New since last release:"; comm -13 prev.txt curr.txt
echo "Resolved since last release:"; comm -23 prev.txt curr.txt

The Portal comparison is the record you show to others. A pipeline diff like this is a convenience for the release engineer, and it only sees what the on-runner scan saw.

Container images per release

If what you ship is a container image, scan the image you built rather than only the source. The container images guide in the docs adds one step after the build:

safeguard ci scan --image "ghcr.io/acme/api:$TAG" --environment prod --wait --fail-on critical

The image is recorded under its repository, one version per environment and tag. With --environment prod and tag 1.4.2, the version is prod-1.4.2; with staging it is staging-1.4.2; a latest or missing tag uses the build's commit, as in dev-<commit>. The image is read from the local Docker daemon when the step runs on the machine that built it, or from a registry using SAFEGUARD_REGISTRY_USERNAME and SAFEGUARD_REGISTRY_PASSWORD. Only the results leave the runner.

You can fail only where it matters, for example --fail-on critical in production and none in development. More in track container images per environment in Safeguard.

The whole release pipeline, in order

Putting the steps together, a release job looks like this. The left column is the order; the right column is the documented source of each piece.

OrderStepCommand or screenWhere it is documented
1Install a pinned CLIcurl -fsSL https://cli.safeguard.sh/install with SAFEGUARD_VERSIONCI/CD Integration; each CI guide
2Platform scan and gatesafeguard ci scan --wait --fail-on critical --fail-on-gate all (add --local to scan the checkout)CI/CD Integration; GitHub Actions; GitLab CI/CD
3Image scan per environmentsafeguard ci scan --image ... --environment prod --waitContainer images
4File copy of resultssafeguard scan --dir . --output out/, then your CI's artifact stepCI/CD Integration, scanning on the build machine; CLI Usage
5Validate any SBOM file you shipsafeguard validate sbom.json --strictCLI Usage
6Sign your own release filesYour signing step, with your keyNot a Safeguard CLI command today
7Review Safeguard attestationsProject, Explore SBOM, Attestation tabESSCM Attestation; Attestation and Signing
8Publish in PortalMy Products, version, Associate SBOM, PublishPortal, My Products
9Diff against the previous releaseSBOM Repository, ComparePortal, SBOM Repository

Steps 1 to 5 automate today, step 6 is yours, and steps 7 to 9 are in the app.

Which path fits your situation

Your situationUse thisWhy
Source must not leave your networksafeguard ci scan --localOnly results leave the runner; works with firewalled Git servers
You want SAST findings recorded per release tooDefault safeguard ci scan with a connected repositoryThe on-Safeguard mode scans SAST, SBOM and dependency vulnerabilities
You release from tags on a connected repositoryThe On Release scan trigger in the project's scanning settingsThe CI docs show branch triggers only
You ship container imagessafeguard ci scan --image with --environmentOne version per environment and tag
You need files attached to the releasesafeguard scan --dir . --output out/ plus your CI's artifact stepWrites JSON results on the runner
A customer wants an SPDX or CycloneDX fileExport from Explore SBOM or Portal, then safeguard validateThe documented route to a standards-format file
You want a hard stop on policy--fail-on-gate <gate> with policy outcomes onBlock fails the gate; Warn and Notify pass it
Customers need the SBOM for the version they runPortal product version, PublishImmutable copy per version, with optional approval
You need to explain what changedPortal SBOM Repository, CompareAdded, removed, version changes, new and resolved vulnerabilities

What you will see

We will not paste invented terminal output. Here is what the docs say each step produces:

  • CI log: the scan's result and a link to it in Safeguard. Auth problems show as AUTHENTICATION_REQUIRED or INVALID_SECRET_KEY, a missing scope as MISSING_SCOPE, and two matching projects as PROJECT_AMBIGUOUS (pass --project-id).
  • Projects list: a version named <branch>-<commit> per release push, with findings and vulnerability counts.
  • Explore SBOM: tabs including Attestation, Dependencies, Provenance, Vulnerabilities and Suppliers and Licenses, and a Findings by Gate chart on the Info tab.
  • Runner: a folder per scan with vuln.json, license.json and collection.json among the files.

What Safeguard does not do

Being specific about limits saves a painful conversation later.

  • It does not sign your release files from the CLI. The docs say there is no CLI command for signing container images or packages yet, and no CLI command for verifying or listing attestations. Your own signing step, with your own key, is still needed for any file you ship.
  • It does not write its signatures to a public transparency log. Safeguard signs with its own key held in a cloud key management service. You verify with cosign and --insecure-ignore-tlog. If your policy requires a public log entry for every signature, plan for that separately.
  • It does not offer a public API for attestations or SBOM uploads today. The attestation docs say there is no public attestation API yet, and several pages that describe /v1 REST endpoints carry a banner saying those endpoints return 404. Use the CLI, CI and in-app flows.
  • It does not fail safeguard scan on findings. The on-runner scan exits 0 when it ran. Gate in the platform with --fail-on or --fail-on-gate, or with your own check on the output files.
  • It does not include SAST in --fail-on yet. SAST findings are scanned and shown in Safeguard, but the severity threshold covers dependency vulnerabilities only.
  • It does not document a tag trigger for safeguard ci scan, or a CI command that publishes to Portal. Use release branches, or the On Release trigger for connected repositories, and publish from the app or through the ESSCM sync.
  • It does not ship a CI plugin, Action or Pipe. Every system runs the CLI in a shell step. That is a feature for portability, but it means you own the pipeline file.
  • It does not decide what a release is. If your team cannot map a version number to a commit, no tool fixes that. Write the mapping down.

Common mistakes

  • Scanning main on a schedule and calling it the release SBOM. The SBOM must describe the commit you shipped. Scan the release branch or the release build.
  • Forgetting the tools on a fresh runner. safeguard scan needs its two open source scanners on the PATH or in TOOLS_DIR. Without them, watch for exit code 2, which means results were written but the vulnerability scan did not run.
  • Treating exit code 0 as clean. For safeguard scan, 0 means the scan ran. It says nothing about findings.
  • Leaving the CLI unpinned. Set SAFEGUARD_VERSION so a new CLI release does not change a release build overnight.
  • Describing Safeguard's attestation signature as your SBOM signature. They are different statements by different signers. Your customer-facing SBOM needs your signature.
  • Publishing the wrong project version. When you associate an SBOM in Portal, pick the version your release pipeline created, not the latest scan of main.
  • Building a KEV gate that the docs do not document. Use the documented policy conditions; use KEV as a filter when you triage.
  • Never reading the diff. A per release SBOM nobody compares is an archive, not a control.

FAQ

How do I generate an SBOM for every release in CI with Safeguard?

Add one shell step that installs the CLI and runs safeguard ci scan --wait, triggered on pushes to your release branches. Each push becomes a project version named <branch>-<commit>. Store a secret key with scans:trigger and scans:read as SAFEGUARD_API_KEY. Add --local with a checkout if the scan should run on the runner.

Is there a Safeguard GitHub Action or GitLab component to install?

No. The CI docs say there is no plugin, Action or Pipe. Every CI runs the CLI in a plain shell step on a Linux or macOS runner with bash and curl. GitLab has an optional job template you can include, which installs the CLI and runs the same command.

Can Safeguard fail the build on policy, not just severity?

Yes. With --wait, --fail-on-gate takes a gate name, id or all, and fails the step when the gate is not Passed or Warning. Policy outcomes must be on for your workspace. Each policy's action is Block, Warn or Notify, and exceptions go through an approval workflow with an expiry.

Does Safeguard sign SBOMs?

Safeguard signs the attestations it produces, including CycloneDX and SPDX SBOM predicates, with its own key held in a cloud key management service. You verify them with cosign against Safeguard's published public key. There is no public transparency log entry, and the docs say there is no CLI command for signing your own files yet.

How do I compare the SBOMs of two releases?

In Portal's SBOM Repository, select two versions of the same product and click Compare. You get added and removed components, version changes, and new and resolved vulnerabilities. In the pipeline, you can also diff the on-runner vuln.json files with your own script.

Can I publish a release SBOM to customers automatically?

Partly. A Portal product has an Auto-publish SBOMs setting and optional approval, and the SBOM Repository can sync from ESSCM projects. No CI command for publishing a specific version is documented.

Does Safeguard produce an SPDX or CycloneDX file in CI?

The documented safeguard scan output is JSON result files. The documented SPDX and CycloneDX export is in the app, from Explore SBOM or Portal. You can also bring your build tool's SBOM and check it with safeguard validate and safeguard scan --sbom.

How often should I generate an SBOM?

Every build or release you ship, per the NTIA 2021 minimum elements and CISA's 2026 update, plus a revised SBOM when you find an error. Safeguard's docs also say it rescans projects daily, so vulnerabilities disclosed after release show up.

Does a per release SBOM satisfy EO 14028?

It supports one practice. Section 4(e)(vii) covers providing a purchaser an SBOM for each product, and SSDF PS.3.2 covers provenance data for each release. A pipeline SBOM, your own signature and a published copy per version support those tasks, not the rest of the SSDF.

Fix what the diff shows

A release diff is most useful when it leads straight to a fix. The section below explains how Safeguard closes the gap between a new vulnerability showing up in your release SBOM and a fixed version actually shipping.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.

3. Gold Registry: start from components that are already fixed

Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingSafeguard policies and gates in CI
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

The first demo is a real Safeguard CLI session: install, open the interactive terminal, ask it to fix a lodash CVE in a repository, and watch it read the finding and advisory, ask before any command that changes something, edit package.json, run the install and tests with approval, print a summary and push nothing. It ends with /permissions, /model and /scan .. The same CLI is what your release pipeline installs; /scan . is the interactive cousin of the safeguard ci scan step in this post.

Click through it at your own pace, or open it full screen.

The second demo sets up the local runner on your own machine, approves it with its device code, then scans from the terminal. It is the same idea as --local in CI: the scan happens where your code lives, and only results go to Safeguard. It is a useful way to try a release scan on a laptop before you wire it into a pipeline.

Click through it at your own pace, or open it full screen.

To try it on your own release branches, install from safeguard.sh/download and keep the CI guides at docs.safeguard.sh/docs open while you edit your pipeline file. If you scan supplier SBOMs as well as your own, see SCA from a supplier SBOM in Safeguard, and for telling customers a CVE in your SBOM does not affect them, see ingest and share VEX and CSAF in Safeguard.

Quick check

Your release job runs safeguard scan --dir . and exits 0. Does that mean the release has no critical vulnerabilities?

No. safeguard scan exits 0 whenever the scan ran, whatever it found, and 2 when results were written but the vulnerability scan did not run. To stop a release, use safeguard ci scan --wait with --fail-on or --fail-on-gate, or check the written vuln.json yourself, as in the docs' jq example.

A customer asks you to prove the SBOM you sent for 2.4.0 has not been altered. Is Safeguard's attestation signature enough?

Not on its own. Safeguard's signature shows Safeguard produced its attestation, verifiable with cosign against Safeguard's published key, with no public transparency log. The customer is asking about your SBOM, so sign the file you ship with your own release key. Portal's published copy per version and its audit trail then show which file the customer received.

Sources

Last reviewed: 25 October 2026. Product capabilities change; check docs.safeguard.sh for the current state.

Never miss an update

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

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.