Safeguard
Product•

Add Safeguard to Jenkins Pipelines: SBOM, Scan and Policy Gate

Add one declarative stage to your Jenkinsfile: store a Safeguard key as Secret text, run safeguard ci scan, and fail builds on severity or a policy gate.

Safeguard Security Team
40 min read

To add Safeguard to a Jenkins pipeline, create a Safeguard secret key with the scans:trigger and scans:read scopes, store it in Jenkins as a Secret text credential with the ID safeguard-api-key, and add one declarative stage that installs the Safeguard CLI and runs safeguard ci scan --wait --fail-on critical. Every scanned build becomes a new version of a Safeguard project with its own SBOM and dependency findings, and the stage fails the build when a vulnerability at or above your chosen severity turns up. To fail on your organisation's own rules instead of a raw severity, add --fail-on-gate and let a Safeguard policy gate decide.

There is no Jenkins plugin to install. The whole integration is a plain sh step on a Linux or macOS agent with curl. This post walks through that stage line by line, shows how to turn it into a real policy gate, covers SBOMs, container images and signing as they apply to Jenkins today, and ends with the limits you should know before you roll it out to every job.

Every Safeguard command, flag and Jenkinsfile block below is copied from docs.safeguard.sh as it read on 25 October 2026; where we combine one with a documented Jenkins step, we say so. We did not run these snippets for this post, so test them in a branch job first.

Key takeaways

  • One stage, no plugin. The Safeguard CLI (GA) runs in a declarative sh step. The docs list Linux or macOS agents with curl; Windows agents are not supported for this stage.
  • Two scan locations. By default the scan runs on Safeguard against the connected repository and covers SAST (GA), SBOM generation (GA) and dependency vulnerabilities through SCA (GA). With --local it runs on the Jenkins agent from lockfiles and manifests, and only results leave the agent.
  • Two ways to fail a build. --fail-on <severity> fails on a dependency vulnerability at or above a severity. --fail-on-gate fails when a Safeguard policy gate (Policies, Gates and Guardrails, GA) is not Passed or Warning. Both need --wait.
  • Gates follow policy Actions. Each policy's Action is Block, Warn or Notify. Approvals for legitimate exceptions go through Safeguard's exception workflow, not through a pipeline flag.
  • SAST is shown, not gated. SAST findings appear in Safeguard but are not part of --fail-on yet.
  • Signing is separate. Attestation and signing (GA) uses Safeguard's own key, verifiable with cosign, with no public transparency log. There is no CLI command for signing yet, so the Jenkins stage does not sign anything.
  • Fixes are opt in. Autonomous remediation (GA) ships switched off, with every category set to Manual.

Why supply chain checks belong in the Jenkins pipeline

Jenkins is often the one place every release passes through. If a vulnerable or unapproved component enters a release, the build is the last automated point where you can stop it cheaply. After that, the fix is a patch release, a customer notice or an incident.

Several standards and regulations say this checkpoint should exist, and they say it in fairly specific terms.

What the standards ask for

Executive Order 14028, section 4(e), asked for guidance that includes "employing automated tools, or comparable processes, that check for known and potential vulnerabilities and remediate them", operating regularly or at a minimum before a product, version or update release. Clause 4(e)(vii) adds providing a purchaser an SBOM for each product. A build stage that scans every release candidate and records an SBOM per version is the plainest way to show both.

NIST SP 800-218, the Secure Software Development Framework (SSDF) v1.1, turns that into practices. Four tasks map directly onto a pipeline stage:

  • PO.3.2 asks you to follow recommended security practices to deploy, operate and maintain tools and toolchains. Its examples include integrating tools with other tools and workflows, and code based configuration for toolchains.
  • PO.3.3 asks you to configure tools to generate artifacts of their support of secure development practices. A recorded scan per build, with an outcome, is that artifact.
  • PS.3.2 asks you to collect, safeguard, maintain and share provenance data for all components of each release, with an SBOM given as the example, and to update that data every time a component changes.
  • PW.4.4 asks you to verify that acquired open source and third party components comply with your requirements throughout their life cycles, with a notional example of building automatic detection of known vulnerabilities into the toolchain.

RV.1.2 adds configuring the toolchain to run automated analysis and testing on a regular or continuous basis.

NIST SP 800-204D (published February 2024) is the CI/CD specific companion. Its stated purpose is strategies for integrating software supply chain security measures into CI/CD pipelines, which it treats as the path code takes through build, test, package and deploy.

The OWASP Top 10 CI/CD Security Risks list names the failure modes a scan stage helps with or must avoid creating. CICD-SEC-3, Dependency Chain Abuse, is the one a dependency scan addresses. CICD-SEC-1, Insufficient Flow Control Mechanisms, is what a gate addresses: nothing should reach production without passing an enforced check. CICD-SEC-6, Insufficient Credential Hygiene, is the one you can create by mishandling the Safeguard key in Jenkins, which we cover below. CICD-SEC-9, Improper Artifact Integrity Validation, is where signing and attestation come in.

SLSA v1.0 defines build levels from L0 (no guarantees) through L1 (provenance exists), L2 (hosted build platform) and L3 (hardened builds). A scan stage does not move you up a level: it checks what you build, not how the build platform is protected.

What goes wrong without it

Without a scan in the pipeline, teams find out late: a customer questionnaire, an auditor asking for an SBOM nobody can produce for an old release, or an advisory that lands after shipping.

The opposite failure is a scan that fails builds on every medium severity finding. Developers learn to rerun or bypass it, and the gate turns into noise. Prioritisation signals help here. CISA describes its Known Exploited Vulnerabilities catalog as "the authoritative source of vulnerabilities that have been exploited in the wild" and asks organisations to use it as an input to prioritisation. FIRST's EPSS publishes a daily probability that a published CVE will be exploited in the next 30 days. Safeguard uses both in its vulnerability management (GA), and a policy gate is where you can encode how much risk a build is allowed to carry. For the prioritisation side, see prioritise vulnerabilities with reachability, EPSS and KEV in Safeguard.

What the Safeguard Jenkins integration actually is

The Safeguard docs describe CI/CD integration in one sentence: one step in your pipeline runs safeguard ci scan. Everything else is configuration around that command.

Some facts from the docs that shape how you set it up:

  • There is no plugin, Action or Pipe. Every supported CI system, Jenkins included, runs the CLI in a plain shell step. You do not install anything in the Jenkins controller's plugin manager for Safeguard.
  • The CLI is a single static binary. The installation docs say there is no runtime to install with it: no Node, no Python, no JVM.
  • Each scanned build is a version. Each push becomes a new version of its Safeguard project, named <branch>-<commit>, for example main-a1b2c3d. On a pull request, the source branch is scanned and its head commit names the version.
  • The first run creates the project. You do not need to create a project in Safeguard before the first build.
  • Jenkins gives the CLI its context. The repository, branch and commit come from the Git plugin's GIT_URL, GIT_COMMIT and BRANCH_NAME (or GIT_BRANCH). For CI systems Safeguard does not detect, you pass them with --repository-url, --branch and --commit, but Jenkins is one of the five the docs cover directly.

Where the scan runs: on Safeguard or on the agent

This is the first real decision, and it depends on how your Git server relates to Safeguard.

ModeCommandWhat it needsWhat it scans
On Safeguard (default)safeguard ci scanThe repository connected as a Safeguard integration, or public on github.com, gitlab.com or bitbucket.orgSAST, SBOM, dependency vulnerabilities
On the Jenkins agentsafeguard ci scan --localA checkout in the job; no integrationSBOM, dependency vulnerabilities

With --local, only the results leave the agent. The docs point out that this makes it work for self-hosted and firewalled Git servers, which describes a lot of Jenkins estates. The on-agent scan reads dependencies from lockfiles and manifests such as package-lock.json, go.sum, requirements.txt and pom.xml.

The trade-off is coverage. The default mode includes SAST because Safeguard clones the repository and analyses the code. The --local mode covers the SBOM and dependency vulnerabilities only. If you want both and your Git server is private, connect the repository through Safeguard's integrations first (or run Safeguard's local runner where your code lives). The post on choosing how Safeguard touches your code compares those routes in more depth.

What one run gives you

From a single safeguard ci scan run you get:

  • An SBOM for that version of the project, through SBOM generation and analysis (GA). Safeguard's offering covers SPDX and CycloneDX, with many export formats beyond those.
  • Dependency vulnerability findings from software composition analysis (GA), including transitive dependencies, with Safeguard's vulnerability management (GA) adding severity, EPSS and KEV context.
  • SAST findings in the default mode, shown in Safeguard. These do not affect --fail-on yet.
  • An exit code that fails or passes the Jenkins stage, if you asked for one with --fail-on or --fail-on-gate.

After that, Safeguard keeps watching. Its docs describe daily continuous scanning of all projects so new disclosures are matched against SBOMs you already have, which means a version built last month can pick up a new finding today without a rebuild.

Before you start: a checklist

Run through this before you touch the Jenkinsfile. Most first-run failures trace back to one of these.

  1. A Safeguard account with admin rights, or an admin who can create a secret key for you.
  2. A Jenkins agent running Linux or macOS with bash and curl. The docs say Windows agents are not supported for this stage.
  3. A multibranch pipeline if you want the when block that scans only some branches and pull requests. In a single branch job you delete that block.
  4. The Git plugin checking out the code, so GIT_URL, GIT_COMMIT and BRANCH_NAME or GIT_BRANCH are set.
  5. A decision on scan location. Either connect the repository in Safeguard (or confirm it is public on one of the three hosted services), or plan to add --local.
  6. A decision on the failure rule. Start with --fail-on critical, report only, or a policy gate. You can change it later without touching anything else.
  7. Outbound access from the agent to cli.safeguard.sh and to Safeguard.

Step by step: add Safeguard to a Jenkins multibranch pipeline

Step 1: Create a Safeguard secret key

In Safeguard, go to Settings → Developer and create a secret key with exactly two scopes: scans:trigger and scans:read. The CI overview docs note you can lock the key to one project. Do that if the key will live in a job that only ever builds one repository, because a leaked key then reaches one project rather than all of them.

If you use the in-app route, there is a shortcut. Under Integrations → Source Control, the Git card has a Set up CI/CD button for Jenkins and any other CI, and a project's own Set up CI/CD does the same with that project's ID filled in. It opens the setup with your branches and severity already filled in.

Step 2: Store the key as a Jenkins credential

In Jenkins, go to Manage Jenkins → Credentials and add the key as a Secret text credential with the ID safeguard-api-key. Jenkins' own credentials documentation describes Secret text as the kind for a token such as an API token, which is what this is.

The stage in Step 4 looks up safeguard-api-key by name, so keep the ID exact. Jenkins can hold global credentials that any job can use; a key only one team needs does not have to be visible to every job, so use the narrowest credential store your setup offers.

Step 3: Connect the repository, or plan for --local

If your Git server is reachable by Safeguard, connect the repository under Integrations using the matching card (GitHub, GitLab, Bitbucket, Azure DevOps or the generic Git card). If the repository is public on github.com, gitlab.com or bitbucket.org, you can skip this. Otherwise, you will add --local to the scan command in Step 4.

The CI docs list one specific error for this case: PROJECT_NOT_RESCANNABLE means the project has no repository Safeguard can clone, and the fix is to connect the repository under Integrations or use --local.

Step 4: Add the stage to your Jenkinsfile

This is the stage exactly as the Safeguard Jenkins docs show it:

stage('Safeguard scan') {
    when {
        anyOf {
            branch pattern: '^(main)$', comparator: 'REGEXP'
            changeRequest target: '^(main)$', comparator: 'REGEXP'
        }
    }
    steps {
        withCredentials([string(credentialsId: 'safeguard-api-key', variable: 'SAFEGUARD_API_KEY')]) {
            sh '''
                set -eu
                export SAFEGUARD_INSTALL_DIR="$WORKSPACE/.safeguard-bin"
                curl -fsSL https://cli.safeguard.sh/install | bash
                "$SAFEGUARD_INSTALL_DIR/safeguard" ci scan --wait --fail-on critical
            '''
        }
    }
}

The docs page also links a full downloadable Jenkinsfile that wraps this stage in a pipeline block with agent any and sets SAFEGUARD_INSTALL_DIR in an environment block. Its when patterns are written slightly differently from the page (a plain main pattern and changeRequest target: 'main'), but the logic is the same. Use whichever matches your existing Jenkinsfile style.

Step 5: Understand each line before you commit it

It is worth reading the stage closely, because each line carries a decision.

The when block. anyOf runs the stage if either condition matches. branch pattern with comparator: 'REGEXP' matches branch builds against a regular expression; Jenkins' Pipeline syntax reference documents the comparator parameter and its REGEXP option for exactly this. changeRequest target matches pull requests (change requests, in Jenkins terms) whose target branch matches the pattern. Together they scan pushes to main and pull requests into main.

withCredentials with string(...). This is the Credentials Binding step. It reads the Secret text credential safeguard-api-key and exposes it to the enclosed steps as the environment variable SAFEGUARD_API_KEY, which is the name safeguard ci scan reads. Jenkins masks the value in the build log, though its own docs are frank that masking is best effort and can be circumvented by anyone who can change the job.

The triple single quotes. The sh '''...''' block uses single quotes on purpose. Jenkins' Jenkinsfile documentation explains that single quotes let the shell expand the secret as an environment variable, while double quotes make Groovy interpolate it first, which can expose it in operating system process listings. Keep the single quotes when you adapt the stage.

set -eu. The shell exits on the first failing command (-e) and treats unset variables as errors (-u). If the installer fails, the stage fails, instead of carrying on and calling a binary that is not there.

SAFEGUARD_INSTALL_DIR. The installer reads this variable and puts the binary there instead of its default location. Pointing it inside $WORKSPACE means the agent does not need write access to /usr/local/bin and each workspace gets its own copy.

The installer line. curl -fsSL https://cli.safeguard.sh/install | bash downloads and runs the installer, which the installation docs say resolves the current release, verifies the download against published checksums and refuses to install on a mismatch. More on the risks under common mistakes.

safeguard ci scan --wait --fail-on critical. --wait makes the command wait for the scan to finish (or for --timeout, default 30 minutes). --fail-on critical fails the step when a dependency vulnerability at or above critical turns up. Without --wait, the step only triggers the scan and cannot fail on what it finds.

Step 6: Push and watch the first run

Commit the Jenkinsfile change on a branch, open a pull request into main, and let the multibranch pipeline pick it up. The first run creates the Safeguard project, so the stage may take longer than later runs. When it finishes, the build log shows the result, and in Safeguard you will find a new version named after the branch and commit.

If the stage is skipped entirely, the job is probably not a multibranch pipeline, so branch and changeRequest never match. The docs give that exact troubleshooting line, and the fix is to delete the when block.

Step 7: Review the findings in Safeguard

Open the project from the Projects table, which shows version, status, findings by severity and a vulnerability count, then open the version your build created. Use this first look to decide whether --fail-on critical is right, too strict or too loose for this repository before you widen the rollout.

Choosing which branches and jobs to scan

The when block in the docs is a starting point. Three changes cover most estates.

Scan release branches as well as main. The docs give the example pattern '^(main|release/.*)$'. Use the same pattern in both the branch and changeRequest lines if you want pull requests into release branches scanned too.

Scan branch builds only. Remove the changeRequest line and the anyOf wrapper. Pull request builds then skip the stage, which can make sense if your pull request checks run elsewhere and you only want a record of what merged.

Scan every build in a single branch job. In a freestyle or plain pipeline job, Jenkins does not know the branch the way a multibranch job does, so delete the when block. Every build is scanned, and the branch comes from GIT_BRANCH.

A practical pattern for larger estates: gate pull requests into main and release branches with --fail-on critical or a policy gate, and scan everything else in report-only mode. You get an SBOM and findings for every branch without blocking experimental work.

Options you will use, with the exact flags

The Jenkins docs list four common changes. The CI overview and container docs add the rest.

ChangeHowSource page
Report only, never failDrop --fail-on criticalJenkins
A different severity threshold--fail-on high (or medium, low)Jenkins
Scan on the agent instead of on SafeguardAdd --localJenkins
Record under a specific projectAdd --project-id <id>Jenkins
Pin the CLI versionexport SAFEGUARD_VERSION=<version> before the install lineJenkins
Fail on a policy gate--fail-on-gate <gate name or id, or all> with --waitCI overview, policy outcomes
Longer wait--timeout (default 30m)CI overview
Disambiguate on first run--integration-idCI overview troubleshooting
Scan a built container image--image <ref> --environment <name>Container images

--fail-on and --fail-on-gate can be combined. --image is a different target from code; the docs list an error for passing both --image and --local --path.

Turning the scan into a policy gate

A severity threshold is a blunt rule. It does not know that one critical finding is in a test dependency, that a licence is banned regardless of vulnerabilities, or that your production services must carry no KEV listed vulnerabilities while internal tools may carry a few. A policy gate does.

Policies and gates, in Safeguard's terms

Safeguard's Policies and Gates docs draw the distinction clearly:

  • Policies are rules that evaluate your SBOMs and generate findings. They can be advisory or blocking.
  • Gates are checkpoints that enforce policies. They block when policies fail, integrate with CI/CD and require approvals for exceptions.

You create policies under Settings → Policies → Create Policy, using the visual builder or YAML, then set severity and actions, save and enable. Safeguard also supports creating policies from plain English prompts (GA). The docs list condition types including vulnerability severity, CVSS score, EPSS score, licence, component age, maintenance (abandoned packages) and SLSA level, along with templates such as No Critical Vulnerabilities, License Compliance, EO 14028 Compliance, SLSA Level 2 and No Abandoned Packages. For the policy writing side, the post on plain English security policies to release gates goes deeper.

One thing to check before you rely on a KEV rule in particular: in Safeguard, KEV status is available as a filter and prioritisation signal. The Policies and Gates docs do not list KEV as its own condition type. If your rule is "no known exploited vulnerabilities", build it from the conditions the docs show and test it against a project whose findings you already know.

What Block, Warn and Notify do

Each policy has one Action, and the Action decides what a failed policy does to the gate:

ActionWhen the policy failsEffect on the Jenkins build with --fail-on-gate
BlockThe gate failsStage exits non-zero and prints the Block policies that failed
WarnThe gate still passes, with a warning naming the policyStage exits 0 and prints the warning
NotifyThe gate still passes, and Safeguard notifies your configured channelsStage exits 0 and lists the policy as notified

Notify goes to the in-app bell, connected chat integrations and email according to each person's preferences. It does not repeat on every evaluation: you are notified when a policy goes from passing (or never evaluated) to failing, and again when a failing policy finds new matching issues.

How a gate's outcome is decided

The policy outcomes docs give the order, and it is worth knowing because it explains builds that fail for reasons other than a vulnerability:

  1. No policies linked: Not evaluated.
  2. Any Block policy failed: Failed.
  3. Otherwise, any policy could not be evaluated: Error, with the reason.
  4. Otherwise, any policy not evaluated yet: Not evaluated.
  5. Otherwise, any Warn policy failed: Warning. The gate passes.
  6. Otherwise: Passed. Failed Notify policies are listed as notified.

An Error is never counted as a pass, and a gate that has never been evaluated shows Not evaluated, never Passed. In Jenkins terms, --fail-on-gate exits 0 only on Passed or Warning. It exits non-zero on Failed, on Error, on Not evaluated, and when the gate's policies were not evaluated before --timeout, with a message saying why.

Policy outcomes must be on for your workspace

This is the step people miss. The docs say policy outcomes are turned on per workspace by Safeguard. If your Policies → Gates and Policies → Policies tables do not show an Outcome column, ask your Safeguard contact to turn it on. While it is off, --fail-on-gate has no outcomes to read.

The gated stage

With outcomes on, the scan line becomes this. We have combined the two documented flags here as the policy outcomes page says you can; the rest of the stage stays as in Step 4.

"$SAFEGUARD_INSTALL_DIR/safeguard" ci scan --wait --fail-on critical --fail-on-gate all

all checks every gate. You can name one gate by its name or ID instead, which is useful when different pipelines should answer to different gates, for example a stricter gate for services that ship to customers.

The command waits for the scan and for the gate's policies to be evaluated on the new data, then prints the Block policies that failed, which is what a developer needs in the Jenkins console.

Exceptions: how approvals work

There is no pipeline flag to approve a failing build. Approvals go through Safeguard's exception workflow, which the Policies and Gates docs describe step by step:

  1. A developer clicks Request Exception.
  2. They select the policy and finding.
  3. They give a business justification.
  4. They set an expiry date.
  5. They submit for approval, approvers are notified, decide, and the decision is recorded in the audit trail.

So the fix for a legitimately blocked build is an exception in Safeguard followed by a rebuild, not an edit to the Jenkinsfile, and the audit trail of who approved what, until when, lives in Safeguard. A Jenkins parameter that skips the stage recreates the bypass the gate exists to prevent.

SBOMs from Jenkins builds

Every safeguard ci scan run records an SBOM for that version in Safeguard, so in most setups you do not need to do anything extra to meet the "SBOM per release" expectation. For export, Safeguard's SBOM generation and analysis (GA) covers SPDX and CycloneDX. If you need to share SBOMs with customers, Safeguard's Portal product handles publishing and sharing, described in publish versus share for SBOMs. For the SBOM fundamentals and the regulatory drivers, see SBOM generation and analysis in Safeguard.

Some teams also want the scan output stored as a Jenkins build artifact, next to the binary it describes. The CI overview docs describe a second route for that: run the scan on the build machine and keep its files.

The on-agent file route

safeguard scan runs fully on the agent and writes files. It reports without failing the build, so you gate on the files. The docs show this pair of commands:

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

The first command builds an SBOM for the directory, matches it against vulnerability databases and writes results as JSON files, including vuln.json, license.json and collection.json, in a folder per scan. The second uses jq -e to exit non-zero when any match has a critical severity.

To keep those files in Jenkins, you would wrap them in a stage and add Jenkins' core archiveArtifacts step. That combination is ours, assembled from the Safeguard CI docs and Jenkins' core step reference, and we have not run it:

stage('SBOM files') {
    steps {
        sh '''
            set -eu
            safeguard scan --dir . --output out/
            jq -e '[.data.matches[].vulnerability.severity | ascii_downcase]
                   | index("critical") | not' out/*/vuln.json
        '''
    }
    post {
        always {
            archiveArtifacts artifacts: 'out/**', allowEmptyArchive: true
        }
    }
}

Three caveats from the docs apply to this route:

  • Scanners must be present. safeguard scan uses two open source scanners that the interactive terminal downloads on first run. On a fresh CI agent that has never opened the terminal, put them on the PATH or point TOOLS_DIR at a directory that holds them. The CLI usage page names both.
  • Exit codes mean "ran", not "clean". safeguard scan 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 that for a clean pass. Do not treat exit 0 as a pass.
  • Sign-in differs. The CLI usage page says the general CLI commands are authorised by signing in once on the machine, and that only a scan from the signed-in interactive terminal uploads results to the platform. safeguard ci scan is the route designed for CI keys. If you want the build recorded in Safeguard, keep the ci scan stage; use the file route as an addition, not a replacement.

Validating SBOMs you receive

If your Jenkins jobs consume supplier SBOMs, the CLI can check them against their specifications before anything else uses them:

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

It checks CycloneDX, SPDX and VEX (CSAF and OpenVEX) documents against their specifications; --strict treats warnings as errors and --fail-fast stops at the first error. To match a supplier SBOM against vulnerabilities and track it over time, see SCA from a supplier SBOM in Safeguard.

Scanning the container image your Jenkins job builds

If the Jenkins job builds a container image, you can scan the image as well as the source. The container images docs show one command, run after the image is built:

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

The image is recorded in Safeguard under its repository, one version per environment and tag (for example prod-1.4.2). Use one step per environment, each with its own --environment, and fail only where it matters, for example --fail-on critical in production and no --fail-on in development.

The scan reads the image from the local Docker daemon when the step runs on the machine that built it. Otherwise it pulls from the registry, which needs SAFEGUARD_REGISTRY_USERNAME and SAFEGUARD_REGISTRY_PASSWORD set as CI secrets. In Jenkins that means two more credentials bound with withCredentials, in the same pattern as the API key. The docs' worked example is for a different CI system, so treat any Jenkins version you write as your adaptation and test it. The full per-environment pattern is in tracking container images per environment, and the findings themselves are covered in scanning container images in Safeguard.

Signing and attestation: what to expect in Jenkins

Teams often expect a CI integration to sign what it scans. Here is where the docs stand today.

  • Safeguard signs with its own key. The attestation docs say every attestation Safeguard produces is signed with Safeguard's own ECDSA P-256 key, held in a cloud key management service, and verified with cosign against Safeguard's published public key.
  • There is no public transparency log. Signatures are not written to one, which is why the documented cosign verification commands pass --insecure-ignore-tlog. The docs state the trade-off directly: you rely on Safeguard to protect the key.
  • There is no CLI command for signing yet, and no CLI command for verifying or listing attestations yet. The Jenkins stage in this post does not sign anything.
  • Attestation records are in the app. Projects, products and components show them on their Attestation tab. There is no public attestation API yet.
  • SLSA level depends on the build platform. The attestation docs' SLSA table lists the build systems that reach L2 and L3, and Jenkins is not among them. For a Jenkins built artifact, that comes down to how you harden and isolate your builds.

For verification of Safeguard signed artifacts, the cosign commands and the public key location are on the Attestation and Signing docs page.

Decision table: requirement, what Safeguard does in Jenkins, what you still own

RequirementSourceWhat the Safeguard Jenkins stage doesWhat you still own
Automated checks for known vulnerabilities before releaseEO 14028 sec. 4(e)(iv); SSDF PW.4.4Scans every matching build for dependency vulnerabilities and can fail it at a severityChoosing which jobs and branches run it, and the threshold
An SBOM per product releaseEO 14028 sec. 4(e)(vii); SSDF PS.3.2Records an SBOM per scanned version, named by branch and commitDeciding which versions are releases and sharing SBOMs with buyers
Evidence that the toolchain enforces security practicesSSDF PO.3.3Gate outcomes and evaluation history recorded in SafeguardLinking gates to the right products and keeping policies current
Enforced flow control before productionOWASP CICD-SEC-1--fail-on-gate fails the build on a Block policyBranch protection, required checks and who can edit the Jenkinsfile
Dependency chain riskOWASP CICD-SEC-3SCA across direct and transitive dependenciesLockfile hygiene and pinning in your own builds
Credential hygiene for CIOWASP CICD-SEC-6Narrow key scopes; key can be locked to one projectCredential scope in Jenkins, rotation and job permissions
Artifact integrityOWASP CICD-SEC-9; SLSASafeguard signs its own attestations with its key, verifiable with cosignSigning your own build outputs and hardening the build platform
Continuous checking after releaseSSDF RV.1.2Daily continuous scanning of recorded SBOMsActing on new findings for shipped versions

Troubleshooting the Jenkins stage

The CI overview docs list the messages you are most likely to see. Here they are with the Jenkins specific cause.

MessageLikely Jenkins causeWhat to do
AUTHENTICATION_REQUIRED or INVALID_SECRET_KEYThe credential ID does not match safeguard-api-key, the variable name is not SAFEGUARD_API_KEY, or the key expired or was revokedCheck the credential ID and the withCredentials variable name; create a new key if needed
MISSING_SCOPEThe key lacks scans:trigger or scans:readCreate a key with both
PROJECT_NOT_ALLOWEDThe key is locked to another projectUse the right key, or pass --project-id for the allowed project
PROJECT_AMBIGUOUS or INTEGRATION_AMBIGUOUSMore than one project or integration matches the repository URLPass --project-id, or --integration-id on the first run
PROJECT_NOT_RESCANNABLESafeguard cannot clone the repositoryConnect it under Integrations, or add --local
SCAN_NOT_DISPATCHED or SCAN_NOT_STARTEDThe platform could not start the scanRetry; if it persists, contact support with the build URL
Stage always skippedNot a multibranch pipelineDelete the when block
curl: command not foundAgent lacks curl, or is a Windows agentUse a Linux or macOS agent with curl

For anything not in the table, safeguard doctor checks the environment, sign-in, tools and policy and prints problems in priority order. It changes nothing and exits 0 whenever the checks ran, so read its output rather than its exit code.

What Safeguard does not do

Honest limits, so you can plan around them:

  • No Jenkins plugin, and no Windows agents for this stage. The integration is a shell step on Linux or macOS. If your build farm is Windows only, you need a Linux or macOS agent for the Safeguard stage, or a different arrangement.
  • SAST does not fail the build yet. In the default mode SAST findings are scanned and shown in Safeguard, but --fail-on covers dependency vulnerabilities only. With --local, SAST does not run at all.
  • The Jenkins stage does not sign or verify. There is no CLI command for signing, verifying or listing attestations yet. If you need your own Jenkins outputs signed, you still need your own signing step.
  • It does not check the Jenkinsfile itself. Safeguard's CI/CD Security checks read pipeline definition files, but the docs say Jenkins files are not checked yet. Securing the Jenkins controller, agents, plugins and job permissions remains your work.
  • --fail-on-gate needs policy outcomes switched on. Safeguard turns them on per workspace. Until then, use --fail-on with a severity.
  • The on-agent route depends on lockfiles and manifests. --local reads dependencies from files such as package-lock.json, go.sum, requirements.txt and pom.xml. Ecosystems or lockfile formats the CLI does not read will not appear. Some newer Python lockfile formats are not read today; check the CLI support matrix for your stack.
  • It does not fix anything by itself. Autonomous remediation can open fix pull requests, but it ships switched off with every category set to Manual. Nothing changes your code until someone turns a category on.
  • It does not replace platform hardening for SLSA. A scan stage is not provenance from a hardened builder. SLSA Build L2 and L3 depend on how your Jenkins builds run.

Common mistakes

Using double quotes in the sh block. Swapping ''' for """ to interpolate a Groovy variable makes Groovy expand the secret, which Jenkins' docs warn can leak it through process listings. Keep single quotes and pass extra values as environment variables.

Leaving the CLI version floating. The installer resolves the newest release on every run. That is fine on a laptop and a problem in CI, because a new release can change a build under you. The docs recommend pinning with SAFEGUARD_VERSION for CI. Pin it, and update the pin on purpose.

Piping the installer without reading it. The installation docs themselves say never to pipe a URL you have not read into a shell, theirs included. Read the installer once, and consider baking a pinned binary into your agent image so builds do not depend on a live download. Checksum verification (the shell installer fails closed if the checksums file is missing) helps, but it does not replace knowing what runs on your agents.

Adding --fail-on without --wait. Without --wait, the step only triggers the scan. It cannot fail on findings it has not seen.

Treating safeguard scan exit 0 as clean. On the file route, 0 means the scan ran. Gate on the output files, and treat exit 2 as "the vulnerability scan did not run".

Blocking on medium severity on day one. Start with --fail-on critical or report only, look at a week of results, then tighten. A gate that fails every build will be bypassed.

Skipping the outcome column check. Teams add --fail-on-gate all, see builds fail with Not evaluated, and assume the gate is broken. Check that policy outcomes are on and that at least one policy is linked to the gate.

Using one global key for everything. A single key with access to every project, stored as a global Jenkins credential, means one leaked build log or one compromised job reaches everything. Lock keys to projects where you can, and scope credentials in Jenkins.

Editing the Jenkinsfile to get past a block. If a block is wrong, fix the policy. If it is right but you need to ship, request a time limited exception in Safeguard. Both leave an audit trail; a commented out stage does not.

FAQ

Is there a Safeguard plugin for Jenkins?

No. The docs say there is no plugin, Action or Pipe to install for any CI system. Jenkins runs the Safeguard CLI in a plain sh step inside a declarative stage, using a Secret text credential for the key.

Which Jenkins agents does the Safeguard stage support?

Linux or macOS agents with bash and curl. The Jenkins docs page says Windows agents are not supported for this stage.

How do I fail a Jenkins build on critical vulnerabilities with Safeguard?

Run safeguard ci scan --wait --fail-on critical. The step fails when a dependency vulnerability at or above critical is found. Change critical to high, medium or low for a stricter threshold, or drop --fail-on to report only.

How do I fail a Jenkins build on a Safeguard policy gate?

Add --fail-on-gate with a gate name, ID or all, together with --wait. The build passes when the gate is Passed or Warning and fails when it is Failed, Error or Not evaluated. Policy outcomes must be turned on for your workspace first.

Does the Safeguard Jenkins stage generate an SBOM?

Yes. Each scanned build becomes a version of a Safeguard project with its SBOM, named <branch>-<commit>. If you also want SBOM related files as Jenkins artifacts, the on-agent safeguard scan --dir . --output out/ route writes JSON results you can archive.

Can I scan a private, self-hosted Git server from Jenkins?

Yes. Add --local so the scan runs on the Jenkins agent from the checkout, and only results leave the agent. That covers the SBOM and dependency vulnerabilities. For SAST as well, connect the repository so Safeguard can scan it.

Does Safeguard sign Jenkins build artifacts?

Not from the Jenkins stage. Safeguard signs its own attestations with its own key, verifiable with cosign and with no public transparency log, but the docs say there is no CLI command for signing yet.

Why is my Safeguard stage always skipped in Jenkins?

The when block with branch and changeRequest only matches in a multibranch pipeline. In a single branch or freestyle job, delete the when block and every build is scanned.

Can the Safeguard scan fail the build on SAST findings?

Not yet. SAST findings are scanned and shown in Safeguard in the default mode, but they are not part of --fail-on. Use policies and the exception workflow to track them until that changes.

Before the demos, a word on what happens after the gate does its job. A blocked Jenkins build tells you a component must change; the section below, which runs in every post in this series, covers how Safeguard helps you change it, whether or not upstream has shipped a fix.

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, the same binary your Jenkins stage installs. You install the CLI, run safeguard to open the interactive terminal and ask it to "fix the lodash CVE in this repo". It reads the finding and the advisory, asks before any command that changes something, edits package.json to lodash 4.17.21, runs npm install and npm test, prints a summary and pushes nothing. That is the developer side of a blocked build: the gate in Jenkins says what must change, and the CLI helps make the change locally for a person to review.

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, and then scans from the terminal. It is a useful companion to the --local route in Jenkins: when your Git server sits on a private network, scanning happens on infrastructure you control and only results leave it.

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

The third demo connects Claude to Safeguard. You add Safeguard from Claude's connector directory, where it has a community listing, sign in with OAuth, and choose which tools Claude can use. Once your Jenkins builds are recording versions, you can ask Claude which gate failed on a build and why, within the tools your administrator has enabled; the policy outcomes docs say the MCP server has read only tools for gate outcomes, policy outcomes and evaluation history when outcomes are on.

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

To try it on your own pipeline, get the Safeguard apps and CLI at safeguard.sh/download and keep the Safeguard docs open for the current flags.

Quick check

Why does the Safeguard Jenkins stage use triple single quotes around the sh block?

So the shell, not Groovy, expands SAFEGUARD_API_KEY. Jenkins' docs warn that double quotes let Groovy interpolate the secret, which can expose it in process listings.

Your Jenkins build fails on a Safeguard gate that shows Not evaluated. What do you check first?

That policy outcomes are turned on for your workspace (an Outcome column appears on the Gates and Policies tables) and that the gate has at least one linked policy. A gate with no policies is Not evaluated, and Not evaluated fails the build.

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.