Security scanning that runs on someone's laptop, sometimes, catches nothing reliably. The only scan that changes outcomes is the one that runs automatically on every change and blocks the merge when the risk is too high. The challenge is doing that without turning CI into a red wall that developers learn to ignore or bypass. This guide wires dependency and container scanning into your pipeline as a required status check, with thresholds you can actually live with and caching so it stays fast.
Prerequisites
- A repository on GitHub or GitLab with an existing CI pipeline (even an empty one).
- A Safeguard project token with
ci:scanandsbom:uploadscopes, created under Project → Settings → API Tokens. - Admin access to configure branch protection or merge-request rules.
Step 1: Store the token as a secret
On GitHub, go to Settings → Secrets and variables → Actions → New repository secret, name it SAFEGUARD_TOKEN, and paste the value. On GitLab, add it under Settings → CI/CD → Variables as a masked, protected variable with the same name. Never commit a token to the repo.
Step 2: Add the workflow (GitHub Actions)
Create .github/workflows/safeguard.yml:
name: safeguard
on:
pull_request:
branches: [main]
permissions:
contents: read
pull-requests: write
id-token: write
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: safeguard-sh/gate@v2
with:
token: ${{ secrets.SAFEGUARD_TOKEN }}
fail-on: high
sbom-upload: true
comment-mode: summary-and-top-findings
cache: true
fetch-depth: 2 lets the action diff the PR head against its base so its comment explains what the change introduced. cache: true reuses advisory data keyed by lockfile hash so repeat runs finish in seconds.
Step 3: Or add it to GitLab CI
If you are on GitLab, add this to .gitlab-ci.yml instead:
safeguard-scan:
image: alpine:3.19
script:
- curl -sSfL https://get.safeguard.sh/install.sh | sh
- export PATH="$HOME/.safeguard/bin:$PATH"
- sg scan --fail-on high --sbom-upload
variables:
SAFEGUARD_TOKEN: $SAFEGUARD_TOKEN
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
The exit code drives the pipeline: 0 is clean, 2 is policy violations, 3 is a scan error.
Step 4: Open a test pull request
Add a known-vulnerable dependency on a branch — for example lodash@4.17.20 — and open a PR. Within a minute the scan runs and posts a comment naming each finding, its severity, and the fixed version, and the check shows as failing. Bump the dependency, push again, and the same check goes green. That round trip is the whole point: fast, specific feedback tied to the change.
Step 5: Make the check required
A check that can be skipped is advisory, not enforcement. On GitHub, go to Settings → Branches → Branch protection rules → main and add safeguard / scan under Require status checks to pass before merging. On GitLab, set the scan job as a required approval in Settings → Merge requests. Now no one can merge to main without a passing scan.
Step 6: Tune the threshold for your team
Start permissive and tighten as the backlog clears, so CI is credible from day one:
# Strict on main, lenient on everything else
fail-on: ${{ github.base_ref == 'main' && 'high' || 'critical' }}
Begin at critical during onboarding, move to high once the existing findings are triaged, and reserve medium for mature, low-noise repos.
Verify the result
Confirm enforcement actually works:
# Reproduce the CI result locally before pushing
sg scan --fail-on high
# Confirm the check is registered as required (GitHub CLI)
gh api repos/:owner/:repo/branches/main/protection/required_status_checks
If a PR with a critical finding can still be merged, the status check name in branch protection does not match the job name — align them exactly.
How Safeguard streamlines this
The reason pipeline scanning usually fails is inconsistency: the local tool, the CI tool, and the policy all drift apart, so developers get blocked by rules they could not have predicted. Safeguard runs one engine everywhere. The CLI a developer runs locally, the CI gate, and the policy file are the same, so a passing local scan means a passing CI check — no surprise red. When the gate does block a PR, Auto-Fix can open a remediation pull request automatically, turning a failed check into a one-click merge instead of a research task. Caching keyed to your lockfile keeps repeat runs to seconds, and every run publishes to a dashboard so you can see posture trends across repos. If you are weighing options, the comparison hub shows how this single-engine approach differs from bolting several scanners together. See plans on the pricing page.
Ready to gate your pipeline? Connect a repository at app.safeguard.sh/register.
Frequently Asked Questions
Where in the pipeline should the security scan run?
On pull or merge requests, before the merge to your protected branch, configured as a required status check. That placement blocks risky code from entering main while giving developers feedback on the change they are actively working on, which is when a fix is cheapest.
Will scanning slow down my builds? Not meaningfully once caching is enabled. Advisory data and resolution results are cached by lockfile hash, so an unchanged dependency set returns in seconds. Only a genuine change to your dependencies triggers a full re-resolution.
How do I stop CI from becoming a wall of red?
Start with a critical-only threshold so the pipeline is green except for the issues nobody disputes, clear the backlog, then tighten to high. Pairing the gate with automated fix pull requests also means a block comes with a ready-made path to green rather than just a rejection.
Do scans work on pull requests from forks? They can, but forks do not receive your secrets by default, which is a good security property. The standard pattern is a two-stage flow: a read-only scan on the fork's push against public advisory data, then a maintainer-approved run that re-scans with the authenticated token.
Can I scan a monorepo with multiple services? Yes. Use a CI matrix with one leg per service directory, each pointing at its own path and project. Every leg reports independently, so you can track and gate each service separately without one failure canceling the others.
Explore the Safeguard CLI and Auto-Fix, compare approaches on the comparison hub, or review plans on the pricing page. Full pipeline recipes live in the Safeguard docs.