In Safeguard, a policy is a rule that looks for matching issues in your scan data, and a gate is the checkpoint that decides what a failed policy means. To set one up, create a policy, choose Block, Warn or Notify, link it to a gate, then fail your CI job on that gate with safeguard ci scan.
This guide takes you from an empty Policies page to a pipeline that stops risky builds and explains why. By the end you will have:
- One gate, called Release in the examples, with three or four policies linked to it.
- Each policy on the right Action, after a short trial on Warn.
- A CI job that fails when the gate fails and passes with a printed warning when only a Warn policy trips.
- An exception process with expiry dates and an audit trail.
- A fix loop in the Safeguard CLI for whatever the gate catches.
You keep the AI tools you already use while you do this. Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. You can also connect Claude or ChatGPT to the Safeguard MCP server and ask about gate results from the chat you already use.
How policies, gates and actions fit together
Three words carry the whole model, so it is worth getting them straight before you click anything.
Policies are rules that evaluate your SBOMs and generate findings. They define acceptable risk thresholds and required security controls, and they can be advisory or blocking.
Gates are checkpoints that enforce policies. They block deployments when policies fail, integrate with CI/CD pipelines and require approvals for exceptions.
Actions connect the two. Each policy has exactly one Action, and it decides what a failed policy does to the gate:
| Action | When the policy fails |
|---|---|
| Block | The gate fails |
| Warn | The gate still passes, with a warning that names the policy |
| Notify | The gate still passes, and Safeguard sends a notification to your workspace's configured channels |
Notify goes to the in-app bell, your connected chat integrations, and email according to each person's preferences. It does not repeat itself. 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 stays quiet.
Outcomes
Every evaluation records an outcome for each policy:
| Outcome | Meaning |
|---|---|
| Failed | The policy's starred search found matching issues |
| Passed | The search ran to the end and found nothing |
| Error | The policy could not be evaluated, for example because its starred search is missing, failed or timed out; the reason is shown |
| Not evaluated | The policy has not been evaluated on this gate yet |
An Error is never counted as a pass. That one rule saves you from the worst kind of gate: the one that goes green because it could not run.
The gate's own outcome is worked out from its linked policies in this order:
- No policies linked: Not evaluated.
- Any Block policy failed: Failed.
- Otherwise, any policy could not be evaluated: Error, with the reason.
- Otherwise, any policy not evaluated yet: Not evaluated.
- Otherwise, any Warn policy failed: Warning. The gate passes and the warning names the policy.
- Otherwise: Passed, with failed Notify policies listed as notified.
A gate that has never been evaluated shows Not evaluated, never Passed.
When evaluation happens
A policy runs its starred search each time Safeguard evaluates it on a gate: when the policy is linked to the gate, and again after new assessment data arrives for the product. So you do not schedule anything. Link the policy, and every new scan re-runs it.
New policies have no threshold, so any match fails the policy. A policy created earlier with a threshold above 0 keeps it, and fails only when the number of matches reaches that threshold.
Before you start: policy outcomes
Policy outcomes are turned on per workspace by Safeguard. If you do not see an Outcome column on the Gates and Policies tables, ask your Safeguard contact to turn it on. While it is off, policies and gates behave as they did before, and the CI flag that fails a job on a gate (covered below) needs it.
Step by step: build your first policy and gate
Do this for one product first. Once the pattern works, copying it to the rest takes minutes.
- Pick the gate and its job. Decide what the gate protects. "Release" is a good first gate: it answers "can this version ship?" Keep the name short, because you will type it in CI.
- Write the search first. A policy runs its starred search, so start in Search. Ask Griffin in plain English, for example "critical vulnerabilities in payments-api" or "reachable and exploitable vulnerabilities only". Griffin reads severity, licence, project, reachability and exploitability out of the sentence, so there is no query language to learn. Type
@to pick the project instead of spelling its name. When the results look right, click the Star icon on the conversation. It now lives under Starred searches in the sidebar. - Create the policy. Go to Settings → Policies and click Create Policy. Define the conditions with the visual builder or in YAML, set the severity and Action, then save and enable it.
- Choose conditions. The docs list these condition types:
| Condition | Example |
|---|---|
| Vulnerability severity | No critical CVEs |
| CVSS score | No vulnerabilities above 7.0 |
| EPSS score | No actively exploited CVEs |
| License | No GPL licenses |
| Component age | No components older than 2 years |
| Maintenance | No abandoned packages |
| SLSA level | Require SLSA Level 2 attestations |
- Or start from a template. Built-in templates include No Critical Vulnerabilities, License Compliance, EO 14028 Compliance, SLSA Level 2 and No Abandoned Packages. A template is the fastest way to a sensible first policy, and you can edit it afterwards.
- Set the Action to Warn for now. Every new policy should spend a week or two on Warn. You see exactly what it would have blocked without stopping a single release.
- Link the policy to the gate. Linking triggers an evaluation, so the Outcome column fills in straight away.
- Read the result. Open Policies → Gates. The Outcome column shows Passed, Failed, Warning, Not evaluated or Error with when it was last evaluated. Expand the 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. Policies → Policies shows the same per policy; a policy linked to several gates shows Failed if it failed on any of them.
- Clean up, then promote. Fix or document what the Warn period surfaced. When a policy only fires on things you would really stop a release for, change its Action to Block.
A sensible starter set
There is no single right list, but this one maps cleanly onto the conditions above and gives each Action a clear job:
| Policy | What it catches | Final Action |
|---|---|---|
| No critical vulnerabilities | Critical severity findings | Block |
| Reachable and known exploited | Reachable findings on CISA's KEV list | Block |
| Licence compliance | Components outside your approved licences | Warn, then Block once the allow list is settled |
| No abandoned packages | Unmaintained dependencies | Notify |
The reachable and known exploited policy is the one that cuts noise. Safeguard's reachability analysis classifies findings as Reachable, Conditionally reachable, Unreachable or Unknown, and the docs show a rule that blocks only on the first two when the finding is also on KEV and in production. That keeps a release from stopping over a high CVSS score in code nobody calls.
If you prefer YAML
The Guardrails and Enforcement docs describe the policy language. Each policy has targets, rules and an effect, and rules evaluate against a document that combines SBOM, vulnerability, licence, signature, provenance and reachability data:
apiVersion: safeguard.sh/v1
kind: Policy
metadata:
name: block-critical-cves-in-prod
spec:
targets:
- kind: Image
labels:
env: production
rules:
- id: no-known-exploited-critical
description: Block production images containing KEV-listed CVEs.
condition: |
any(
vulnerabilities,
severity == "critical" AND kev == true
)
effect: BLOCK
- id: requires-slsa-l3
condition: slsa.level < 3
effect: WARN
Keep YAML policies in version control next to the code they protect, and review changes to them like any other change.
Wire the gate into CI
Gates are evaluated by the platform on the scans it runs for your connected repositories. They are not evaluated by a command on your laptop. There are two ways to get a pipeline involved.
Connect the repository. Once a repository is connected as an integration, the platform scans it and evaluates your policies and gates on the results. Nothing has to run in your pipeline for that.
Run Safeguard in the pipeline. safeguard ci scan scans each push on Safeguard as a new version of its project, named after the branch and short commit. With --wait it polls until the scan finishes. With --fail-on-gate it then waits for the gate's policies to be evaluated against that scan and fails the step when it should.
Step 1: create the key
Create a secret key with the scans:trigger and scans:read scopes (the CI docs place keys under Settings → Developer). Store it in your CI system as SAFEGUARD_API_KEY. In GitHub that is the repository's Settings, then Secrets and variables, then Actions, then New repository secret.
Step 2: add the job
This GitHub Actions workflow follows Safeguard's published template and adds the gate:
# .github/workflows/safeguard.yml
name: Safeguard
on: [push, pull_request]
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 --fail-on-gate Release
No checkout step is needed, because the scan clones the repository on Safeguard. The repository, branch and commit are detected automatically on GitHub Actions, GitLab CI/CD, Bitbucket Pipelines, Jenkins, Azure Pipelines and CircleCI; elsewhere pass --repository-url, --branch and --commit. Pin a CLI version once the job works, by setting SAFEGUARD_VERSION on the install line, so a release does not change your build under you.
On GitLab, a plain job does the same thing. Set SAFEGUARD_API_KEY as a masked CI/CD variable:
safeguard-gate:
stage: test
image: ubuntu:24.04
script:
- apt-get update && apt-get install -y curl
- curl -fsSL https://cli.safeguard.sh/install | bash
- safeguard ci scan --wait --fail-on-gate Release
Step 3: know what the exit code means
With --fail-on-gate the step:
- exits 0 when the gate is Passed or Warning, and prints the warnings and notified policies;
- exits non-zero when the gate is Failed, and prints the Block policies that failed;
- exits non-zero when the gate is Error or Not evaluated, or when its policies were not evaluated before
--timeout, with a message saying why.
--fail-on with a severity keeps working and combines with --fail-on-gate, as in the example. The default --timeout is 30 minutes and covers the scan and the gate evaluation together. You can also pass all instead of a gate name to require every gate.
Step 4: make the check required
A failing job only stops a merge if the branch requires it. In GitHub, add the job as a required status check in a branch protection rule. GitHub's docs are plain about the effect: all required status checks must pass before collaborators can merge changes into the protected branch.
Keeping source on the runner
safeguard ci scan --local scans the checkout on the CI runner over --path and uploads only the results, never the source. It needs no integration and works for private and self-hosted repositories. It covers dependencies (SBOM and vulnerabilities); SAST runs only in platform mode. Add --wait and your fail options as usual.
Exceptions, mitigations and the audit trail
A gate with no exception path gets switched off the first time it blocks a hotfix. Set the path up before you turn on Block.
Request an exception. Click Request Exception, select the policy and finding, give a business justification, set an expiration date and submit it. Approvers are notified, review it and decide, and the audit trail records the result.
Keep exceptions time boxed. The guardrails docs describe exceptions granted by an approver with the right role, for a specific asset and duration. They appear in the audit log and expire automatically. Emergency breakglass exceptions need two person approval and trigger alerts in the security channel.
Use mitigations for findings that do not apply. From the Vulnerabilities tab, Add Mitigation records a Risk Acceptance, False Positive, Workaround Applied or Not Applicable, each with a justification, an approver and a scope. The gate link is the important part: active mitigations exclude vulnerabilities from gate failures, and expired ones do not. A risk acceptance with a 90 day expiry comes back to the gate on day 91, which is exactly what you want. Safeguard notifies you 14 days and 7 days before expiry and on the day.
Use mitigation policies sparingly. Settings → Mitigation Policies can auto-apply mitigations by CVE, package, severity threshold or development only scope. Each one silently removes findings from every gate it touches, so give each a named owner.
Read the record. Every guardrail decision produces a signed audit record, is logged to the audit stream, is emitted as an OpenTelemetry span for your SIEM, and is counted on the Enforcement dashboard. Compliance reports cover status by policy, exception history and violation trends. The docs offer a useful health check: a policy that fires Block thousands of times a week probably needs auto-fix or a refinement.
Do the work properly with the Safeguard CLI
A gate tells you what is wrong. The Safeguard CLI is where you fix it. It scans code, container images and SBOMs on your own machine, sends only the results to the platform, and includes an assistant that can fix what it finds. It writes code, not only scans.
Install
curl -fsSL https://cli.safeguard.sh/install | bash
safeguard --version
On Windows: irm https://cli.safeguard.sh/install.ps1 | iex. It is a single static binary with no Node, Python or JVM needed, for macOS, Linux and Windows on x86_64 or arm64 (Windows x86_64 only). See installing the Safeguard CLI on Linux for a build server.
First session
Run safeguard, type /login and approve the session in your browser, then /scan . to scan the folder you are in. Ask in plain language, for example "how many critical vulns do we have". Type @ to attach a file such as a lockfile, and /resume to pick up the last session in that folder.
Fix what failed the gate
Take the Block policy the CI log printed and ask for the fix. The recorded session below is a real run on a sample project called payments-api with test data. It installs with the curl command (checksum verified), checks safeguard --version, opens the interactive terminal and asks "fix the lodash CVE in this repo". The assistant reads the finding, files and advisory, then asks before each command (press y or n), edits package.json to lodash 4.17.21, runs npm install and npm test with approval, and prints a summary with a suggested commit message. It pushes nothing. It finishes with /permissions, /model and /scan ..
Click through it at your own pace, or open it full screen.
For dependency findings there is also a direct command. Preview, then apply:
safeguard fix --severity critical --dry-run
safeguard fix --severity critical
safeguard review --base main
safeguard fix updates vulnerable packages to safe versions and writes a fix report; --dry-run previews without touching files. safeguard review checks the working tree changes for correctness and security against the ref you pass. Push, and the next CI run re-evaluates the gate on the new scan. Reviewing AI generated code before merge goes deeper on that last step.
Send the fix from the desktop app
You do not have to be in the terminal when the gate fails. A CLI session is live: it shows in the web app, the desktop app and the phone app, and other devices can send work to it. In this recorded desktop session, the CLI session shows in the desktop app's conversation list; "2 connected" shows the computer, the browser and Safeguard cloud. The user asks for work on their computer, the app asks which computer and folder, they pick the folder, then Approve or Deny each command and file edit. It ends with the upgrade done and tests passing.
Click through it at your own pace, or open it full screen.
The download page lists desktop app 3.2.2 for Windows, macOS and Linux, plus iOS and Android apps, so you can start in the terminal, continue on the desktop and approve from your phone. The desktop app guide covers setup.
Your plan, your model
As covered at the top, the Claude, ChatGPT or Codex plan you already pay for works inside the CLI with no extra Safeguard charge, under your plan's own limits and terms. /model shows or switches the model; the CLI docs cover connecting one.
Approvals
/permissions sets how often the assistant asks: Ask (reads run; edits and commands ask first), Accept edits (edits in this folder and read only commands run; other commands ask), Plan (read and plan only), Auto (read only commands and what the policy allows run; anything else asks; the default) and Strict (pre-approved actions only, for CI). A --yolo flag approves everything; keep it for throwaway sandboxes.
CI with safeguard exec
safeguard exec runs one non-interactive assistant turn, useful for a readable summary next to the gate result:
safeguard exec "summarise the critical findings in this repository"
It defaults to strict mode, where nothing is asked and anything not pre-approved is denied, and it refuses to run outside a git repository. Gating stays with safeguard ci scan; exec explains. The full setup is in running the Safeguard CLI in CI with safeguard exec.
Troubleshooting and common mistakes
Troubleshooting
There is no Outcome column. Policy outcomes are off for your workspace. Ask your Safeguard contact to turn them on.
The CI step fails with Not evaluated. Either the gate has no linked policies, or they were not evaluated before --timeout. Check the gate's linked policies, then raise the timeout if your scans are long.
A policy shows Error. Its starred search is missing, failed or timed out. The reason is shown next to the outcome. Re-star the search or simplify it.
The gate passed but you expected it to fail. Check the policy's Action (Warn and Notify never fail a gate), an older policy's threshold, and active mitigations that exclude the finding.
The job fails on pull requests from forks. Fork workflows get no secrets, so the key is missing. Both GitHub's docs and Safeguard's CI docs note this.
The job cannot find the project. By default the project is found from the repository URL. Pass --project or --project-id to be explicit. A repository not imported yet is imported on its first run; if several integrations fit, add --integration-id.
Common mistakes
- Starting on Block. The first week of any policy finds old debt. Warn first, then Block.
- Expecting the CLI to enforce gates.
safeguard scanreports findings and exits 0 when the scan ran. The CLI has no gate command;safeguard ci scan --fail-on-gateasks the platform for the gate's outcome. - Using the old GitLab include address. The template moved in October 2026 to
https://registry.safeguard.sh/ci/safeguard.gitlab-ci.yml; the old cli.safeguard.sh address no longer serves it. - Leaving the CLI unpinned in CI. Set
SAFEGUARD_VERSIONand update it deliberately. - Exceptions without an expiry. An exception that never expires is a policy change nobody reviewed.
- Installing an admission controller chart. Safeguard's docs say the Kubernetes admission controller is not published yet and warn against any chart claiming to be it.
Frequently asked questions
What is the difference between a policy and a gate?
A policy is a rule that finds matching issues. A gate is the checkpoint that collects policy results and decides, from each policy's Action, whether the gate is Passed, Warning, Failed, Error or Not evaluated.
Can the Safeguard CLI evaluate gates on my laptop?
No. Gates are evaluated by the platform on the scans it runs. The CLI triggers that scan with safeguard ci scan and reports the gate's outcome, and its assistant helps you fix what failed.
What happens if a policy cannot be evaluated?
Its outcome is Error, which is never counted as a pass. The gate shows Error with the reason, and a CI step using --fail-on-gate fails.
Can I fail on severity and on a gate at the same time?
Yes. --fail-on critical and --fail-on-gate Release can be combined in one safeguard ci scan --wait call.
Can I keep using Claude, ChatGPT or Codex?
Yes. Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. The Safeguard MCP server also has read-only tools for gate outcomes, policy outcomes and evaluation history when policy outcomes are on and your MCP access settings allow them, so you can ask Claude or ChatGPT why a gate failed. See connecting ChatGPT to the Safeguard MCP server.
Does my source code leave my machine or runner?
With safeguard ci scan --local, the scan runs on the CI runner and only results are uploaded. When the local runner runs platform scan jobs on your machine, only SBOMs and findings are uploaded.
How do exceptions affect a gate?
Active mitigations exclude a vulnerability from gate failures; expired ones do not. Exceptions are time boxed and recorded in the audit log.
Try it yourself
This walkthrough sets up the runner on your own machine: pick the platform, start it, approve it with its device code, then use the same CLI to scan from the terminal.
Click through it at your own pace, or open it full screen.
Ready to build your first gate? Download Safeguard, run safeguard, type /login, then /scan ., and create your first policy under Settings → Policies. The full reference is in the CLI docs.
Quick check
A Warn policy fails and every other policy passes. What is the gate's outcome?
Warning. The gate passes, the warning names the policy, and a CI step using fail on gate exits 0.
Why should a new policy start on Warn?
So you can see what it would block without stopping releases, clean up existing debt, and only then switch it to Block.
Does an expired mitigation still keep a finding out of gate failures?
No. Only active mitigations exclude vulnerabilities from gate failures. Expired ones do not.
Sources
- Safeguard docs: Policies and Gates
- Safeguard docs: Policy outcomes and actions
- Safeguard docs: Guardrails and Enforcement
- Safeguard docs: CI/CD Integration
- Safeguard docs: Mitigations
- Safeguard docs: Findings
- Safeguard docs: Search
- Safeguard docs: Vulnerability Prioritization (Reachability Analysis)
- Safeguard docs: Safeguard CLI
- Safeguard docs: CLI interactive terminal
- Safeguard downloads
- GitHub Docs: About protected branches
- GitHub Docs: Using secrets in GitHub Actions
Last reviewed: 21 October 2026.