Safeguard
Product•

Policy Gates in CI with Safeguard: Block, Warn, Notify and Break-Glass

Add one Safeguard CLI step that fails the build on a policy gate, set each policy to Block, Warn or Notify, and pass urgent fixes with approved exceptions.

Safeguard Security Team
41 min read

To gate a pipeline on Safeguard policies, add one shell step that runs safeguard ci scan --wait --fail-on-gate all (or a named gate). Each policy's Action decides the result: Block fails the gate and the build, Warn passes with a warning, Notify passes and alerts. Approved, expiring exceptions are the documented way past a Block.

This post is the pipeline half of the story: copy-ready configuration for five CI systems as Safeguard's docs show it, what the CLI does with every gate outcome, and how a blocked release gets out in an emergency without turning the gate into decoration. For writing and previewing the policies themselves, see From plain English security policy to release gates in Safeguard.

Key takeaways

  • Policies and gates (GA). Policies generate findings; a gate is a checkpoint that links policies. Each policy has one Action: Block, Warn or Notify.
  • CI/CD integration through the Safeguard CLI (GA). There is no plugin, Action, Orb or Pipe to install. Every supported CI runs the CLI in a plain shell step on a Linux or macOS runner with bash and curl.
  • Two ways to fail a build. --fail-on <severity> fails on a dependency vulnerability at or above a severity. --fail-on-gate <gate> fails when a policy gate is not Passed or Warning. Both need --wait, and you can combine them.
  • Exit codes are strict about uncertainty. A Failed gate exits non zero, and so do Error, Not evaluated and a timeout. Passed and Warning exit 0.
  • Policy outcomes are switched on per workspace. No Outcome column on the Gates table means ask your Safeguard contact first.
  • Exceptions are the approval path. A request names the policy and finding, carries a business justification and an expiration date, and goes to approvers with an audit trail.
  • Break-glass is mostly yours to design. Safeguard's Guardrails docs describe emergency breakglass exceptions with two-person approval; the pipeline side, the SCM side and the after-action review are process you own.
  • The MCP server (GA) has read-only tools for gate outcomes, policy outcomes and evaluation history, when your access settings allow them.
  • Autonomous remediation (GA) ships switched off, with every category set to Manual.

Why a scan without a gate is not a control

A scan that cannot stop a merge or a release is an inventory tool. Inventory matters, but the control is the decision: this change may proceed, or it may not, and here is who said so. Without that decision, three things go wrong:

  1. The threshold lives in someone's head. One reviewer blocks a critical, another waves the same finding through because the release is late.
  2. The gate is bypassable by default. The check fails, but administrators can merge anyway, or the job is not required.
  3. Exceptions never end. A finding silenced to ship on a Friday is still silenced two years later.

A policy gate fixes the first problem. The other two need branch protection, expiring exceptions and an agreed break-glass process, which the rest of this post covers.

What NIST SSDF asks for

NIST SP 800-218, the Secure Software Development Framework (SSDF) version 1.1, describes gates without using the word:

  • PO.4.1: define criteria for software security checks and track them throughout the SDLC. Example 5 is to record security check approvals, rejections and exception requests as part of the workflow and tracking system.
  • PO.4.2: gather and safeguard the information that supports the criteria. Example 3 is to automate decision-making using the criteria and review those processes periodically.
  • PO.1.2: document security requirements. Example 7 is to handle requirement exception requests, including periodic reviews of all approved exceptions.

That is a policy gate: a written criterion, an automated decision, a recorded approval path and a periodic look back.

What NIST SP 800-204D and OWASP add

NIST SP 800-204D (February 2024) covers supply chain security in DevSecOps CI/CD pipelines. Section 5.1.1, Secure Build, says to specify build policies, enforce them with techniques such as an agent and a policy enforcement engine, and generate attestation evidence during the build. It also notes that a scan's value depends on when it ran, which is why gates should be evaluated again when new data arrives.

The OWASP Top 10 CI/CD Security Risks puts CICD-SEC-1, Insufficient Flow Control Mechanisms first: shipping code or artifacts through a pipeline without approval or review. Its recommendations include branch protection on production branches, avoiding exclusions from those rules, and requiring approval before accounts can trigger production builds. A gate is one verification step; it does nothing if it can be skipped.

For prioritisation inputs such as CISA's Known Exploited Vulnerabilities (KEV) catalog and FIRST's Exploit Prediction Scoring System (EPSS), which estimates the probability that a CVE will be exploited in the next 30 days, see Prioritise vulnerabilities with reachability, EPSS and KEV in Safeguard.

The standards side by side

SourceWhat it asks forHow a Safeguard gate mapsWhat you still own
SSDF PO.4.1Defined criteria for security checks, tracked through the SDLC; approvals, rejections and exceptions recordedPolicies (GA) hold the criteria; gate outcomes and exception records are kept in the platformChoosing the criteria and the severity bar
SSDF PO.4.2Automated decisions using the criteria, reviewed periodically; protected supporting data--fail-on-gate automates the decision in CI; evaluation history is kept per gateThe periodic review itself
SSDF PO.1.2, Example 7A process for exception requests, with periodic review of approved exceptionsRequest Exception flow with justification, expiry, approvers and an audit trailWho approves, review cadence, renewal limits
SP 800-204D 5.1.1Build policies enforced with a policy enforcement engine, with evidence generated during the buildGates evaluated by the platform on each scan; the CI step reports the outcomeBuild isolation, runner hardening, signing
OWASP CICD-SEC-1No single entity can push code or artifacts through without verificationThe gate is one verification stepBranch protection, required checks, bypass lists

How a Safeguard gate decides pass or fail

The CLI only reports what the platform decided, so start with the platform.

Policies, gates and Actions

In Safeguard's Policies & Gates docs, a policy is a rule that evaluates your SBOMs and generates findings, and a gate is a checkpoint that enforces linked policies. Each policy has one Action:

ActionWhen the policy failsWhat a developer sees in CI
BlockThe gate failsThe job fails; the CLI prints the Block policies that failed
WarnThe gate still passes, with a warning that names the policyThe job passes; the warning is printed in the log
NotifyThe gate still passes, and Safeguard sends a notification to the workspace's configured channelsThe job passes; notified policies are printed, and a notification goes out

Notify sends to the in-app bell, your connected chat integrations, and email according to each person's preferences. It is deliberately quiet: you are notified when a policy goes from passing (or never evaluated) to failing, and again when a failing policy finds new matching issues. A policy that keeps failing with the same matches is not notified again on every build.

Safeguard's offering material lists a fourth gate behaviour, require approval. In the current docs that is not a fourth Action on a policy. It is the exception workflow, covered below: gates "require approvals for exceptions", in the docs' words.

When a policy is evaluated

A policy runs its saved (starred) search when it is linked to a gate, and again after new assessment data arrives for the product. A gate can therefore turn from Passed to Failed without anyone pushing code, when a new advisory matches a component you already ship.

New policies have no threshold, so any match fails them. A policy created earlier with a threshold above zero keeps it, and fails only when the number of matches reaches that threshold.

The order that decides a gate's outcome

The Policy outcomes docs give the order exactly, and it matters because it is what your pipeline sees:

  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, and the warning names the policy.
  6. Otherwise: Passed. Failed Notify policies are listed as notified.

A gate that has never been evaluated shows Not evaluated, never Passed. An Error is never counted as a pass, so a broken policy stops the build rather than hiding behind a green tick.

What the CLI does with each outcome

safeguard ci scan --wait --fail-on-gate takes a gate name, a gate id, or all. It waits for the scan to finish and for the gate's policies to be evaluated on the new data, then exits as follows:

Gate outcomeCLI exitWhat is printedTypical cause
Passed0Notified policies, if anyNo Block or Warn policy matched
Warning0The Warn policies that matchedA rule in rollout matched
FailedNon zeroThe Block policies that failedA rule you enforce matched
ErrorNon zeroWhy the policy could not be evaluatedSaved search missing, failed or timed out
Not evaluatedNon zeroWhyNo policies linked, or evaluation not finished
Not evaluated before --timeoutNon zeroA timeout messageSlow scan or evaluation; default timeout 30 minutes

Share this table with developers. A red job can mean "the gate could not decide", and that fix is on the security team's side.

Severity threshold or policy gate

The CI/CD Integration docs give you two failure switches, both used with --wait:

FlagFails the step whenGood for
--fail-on <severity>A dependency vulnerability is at or above critical, high, medium or lowA quick, blunt bar while you set up policies
--fail-on-gate <gate>The named gate, or all gates, is not Passed or WarningRules with nuance: licenses, component age, SLSA level, EPSS, exceptions

The two can be combined. SAST findings are scanned and shown in Safeguard but are not part of --fail-on yet, and the docs show no reachability option for it. A reachable-only rule belongs in a policy: run it on Warn first and confirm it matches what you expect before setting it to Block.

The Policies & Gates docs list conditions for vulnerability severity, CVSS, EPSS, license, component age, maintenance and SLSA level. KEV is not a named gate condition; treat it as a triage filter unless a policy you have tested on Warn expresses it.

Before you start

  • A Safeguard project for the repository, or a way to make one. With the default mode, the repository must be connected as an integration, or be public on github.com, gitlab.com or bitbucket.org. The first CI run creates the project. If you would rather not connect the repository, the --local mode (below) scans on the runner. For the trade-offs, see Repo connection, CLI or local runner.
  • Policy outcomes switched on. Outcomes are turned on per workspace by Safeguard. If you cannot see an Outcome column on the Gates and Policies tables, ask your Safeguard contact. While outcomes are off, policies and gates behave as before, and you should use --fail-on only.
  • At least one gate with linked policies, each with an Action. A gate with no linked policies is Not evaluated, which fails the step.
  • Permission to add a secret in your CI system, and, for the step to matter, permission to make it a required check on the protected branch.

Step by step: add a policy gate to a pipeline

These steps use GitHub Actions for the walkthrough. Every other CI system follows the same shape, and its configuration is in the next section. The commands and YAML below are as Safeguard's docs show them on 25 October 2026; we have not run them for this post, so test in a branch first.

1. Check that outcomes are on

Open Policies, then Gates. If the table has an Outcome column showing Passed, Failed, Warning, Not evaluated or Error with a last evaluated time, outcomes are on for your workspace. If not, ask your Safeguard contact to switch them on and use --fail-on alone until they do.

2. Decide each policy's Action

Policies are created under Settings, then Policies, with Create Policy: define conditions with the visual builder or YAML, set severity and actions, save and enable. Templates such as No Critical Vulnerabilities, License Compliance and No Abandoned Packages are a good start. Then split:

  • Block only rules you would stop a release for today. For many teams that is one or two.
  • Warn for rules you believe in but have not measured.
  • Notify for rules you are still sizing.

3. Link the policies to a gate

Link the policies to a gate; an outcome appears in the Outcome column shortly after. Expand the gate to see each policy's Action, outcome, effect, matches and evaluation time. If it already shows Failed on main, you found the backlog before your developers did. Use all, or named gates such as a pull request gate and a stricter release gate.

4. Create a CI secret key

In Safeguard, go to Settings, then Developer, and create a secret key with the scopes scans:trigger and scans:read. Lock it to one project where you can, so a leaked key can only trigger scans of that project.

5. Store the key in GitHub

In GitHub, go to Settings, then Secrets and variables, then Actions, and add a new repository secret named SAFEGUARD_API_KEY. An organisation secret works too.

6. Add the workflow

Save this as .github/workflows/safeguard.yml. It is the docs' workflow unchanged:

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

No checkout step is needed in this mode: Safeguard clones the repository itself. To gate on policies, the docs' Options table says to add --fail-on-gate all. The last line then reads:

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

Swap all for a gate name or id if you want this pipeline to answer to one gate only. Drop --fail-on critical if you want the policies to be the only thing that fails the build. The Options table lists the other changes you may want: --fail-on high (or medium, low) for another threshold, --project-id for a specific project, and SAFEGUARD_VERSION in env to pin the CLI version.

7. Make the check required

A failing job only stops a merge if the branch requires it. In GitHub, protected branch rules can require status checks to pass before merging. GitHub's docs note that, by default, branch protection restrictions do not apply to people with admin permissions or custom roles with the "bypass branch protections" permission, and that you can choose to apply them to administrators too, through the "Do not allow bypassing the above settings" option. Decide that deliberately; it is the most common silent hole in a gate, and it is also part of your break-glass design below.

8. Prove it fails, then prove it passes

In a throwaway branch, open a pull request that matches a Block policy, for example by pinning an older dependency with a known critical issue. The job should fail with the policy named in the log. Revert and push; it should pass. Any other failure is explained by the tables above and below.

Each push becomes a new version of its Safeguard project, named <branch>-<commit>, for example main-a1b2c3d. On a pull request, the head branch is scanned and its head commit names the version, so you can find the exact scan a failed job refers to.

Copy-ready configuration for each CI system

All five supported CI systems use the same setup: a secret key with scans:trigger and scans:read, stored as SAFEGUARD_API_KEY, and one shell step. 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 any other CI. It opens the setup with your branches and severity filled in.

Each snippet below is the docs' version. To gate on policies, add --fail-on-gate all (or a gate name) to the ci scan line, as in step 6. We have not run these snippets for this post.

GitLab CI/CD

Add the key under Settings, then CI/CD, then Variables, as SAFEGUARD_API_KEY, with Mask variable on. GitLab's own docs explain that a protected variable is only available to pipelines on protected branches or tags, so leave Protect variable off if unprotected branches or merge requests should be scanned.

Safeguard's docs offer an include template and a version without it. This is the version without the template, which keeps every line visible in your repository:

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"

If you use the template instead, its documented variables include SAFEGUARD_FAIL_ON (empty means no severity gate), SAFEGUARD_TIMEOUT (default 30m), SAFEGUARD_PROJECT, SAFEGUARD_LOCAL and SAFEGUARD_VERSION, plus SAFEGUARD_BRANCH_PATTERN and SAFEGUARD_SCAN_MERGE_REQUESTS for branch selection. The docs do not list a template variable for the policy gate flag, so if you want --fail-on-gate and need certainty, use the job above and add the flag to the script line. On a merge request the source branch is scanned. If the job never appears, the branch does not match the pattern or the pipeline has no test stage; if you get AUTHENTICATION_REQUIRED on a merge request, the variable is protected.

For the source control side of GitLab, see Connect GitLab to Safeguard.

Bitbucket Pipelines

Add the key under Repository settings, then Repository variables, as SAFEGUARD_API_KEY, with Secured on. A workspace variable works too.

definitions:
  steps:
    - step: &safeguard-scan
        name: Safeguard scan
        image: alpine:3.20
        script:
          - apk add --no-cache bash curl ca-certificates
          - curl -fsSL https://cli.safeguard.sh/install | SAFEGUARD_INSTALL_DIR=/usr/local/bin bash
          - safeguard ci scan --wait --fail-on critical
pipelines:
  branches:
    main:
      - step: *safeguard-scan
  pull-requests:
    '**':
      - step: *safeguard-scan

Bitbucket allows one definitions key and one pipelines key, so merge this into the file you have rather than appending a second copy. The pull-requests: '**' block scans every pull request whatever its target, because Bitbucket matches that pattern against the source branch and cannot filter by target. Secured variables are not passed to fork pull requests.

Jenkins

Add the key under Manage Jenkins, then Credentials, as a Secret text credential with the ID safeguard-api-key. The agent needs Linux or macOS with curl; Windows agents are not supported.

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 when block needs a multibranch pipeline, where Jenkins knows the branch. In a single-branch job, delete the when block and every build is scanned. If the stage is always skipped, this is almost always why. The repository, branch and commit come from the Git plugin's GIT_URL, GIT_COMMIT and BRANCH_NAME (or GIT_BRANCH).

Azure Pipelines

In Azure DevOps, edit the pipeline, open Variables, and add SAFEGUARD_API_KEY with Keep this value secret on. A variable group works too.

trigger:
  branches:
    include: [main]
pr:
  branches:
    include: [main]
pool:
  vmImage: ubuntu-latest
steps:
  - checkout: self
  - bash: |
      set -eu
      export SAFEGUARD_INSTALL_DIR="$(Agent.TempDirectory)/safeguard-bin"
      curl -fsSL https://cli.safeguard.sh/install | bash
      "$SAFEGUARD_INSTALL_DIR/safeguard" ci scan --wait --fail-on critical
    displayName: Safeguard scan
    env:
      SAFEGUARD_API_KEY: $(SAFEGUARD_API_KEY)

Secret variables reach a script only through env, as shown; forgetting the mapping is the usual cause of AUTHENTICATION_REQUIRED. For Azure Repos, the pr block is ignored, so add a Build validation branch policy on the target branch instead. For GitHub and Bitbucket repositories built in Azure Pipelines, the pr block works as shown.

Any other CI

Any CI that runs a shell step works. When your CI is not one of the five above (CircleCI is detected too), 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"

Pin a CLI version with SAFEGUARD_VERSION=<version> on the install line so a release does not change a build under you.

Where to put gates in the pipeline

The same CLI step can sit at different points, and each point answers a different question.

CheckpointTriggerSuggested gate postureWhy
Pull request into mainpull_request or merge request eventsBlock on rules about new risk; Warn on backlog rulesStop new problems at the cheapest moment, without punishing a developer for old ones
Push to mainpush to the protected branchSame gate as pull requests, or report onlyCatches direct pushes and confirms what merged
Release branch or tagrelease/* branches, wildcards supportedA stricter, named release gate set to BlockThe decision that matters for customers
Scheduled run on mainYour CI's schedulerReport only, with Notify policiesNew advisories against code nobody changed; the platform also re-evaluates gates when new data arrives

A strict release gate is easier to justify than a strict pull request gate: a false positive lands on a release manager who expects scrutiny, not on every developer. For images built in the pipeline, see tracking container images per environment.

Block, Warn and Notify from the developer's chair

A Block should be unambiguous. The CLI prints the failed Block policies, so name policies as instructions ("No critical vulnerabilities in direct dependencies"), not categories ("Vuln policy 3").

A Warn should be cheap to act on. If a Warn policy mostly matches old findings in untouched code, move it to Notify until the backlog shrinks.

A Notify should reach an owner. It never fails CI, so the channel must belong to someone who triages. The Slack card sits under Integrations on the Notification tab; Safeguard's docs recommend a least privilege app with only chat:write and channels:read.

Fixes should be close at hand. Autonomous remediation (GA, ships switched off with every category set to Manual) can open a tested, policy gated pull request when a fixed version exists, so the step after a Block can be reviewing a fix.

Exceptions: the normal way past a Block

Without a way to say "we accept this for now", people delete the step. The Policies & Gates docs describe the request flow:

  1. Click Request Exception.
  2. Select the policy and the finding.
  3. Provide a business justification.
  4. Set an expiration date.
  5. Submit for approval.

Approvers are notified, one decides, and an audit trail is recorded. The Guardrails docs add that exceptions apply to a specific asset for a specific duration and expire automatically, and exception history is among the reports the Policies & Gates docs list. That covers SSDF PO.4.1 Example 5; the periodic review in PO.1.2 Example 7 is yours.

Rules that make exceptions honest

  • Shortest useful expiry. Match the fix plan, not a round quarter.
  • Requester never approves.
  • One finding per exception. Never clear a whole gate.
  • Justify for an auditor. "Release deadline" is not a justification; "not reachable, upgrade blocked by a breaking API change, fix in sprint 42" is.
  • Review monthly. An exception renewed twice means the rule, the plan or the staffing is wrong.
  • Decide in advance which rules are never waivable.

If your product is not affected, use a VEX statement instead; it is better evidence and travels with your SBOM. See Ingest and share VEX and CSAF in Safeguard.

Break-glass: the emergency way past a Block

An exception accepts a known finding. Break-glass is a different moment: production is down, the fix is ready, and a gate is failing for an unrelated reason or is itself broken. You need the release out within the hour and an exact record afterwards of what was bypassed, by whom and why.

What Safeguard's docs say

Safeguard's Guardrails & Enforcement docs say every blocking guardrail supports time-boxed exceptions, and that emergency breakglass exceptions require two-person approval and trigger alerts in the security channel. That is the Safeguard side of break-glass. The Policies & Gates docs do not describe a separate break-glass button for CI gates; their documented path past a Block is the exception workflow above.

So the honest framing is this: Safeguard gives you an approval path with an audit trail. The emergency procedure around your pipeline and your source control is yours to design, and it is worth designing before you need it.

The levers you actually have

LeverScopeWho should pull itAudit trailRisk
Exception on the findingOne policy, one finding, until expiryDeveloper requests, approver decidesSafeguard exception historyLow, if expiry is short
Emergency (breakglass) exception, per the Guardrails docsOne asset, time-boxed, two approversTwo named peopleSafeguard audit log, alert to the security channelLow to medium
Change a policy's Action from Block to WarnEvery gate that links that policy, every pipelineSecurity leadPolicy change in Safeguard; record it yourselfHigh: it relaxes the rule everywhere
Change the pipeline step to report only (drop the failure flags, as the docs' Options table describes)One pipelineRelease manager, through a reviewed changeYour source control historyMedium: easy to forget to revert
SCM admin bypass of a required checkOne mergeRepository administrator, if bypass is allowedYour SCM's audit logHigh: invisible to Safeguard

The higher the row, the narrower and better recorded the lever. Most emergencies fit the first two rows. The last three exist whether you plan for them or not.

A break-glass runbook you can adopt

  1. Write down who can declare an emergency. One role, not "anyone senior". Put it next to your incident process.
  2. Prefer an exception on the finding. If the blocking finding is known and the fix is not part of the emergency, request a short exception (hours or days, not months) with the incident reference in the justification.
  3. If approvers are slow, use the two-person emergency path. Name both people in advance. The Guardrails docs say breakglass exceptions alert the security channel; make sure that channel is watched out of hours.
  4. Only if the gate itself is broken, change the pipeline, not the policy. If the gate is returning Error or Not evaluated because of a platform or network problem, a reviewed one-line change that switches the step to report only (as the docs' Options table describes: drop the failure flag) affects one pipeline and leaves a commit you can revert. Changing a policy's Action to Warn affects every pipeline that links it.
  5. Never use SCM admin bypass as the default. If you allow it at all, restrict it to named administrators and log its use. OWASP CICD-SEC-1 explicitly recommends avoiding exclusions from branch protection.
  6. Set the revert before you ship. Open the revert pull request or the exception expiry at the same time as the bypass, not after.
  7. Review within one business day. What was bypassed, by whom, for how long, and was the gate right? Add the answer to the exception history or your incident record, which is what SSDF PO.4.1 Example 5 asks for.

When the gate cannot decide: Error, Not evaluated and timeouts

The CLI fails the step when the gate is Error or Not evaluated, or when the gate's policies were not evaluated before --timeout. This is deliberate and correct: a gate that cannot decide should not report success. It does mean the security team owns a class of red builds that developers cannot fix.

Symptom in the CI logLikely causeWho fixes itWhat to do
Gate Not evaluated, no policiesGate has no linked policiesSecurity teamLink policies, or point the pipeline at the right gate
Gate Error with a reasonA policy's saved search is missing, failed or timed outSecurity teamFix the policy's search; check the reason shown
Not evaluated before timeoutScan or evaluation slower than --timeout (default 30m)Platform teamRaise the timeout for large repositories; check scan size
AUTHENTICATION_REQUIRED or INVALID_SECRET_KEYSecret missing, misnamed, expired or revoked, or not mapped into the stepPlatform teamCheck the secret's name and that the step can read it
MISSING_SCOPEKey lacks scans:trigger or scans:readPlatform teamCreate a key with both
PROJECT_NOT_ALLOWEDKey is locked to another projectPlatform teamUse the right key, or pass the right project
PROJECT_AMBIGUOUS or INTEGRATION_AMBIGUOUSMore than one project or integration fits the repositoryPlatform teamPass --project-id (or --integration-id on the first run)
PROJECT_NOT_RESCANNABLEThe project has no repository Safeguard can clone, for example an upload projectPlatform teamConnect the repository under Integrations, or use --local
SCAN_NOT_DISPATCHED or SCAN_NOT_STARTEDThe platform could not start the scanRetry, then supportContact support with the build URL if it persists
The step never runsBranch not in the template's list, or fork builds get no secretsPlatform teamFix branch filters; decide your fork policy

Put this table in your developer docs.

Forks, secrets and the install line

Fork pull requests get no secrets. GitHub's docs say that, apart from GITHUB_TOKEN, secrets are not passed to workflows triggered from forks, and Safeguard's Bitbucket and Azure guides note the same for those systems by default. A required check on fork pull requests therefore fails to authenticate. Safeguard's GitHub guide says to scan pushes only, or use pull_request_target with care and never run the fork's code in that job.

Pin the CLI. Every guide documents SAFEGUARD_VERSION. Update the pin on purpose, in its own pull request.

Think about the install line. The documented install pipes a script into bash, while Safeguard's own pipeline hardening docs advise downloading, verifying, then running installers. If your organisation forbids piped installs, ask Safeguard which verification it supports; the CI docs do not show one.

On Safeguard or on the runner

The CI/CD Integration docs describe two places the scan can run:

ModeCommandNeedsScans
On Safeguard (default)safeguard ci scanThe repository connected as an integration, or public on github.com, gitlab.com or bitbucket.orgSAST, SBOM, dependency vulnerabilities
On the runnersafeguard ci scan --localA checkout in the job; no integrationSBOM, dependency vulnerabilities

With --local, only results leave the runner, so it works for self-hosted and firewalled Git servers. It reads lockfiles and manifests such as package-lock.json, go.sum, requirements.txt and pom.xml. In GitHub Actions, add actions/checkout@v4 before the step. The docs do not spell out how --fail-on-gate behaves with --local, so test it on a branch before making the check required.

The build-machine path, without the platform gate

safeguard scan runs fully on the runner and writes files you can keep as artifacts. It never fails on findings: it exits 0 when the scan ran, 1 when the command failed, and 2 when results were written but the vulnerability scan did not run. The CLI docs say gating on findings is done by policy gates in the platform. To gate on the files yourself, the docs show:

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

That is a local severity check, not a policy gate: no Actions, exceptions or audit trail. On a fresh runner, the scanning tools it uses must be on the PATH or under TOOLS_DIR.

After the build: reading outcomes

  • Policies, then Gates, and Policies, then Policies. On both tables the Outcome column shows Passed, Failed, Warning, Not evaluated or Error with when it was last evaluated. A policy linked to several gates shows Failed if it failed on any of them.
  • Expand a gate to see each linked policy's Action, outcome, effect on the gate, matches and evaluation time, plus the gate's evaluation history page by page.
  • From an assistant. Safeguard's MCP server (GA) has read-only tools for gate outcomes, policy outcomes and evaluation history, available when policy outcomes are on and your MCP access settings allow them. Add Safeguard to Claude or ChatGPT from their connector directories rather than configuring a custom server; see Safeguard in your IDE and in the Claude and ChatGPT connector directories.

Track two numbers monthly: the override rate (exceptions granted per Block failure, per policy) and the Error rate (evaluations ending in Error or Not evaluated). The second is the security team's queue, not the developers'.

Which option for which situation

SituationUseNot this
First week, no policies yet--fail-on critical alone--fail-on-gate with an empty gate (it fails as Not evaluated)
Policies exist, outcomes on--fail-on-gate all, optionally with --fail-onSeverity only, which ignores license and age rules
Different strictness for pull requests and releasesTwo named gates, one per pipelineOne gate and per-pipeline policy edits
A rule you are still sizingNotifyBlock "to see what happens"
A known finding you accept for nowException with a short expiryChanging the policy's Action
Not affected, with evidenceA VEX statementA permanent exception
Production emergency, approvers reachableException, or two-person emergency exceptionDeleting the step
Gate broken by a platform or network errorReviewed report-only change to one pipeline, with a revert readySwitching the policy to Warn everywhere
Fork pull requests on a public projectScan pushes after reviewpull_request_target that runs fork code
Air-gapped runner--local, or the build-machine pathAssuming the platform can clone the repository

What Safeguard does not do

  • It does not install a plugin into your CI. There is no Safeguard GitHub Action, Orb or Pipe; the GitLab option is an include template or a plain job. Every system runs the CLI in a shell step on Linux or macOS. Windows agents are not supported for the Jenkins step, per the docs.
  • It does not gate on SAST through --fail-on yet. SAST findings are scanned and shown in Safeguard, but the severity flag covers dependency vulnerabilities only. The --local mode does not run SAST at all.
  • It does not enforce your branch protection. Whether a failed check stops a merge, and whether administrators can bypass it, is configured in your source control platform. A Safeguard gate cannot stop a merge your SCM allows.
  • It does not run your break-glass process. The docs describe exceptions and two-person emergency exceptions. Who may declare an emergency, which channel is watched, and the after-action review are yours.
  • It does not review your exceptions for you. Exceptions expire, and history is reported, but the periodic review SSDF PO.1.2 Example 7 describes is a human job.
  • It does not turn on policy outcomes by itself. Outcomes are switched on per workspace by Safeguard, and until then --fail-on-gate has nothing to read.
  • It does not provide a Kubernetes admission controller today. The Guardrails docs say the admission controller is not published and warn against installing any chart that claims to be it. Gate before deploy in CI.
  • It does not harden your runners. Runner isolation, secret handling and pipeline file review (OWASP CICD-SEC-4 to CICD-SEC-7 territory) stay with your platform team.

Common mistakes

  1. Pointing --fail-on-gate at a gate with no policies. It fails every build as Not evaluated, and developers learn to distrust the check on day one.
  2. Starting with Block on everything. Most first gates fail half the repositories on old findings. Start with Notify and Warn, and Block one or two rules.
  3. Leaving the check optional. A job that fails but is not required in branch protection changes nothing.
  4. Letting administrators bypass by default. GitHub's docs note that branch protection does not apply to admins unless you say so. Decide on purpose.
  5. Protecting the GitLab variable, then scanning merge requests. Protected variables reach only protected branches, so merge request pipelines fail with AUTHENTICATION_REQUIRED.
  6. Forgetting the env mapping in Azure Pipelines. Secret variables reach scripts only through env.
  7. Using the Jenkins when block in a single-branch job. The stage is skipped every time. Delete the block.
  8. Treating Error as a developer problem. Error means the gate could not decide. Route it to whoever owns the policies.
  9. Using exceptions for false positives. If you are not affected, say so with VEX, which outlives the exception and helps your customers too.
  10. Setting a timeout shorter than a real scan. A large repository that routinely needs more than the default 30 minutes will fail as Not evaluated, which looks like a policy problem and is not.

FAQ

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

Add --fail-on-gate all, or a gate name or id, to the safeguard ci scan --wait step. The step exits non zero when the gate is Failed, Error or Not evaluated, and 0 when it is Passed or Warning. Policy outcomes must be switched on for your workspace.

What is the difference between a severity threshold and a policy gate in Safeguard CI?

--fail-on <severity> fails when a dependency vulnerability is at or above critical, high, medium or low. --fail-on-gate fails when a policy gate is not Passed or Warning, so it can enforce license, component age, SLSA level and other rules. Both need --wait, and you can use them together.

What do Block, Warn and Notify do in Safeguard?

They are policy Actions. Block fails the gate. Warn lets the gate pass with a warning naming the policy. Notify lets the gate pass and sends a notification to the in-app bell, connected chat integrations and email, without repeating the same failure on every evaluation.

How do policy exceptions work in Safeguard?

Click Request Exception, select the policy and finding, give a business justification, set an expiration date and submit for approval. Approvers are notified and decide, and the decision is recorded in an audit trail. Exceptions expire automatically.

Does Safeguard have a break-glass option for CI gates?

Safeguard's Guardrails docs describe emergency breakglass exceptions that require two-person approval and alert the security channel. The Policies & Gates docs describe the standard exception workflow. The rest of a break-glass process, such as who declares an emergency and how pipeline or branch protection changes are reverted, is yours to define.

Why did my Safeguard CI step fail when there are no vulnerabilities?

The gate may be Error (a policy could not be evaluated), Not evaluated (no linked policies, or evaluation unfinished) or timed out. All three exit non zero by design. The CLI prints the reason; route these to the team that owns the policies.

Can Safeguard gate pull requests from forks?

Not with secrets, by default. CI platforms do not pass secrets to fork builds, so the step cannot authenticate. Safeguard's GitHub guide suggests scanning pushes only, or using pull_request_target with care and never running fork code in that job.

Does Safeguard gate on KEV or reachability in CI?

The CI flags do not take KEV or reachability options. The Policies & Gates docs list conditions such as severity, CVSS, EPSS, license, component age, maintenance and SLSA level. Use KEV and reachability to prioritise, and test any policy built on them with Warn before setting it to Block.

Gates stop known vulnerable versions from shipping, but a Block only helps if there is somewhere to go next. The section below describes the ways Safeguard closes the gap between a failed gate and a fixed build, including when upstream has not shipped a fix yet.

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 CLI demo is a real session: ask the interactive terminal to fix the lodash CVE in a repository, and it reads the finding and advisory, asks before any command that changes something, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, and pushes nothing. That is the developer's next move after a Block: fix locally, push, let the gate run again.

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

The local runner demo sets up the runner on your own machine, approves it with its device code and scans from the terminal, the closest match to this post's --local pattern.

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

The Claude demo connects Claude to Safeguard with OAuth and chooses which tools Claude can use; install Safeguard from Claude's connector directory to get there. With outcomes on, the read-only gate tools answer which gates failed and why.

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

To try this on your own pipeline, download Safeguard from safeguard.sh/download and follow the CI/CD guides in the Safeguard docs.

Quick check

A gate has two linked policies: one Warn policy that matched, and one Block policy whose saved search timed out. What does the CI step do when it fails on that gate?

It exits non zero. No Block policy failed, but a policy could not be evaluated, so the gate's outcome is Error, which comes before Warning in the evaluation order. Error is never counted as a pass.

Production is down, the fix is ready, and the release is blocked by an unrelated known finding. What is the narrowest documented way past the gate?

An exception on that finding with a short expiry, the incident reference as justification and an approver's decision, or, if your process uses it, a two-person emergency breakglass exception as the Guardrails docs describe. Changing the policy's Action or deleting the CI step affects far more than the one release.

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.