Safeguard
Product•

Fast, Deep or Progressive Scans: Which Depth for Each Pipeline Stage

Run a quick dependency scan on every pull request, a full transitive scan before release, and scheduled rescans after it, each with its own time budget.

Safeguard Security Team
40 min read

Run security scans in CI without slowing every build by matching scan depth to the stage: a quick, cached dependency check on each pull request, a full transitive scan on the main branch and before release, and rescans on a schedule you set after the code ships. Give each stage a written time budget, make the quick scan report rather than decide anything it cannot see, and make the release gate refuse to pass unless the full scan for that exact commit has run.

The rest of this post works that plan through stage by stage, with the standards behind it, a method you can run with any tooling, and then how Safeguard's scan modes, CI step and gates map onto it.

Key takeaways

  • Scan time goes into resolution, transitive depth, advisory matching, reachability, container layers, code analysis and queueing. Each stage can afford a different share.
  • Pull requests want fast feedback on what changed. Main branch and release candidates want the full graph. Rescans on a schedule you set cover the days between code changes, when new advisories land on code you already shipped.
  • A fast scan may miss things only if a deeper scan is guaranteed to run before release. Write that guarantee into the release gate.
  • Time budgets belong in the pipeline file as job timeouts. Fail closed on a timeout at release; report and retry earlier.
  • Safeguard has three scan modes you choose when you connect a repository: Fast (quicker) and Deep (full) are GA, and Progressive (fast results now, full depth after) is generally available. Since CLI v11.2.0, safeguard ci scan --scan-mode picks the mode for one CI run.
  • Safeguard rescans on the schedule you set per integration, when you choose Rescan, and on each CI run. Scheduled rescans are GA. Azure DevOps can also rescan on push through optional service hooks. Software composition analysis (SCA) and deep dependency scanning are GA. The CLI, the local runner and the CI step are GA. Policies and gates are GA, with the documented actions Block, Warn and Notify.
  • Static analysis (SAST) is generally available, and the docs say SAST findings are not part of the CI step's --fail-on severity check yet.
  • Reachability analysis is GA. Autonomous remediation is GA and ships switched off, with every category set to Manual.

The problem: one scan profile for every stage

Most teams that complain about slow security scans run one scan profile everywhere: on a two line documentation change, on a merge to main and on the release tag. If that job is thorough, pull requests wait and developers resent it. If it is quick, the release ships on a scan that never looked past the first few layers of the dependency tree.

The second failure is the dangerous one. A team that tunes its scan down for pull requests often never adds the slower scan back anywhere. Nobody decides to stop deep scanning; it just stops, and the gate keeps going green.

The fix is not a faster scanner. It is a pipeline design where each stage runs the depth it can afford, the stages together cover the whole graph, and the final gate checks that the full scan ran on the thing being released.

What the standards say about where checks run

No standard tells you how many minutes a scan may take. Several tell you what has to be true by the time software ships, and that is what sets the floor for your deepest stage.

NIST SP 800-218, the Secure Software Development Framework

The SSDF (NIST SP 800-218, version 1.1) has three practices that bear on scan depth by stage:

  • PO.4, Define and Use Criteria for Software Security Checks. PO.4.1 asks you to define criteria for security checks and track them throughout the lifecycle; PO.4.2 asks for mechanisms that gather the information those criteria need. A per stage scan plan with written thresholds is a PO.4 artifact.
  • PW.7, Review and/or Analyze Human-Readable Code. PW.7.1 asks you to decide whether code review, code analysis or both are used, and PW.7.2 asks you to perform it and record and triage what it finds. The aim is to find vulnerabilities "before the software is released".
  • PW.8, Test Executable Code. PW.8.1 asks you to decide whether executable code testing should be performed and which types, and its second example is choosing testing methods based on the stage of the software. That is close to a direct instruction to vary depth by stage.

None of these says "scan every pull request deeply". They say: decide, write down the decision, do it before release, and keep the record.

NIST SP 800-204D, supply chain security in CI/CD pipelines

NIST SP 800-204D (February 2024) is the most specific federal text on scanning inside pipelines. Three passages matter here.

Section 5.1.4 says dependencies must be enumerated and evaluated for policy, possibly with SCA tools, and that dependency detectors should find all dependencies, including transitive ones, "with preferably no limit to the depth" of nesting. That sets the bar for at least one stage in your pipeline: somewhere, the full depth has to be walked.

Section 5.2 includes DEPLOY_REQ-4: there should be evidence that a container image was scanned for vulnerabilities, and the time a scan ran matters because scanners keep learning about new vulnerabilities, so recent results give better assurance than old ones. That is the argument for rescanning on a schedule and at release, not only at build.

The same section, under DEPLOY_REQ-5, says a container image should be scanned as soon as it is built, even before it is pushed to a registry. That supports an early, fast scan as well as a late, deep one.

Section 3 also lists, among attack vectors, that checks such as scanning "may have been bypassed in the CI/CD process". A fast scan that quietly replaced the deep one is a self inflicted version of that bypass.

SLSA v1.0

SLSA's build track is about provenance, not vulnerability scanning: Build L1 means provenance exists, L2 means signed provenance from a hosted build platform, and L3 means a hardened build platform. It matters here because provenance is how you tie a scan result to the exact artifact you release. If your deep scan ran on commit A and you shipped an artifact built from commit B, provenance is what lets an auditor, or your own gate, notice.

OWASP Software Component Verification Standard

The OWASP SCVS organises component verification requirements into three levels so organisations can adopt them gradually. It is reasonable to meet a higher level at release than on every pull request, as long as the release level is met every time.

What the exploited vulnerability sources add

CISA calls its Known Exploited Vulnerabilities (KEV) catalog the authoritative source of vulnerabilities exploited in the wild. FIRST describes EPSS as estimating the probability that a published CVE is exploited in the next 30 days. Both change independently of your commits, so a pipeline that only scans when code changes will always be behind.

What a security scan actually spends its time on

Before you can budget a scan, you need to know where its minutes go. Every dependency and container scan does some version of the steps below, and each has a different cost profile.

1. Getting the code or artifact

The scanner needs the source tree, the lockfiles or the image. A fresh clone of a large repository, or a pull of a large image, can cost more than the analysis itself. Scanning in the job that already checked the code out avoids paying this twice.

2. Resolving the dependency graph

With a complete lockfile, working out exact versions is mostly parsing. Without one, or when the scanner asks a registry about transitive versions, it becomes network bound and can dominate the run, and results drift if versions float.

3. Walking transitive depth

Direct dependencies are a small fraction of what ships. A shallow walk is fast because it stops early; a full walk reaches the long tail of utility packages and shims nobody chose directly. Deduplicating shared subgraphs helps, but the graph still has to be traversed.

4. Matching against advisories

Every resolved package and version is compared with advisories. A scanner that downloads a fresh database every run pays a fixed cost each time; one that keeps a warm database or matches server side pays far less.

5. Reachability and prioritisation

Working out whether your code calls the vulnerable function means building a call graph, far more expensive than matching versions, and it is what turns a long list into a short one. Many teams run it on main or before release only.

6. Container layers

An image scan unpacks layers and inventories operating system packages and language packages inside them. Base image layers rarely change between builds, so caching by layer digest saves most of the work, but the first scan of a new base is slow.

7. Code analysis

Static analysis of your own source works on a different axis from dependency scanning: its cost grows with lines of code and the depth of its data flow analysis, not with the dependency graph. It usually has its own time profile and deserves its own budget.

8. Queueing, uploading and policy evaluation

On a shared service the job may wait for a worker, upload results, then wait for policies to be evaluated. None of that is analysis, but all of it is pipeline time.

When a developer says the scan is slow, ask which of these eight phases it is. The answer decides whether you need caching, a different stage, or a different scan.

Which stage needs which depth

The table below is a starting design. Its time budgets are illustrative placeholders for your own measurements, not benchmarks of any tool.

StageTriggerWhat the scan must answerDepthIllustrative budgetGate behaviour
Developer machineBefore push, on demandDid I just add something obviously bad?Fast, changed manifests onlyA minute or soAdvisory only
Pull requestEvery PR into main or release branchesDoes this change add a new vulnerable or disallowed dependency?Fast: lockfile resolution, direct plus cached transitive results, no reachabilityUnder five minutes, illustrativeBlock only on clear, high confidence rules; warn on the rest
Main branchEvery mergeWhat is the full state of main now?Deep: full transitive graph, container layers, reachability where availableTens of minutes is acceptable off the critical path, illustrativeRecord and notify; open fixes
Release candidateRelease branch or tagIs this exact commit fit to ship?Deep, on the release commit, plus provenanceWhatever it takes, within a hard ceilingBlock; exceptions need a named approver
ScheduledNightly or weekly, on a schedule you setHas anything new been disclosed against what is on main or deployed?Deep, or a rescore of the stored inventoryOff the critical pathNotify owners; open fixes
On demandA high profile advisory or KEV addition you decide to act onWhich projects contain the newly affected component?Lookup in stored SBOMs, then a rescan of affected projects onlyMinutes, off the critical pathNotify; escalate by exploitation status

Pull request: fast feedback on the change

A pull request scan has one job: tell the author, while they remember the change, whether it made things worse. The question is not "is this repository clean" but "did this change add a vulnerable or disallowed dependency".

That framing cuts most of the work. Resolve from the lockfile, compare against cached base branch results, and report the difference. Skip reachability unless it runs incrementally, and skip the image scan unless the change touches the Dockerfile or base image.

Block only on rules that are both important and unambiguous: a new dependency with a critical vulnerability that has a fixed version available, or a license your policy forbids. Everything else should warn. Developers accept a fast scan that blocks rarely and correctly. They route around one that blocks often on findings they did not introduce.

Main branch: the full picture, off the critical path

Once code is merged, nobody is waiting on the scan. Run the deep scan here: the full transitive graph, the container image, reachability where you have it, and code analysis if you run it. A long run is acceptable, because the result feeds triage and fix pull requests, not a developer's next five minutes.

The main branch scan is also where you build the baseline that makes pull request scans cheap. If you store the full result per commit on main, a pull request scan can compare against it instead of recomputing it.

Release candidate: depth on the exact commit you ship

The release gate is where "fast" stops being acceptable. The scan that decides release must run on the release commit or the built release artifact, at full depth, and its result must be recorded against that commit.

Two details catch teams out. First, a deep scan of main from yesterday is not a scan of today's release tag if anything merged in between. Second, a scan of the source tree is not a scan of the image if the image adds a base layer with its own packages. Scan what you ship.

Give this stage a hard ceiling rather than a target. If the deep scan cannot finish inside the ceiling, the release waits and someone looks at why. Shipping anyway because the scan was slow is the exact bypass that NIST SP 800-204D warns about.

Scheduled scans: catching what changed in the world

A repository nobody touched for three weeks still accumulates risk as new advisories land against packages it contains. A nightly or weekly job rescans main and deployed release tags, or rescores their stored inventories.

Scheduled runs have quirks. One widely used CI platform's docs say scheduled workflows run only on the default branch and can be delayed or dropped under high load, with the start of every hour named as busy. Schedule off the hour, and cover release branches explicitly.

On demand: when a major advisory lands

Some advisories cannot wait for next week's run. The cheapest response rebuilds nothing. If you keep an SBOM per project and version, search those stored inventories for the affected component first, then rescan only the projects that contain it. That answers "are we affected?" the same day without kicking off a hundred pipelines. Name who watches the KEV catalog and makes that call.

What you lose with a fast pre-merge scan, and where you get it back

Every shortcut in a fast scan has a cost. The design question is where each cost is recovered.

What the fast scan skipsWhat can slip throughWhere you recover it
Deep transitive walkA vulnerable package several levels down, reached through a dependency you did not changeMain branch deep scan, release gate
Fresh resolution of floating versionsA transitive version that changed upstream since the lockfile was writtenLockfile discipline; deep scan with fresh resolution on main
ReachabilityNothing is missed, but noise goes up and real issues get buriedMain branch and release scans with reachability
Container image scanOperating system packages in the base imageImage scan when the Dockerfile or base changes; release image scan
Code analysisFlaws in your own codeA separate code analysis job with its own budget
Advisory updates after mergeA new CVE against a package already on mainScheduled rescans, plus an on demand rescan for major advisories
Policy evaluation on full dataRules that depend on the whole graph, like depth or license combinationsRelease gate

The pattern is simple: every row in the left column must have a named recovery point in the right column that runs automatically. If one does not, the fast scan is quietly your only scan for that class of issue.

Setting and enforcing a time budget per stage

A time budget is a number in the pipeline file, not a feeling in a retrospective. Here is a method that works with any CI system.

Step 1: measure before you cut

Record scan duration per stage for two weeks, split at least into clone, resolve, scan and wait. Slow scan complaints can turn out to be clone time, cold databases or queue waits, fixed by caching and placement rather than by scanning less.

Step 2: write the budget as a job timeout

Most CI platforms let you set a timeout per job. One platform's workflow syntax documents a job level timeout-minutes with a default of 360 minutes. Another documents a job level timeout that overrides the project setting but cannot exceed the runner's own timeout. Set these explicitly per stage. Six hours is not a budget.

Step 3: decide what a timeout means at each stage

A timeout is a result, and it needs a rule:

  • Pull request: a timed out scan should report "not scanned" visibly and not block, then retry on merge. Blocking on a timeout teaches people to rerun until it passes.
  • Main branch: a timeout should alert the owning team and rerun. It should not be silent.
  • Release: a timeout must fail closed. No result is not a pass.

Step 4: only scan when it can matter

Path filters keep a dependency scan from running on changes that cannot affect dependencies. One CI platform's rules:changes keyword adds a job only when listed files change, and its reference notes that it compares against the target branch in merge request pipelines and against the previous commit in branch pipelines, and that it always evaluates to true for new branches or when there is no push event. Use filters on pull request scans; never on the release scan.

Step 5: cache what does not change

Cache the vulnerability database, the resolved dependency graph keyed by lockfile hash, and image layer results keyed by digest. Pin your scanner version so a new release does not change timing or results under you.

Step 6: run in parallel, not in series

Dependency scanning, image scanning and code analysis are independent. Run them as parallel jobs with separate budgets so the slowest one does not hold the others, and so you can see which one is slow.

How gates should behave when only a fast scan has run

This is the question that decides whether your design is honest.

A gate that only has fast scan data should say so. There are three workable patterns:

  1. Scope the rule to what was scanned. A pull request gate blocks on "new critical vulnerability with a fix available in a dependency this change added". It does not claim the branch is clean.
  2. Warn on everything else. Findings that need full depth or reachability to judge fairly become warnings, so the author sees them without being blocked on someone else's debt.
  3. Make the release gate demand the deep result. The release gate checks that a full scan exists for the release commit and that its policies passed. If the deep scan did not run, or did not finish, the gate does not pass. "Not evaluated" must never read as "passed".

The third pattern is the one teams forget. If the only automated check before release is the pull request gate, then the fast scan is your release gate, whatever your design document says.

Keeping the SBOM current between code changes

An SBOM generated at build time describes the artifact at that moment. Its component list stays true as long as the artifact does not change. Its vulnerability picture does not, because advisories keep arriving.

Three mechanisms keep it current:

  • At release. Generate the SBOM from the release build and store it with the release. This is the inventory you will be asked about later.
  • On a schedule. Rescan or rescore main and every supported release on a fixed cadence, so a quiet repository is not a stale one. NIST SP 800-204D's point about scan time applies directly: a recent result is worth more than an old one.
  • When a major advisory lands. Search the stored SBOMs for the affected component, rescan the projects that contain it, and notify their owners. The lookup needs no rebuild and no pipeline run.

Together these mean the answer to "what is in production and what is it exposed to today" does not depend on whether someone happened to push code this week.

Stopping the fast scan from becoming the only scan

The drift from "fast on pull requests" to "fast everywhere" happens through small, reasonable changes: a shortened main branch job, a pull request job copied into a new template, a nightly run disabled because it was noisy. Guard against it with checks that do not depend on memory:

  • Name the jobs by depth. A job called deps-fast-pr and one called deps-deep-release are hard to confuse.
  • Make the release gate check for the deep result, by commit, not just for "a scan passed".
  • Alert on missing scans. If a project has had no deep scan in a set number of days, that is a finding in its own right.
  • Review the pipeline template, not each pipeline. Put the stage plan in a shared template so one review covers every repository.
  • Track coverage, not just findings. Report the share of projects with a recent deep scan alongside vulnerability counts.

How Safeguard does this job, step by step

Everything above works with any tooling. This section shows how Safeguard's documented features map to the plan, with commands exactly as Safeguard's docs show them, and with each capability's maturity label.

What the scan modes are, and where the docs describe them

Safeguard has three scan modes, and the source control integration guides describe the choice at the moment you connect repositories. The GitHub integration guide says the Connect button's menu offers "Fast scan (quicker)", "Deep scan (full)" and "Progressive scan (fast now, full depth after)". The other integration guides list the same three choices.

  • Fast (GA): the quicker option.
  • Deep (GA): the full scan.
  • Progressive (GA): fast results now, full depth after. It is this post's quick answer first, full answer later pattern in one option, and it is generally available.

The docs do not spell out what Fast leaves out compared with Deep, so do not assume it matches the generic fast scan described earlier in this post. Before you settle on a mode per stage, compare a Fast and a Deep result on one of your own repositories. Whatever you run on pull requests, keep a Deep scan on the release commit.

In CI, the mode is a flag. Since CLI v11.2.0, safeguard ci scan takes --scan-mode fast, --scan-mode deep or --scan-mode progressive. Without the flag, the CI scan uses the mode set on the integration. That lets one connected repository run Fast on pull requests and Deep on the release branch. The CI/CD docs page does not list the flag yet, so confirm it with safeguard ci scan --help on the version you install.

Step 1: connect the repository and pick the initial depth

In the app, connect your source control under Integrations, select the repositories and the branch or tag of each, and choose a Connect option: Fast, Deep or Progressive (GA). For a large repository you want results from quickly, Progressive matches the plan in this post: a fast first answer, then the full depth result behind it. CI scans use the mode set on the integration unless you pass --scan-mode.

Step 2: add the CI step for pull requests and main

Safeguard's CI integration (GA) runs the CLI (GA) in a plain shell step; the docs say there is no plugin, Action or Pipe to install. Create a secret key under Settings › Developer with the scopes scans:trigger and scans:read, store it as SAFEGUARD_API_KEY in your CI, and add the step. The GitHub workflow the docs show scans pushes to main and pull requests into main:

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

Each push becomes a new version of its Safeguard project, named after the branch and commit (the docs' example is main-a1b2c3d). On a pull request the head branch is scanned and the version is named after its head commit. Branch lists take wildcards, so a release pattern such as [main, 'release/**'] is a documented option. Add a job level timeout from your own budget; that setting belongs to your CI, not to Safeguard.

Step 3: choose where the scan runs for each stage

The docs describe two places a CI scan can run:

WhereCommandWhat it scans, per the docsFits which stage
On Safeguard (default)safeguard ci scanSAST, SBOM, dependency vulnerabilitiesMain branch and release, when the repository is connected as an integration
On the runnersafeguard ci scan --localSBOM, dependency vulnerabilitiesPull requests, self hosted or firewalled Git servers

With --local, only the results leave the runner, and the CLI reads dependencies from lockfiles and manifests. No clone happens on Safeguard's side, which removes one of the cost phases described earlier. SAST is generally available, so the code analysis part of an on Safeguard scan is too.

Step 4: set the failure rules per stage

The CI step's documented flags:

FlagFails the step when
--waitThe scan fails or takes longer than --timeout (default 30 minutes)
--fail-on <severity>With --wait: a dependency vulnerability at or above critical, high, medium or low
--fail-on-gate <gate>With --wait: a policy gate (its name, id, or all) is not Passed or Warning

Two limits from the docs shape how you use these. SAST findings are scanned and shown in Safeguard but are not part of --fail-on yet. And dropping --fail-on gives you report only behaviour, which is the right default for a fast pull request scan you are still tuning.

A reasonable mapping: pull requests run --local --wait --fail-on critical (or report only at first), or --scan-mode fast --wait --fail-on critical on Safeguard if the repository is connected. Main runs on Safeguard with --scan-mode deep --wait and no failure flag, so results flow to triage. Release branches run on Safeguard with --scan-mode deep --wait --fail-on-gate on your release gate. Set --timeout on the release job to your hard ceiling, because the docs say --wait fails the step when the scan exceeds it, which is the fail closed behaviour you want at release.

Step 5: build the release gate from policies

Policies, gates and guardrails are GA. In Safeguard, policies evaluate scan data and produce findings; gates are checkpoints that enforce them. Create policies under Settings › Policies with the visual builder or YAML. The documented conditions include vulnerability severity, CVSS score, EPSS score, license, component age, maintenance and SLSA level, and there are templates such as "No Critical Vulnerabilities".

Each policy has one Action, and the docs list three: Block (the gate fails), Warn (the gate passes with a warning naming the policy) and Notify (the gate passes and the workspace's configured channels are notified). Keep Block for the release gate; use Warn and Notify for rules you want visible on pull requests.

Three documented details matter for the "only a fast scan has run" problem:

  • A gate with no evaluated policies shows Not evaluated, never Passed, and an Error is never counted as a pass.
  • safeguard ci scan --wait --fail-on-gate exits non zero when the gate is Failed, Error or Not evaluated, or when its policies were not evaluated before --timeout. A missing deep result therefore fails the release step instead of slipping through.
  • Policy outcomes are turned on per workspace by Safeguard. While they are off, the Outcome column does not appear and gate failure in CI through --fail-on-gate depends on them, so ask your Safeguard contact to turn them on before you rely on this step.

The docs say gates are evaluated by the platform on the scans it runs for your connected repositories, which is another reason to run the release scan on Safeguard rather than with --local.

KEV status and reachability are not documented gate conditions. Use them as triage filters in the Vulnerabilities view, not in the release gate.

Step 6: walk the full depth when you scan locally

For a deep scan on the build machine, the deep dependency scanning docs show:

safeguard scan --dir . --resolve-transitive --transitive-depth 100

Deep dependency scanning is GA. Safeguard markets it as reaching up to 100 levels of transitive dependencies, and its docs describe deduplicating shared subgraphs and caching paths with no known vulnerabilities. The CLI usage page also lists --scanners <list> (run only these scanners) and says safeguard --help wins if the two disagree.

Note that safeguard scan does not gate: per the docs it exits 0 whenever the scan ran, whatever it found, and it has no severity threshold flag. Gate on the JSON it writes under ./output, or use safeguard ci scan. On a fresh CI container, the docs say to put the scanner tools on the PATH or point TOOLS_DIR at them, since the interactive terminal normally downloads them on first run. Cache that download.

Step 7: run scans on your own machine with the local runner

The local runner (GA) is part of the CLI. Open safeguard, type /login, approve the session in the browser, and the machine starts polling the platform for scan jobs assigned to it. It runs each job locally and uploads only the SBOM and findings, never source code. From the interactive terminal, /scan . scans the current folder and /jobs shows recent scan history. This suits the developer machine stage in the table, and private repositories you cannot connect to a cloud service.

Step 8: schedule rescans for the days between commits

Safeguard rescans a project in three ways: on the schedule you set, when you choose Rescan, and on each CI run of safeguard ci scan. Set the schedule per integration under Integrations › Scheduled: once, every 1 to 24 hours, daily, weekly, or with a cron expression. The GitHub integration guide says that integration has no webhooks to set up, and that to rescan new commits you set a schedule or choose Rescan. Azure DevOps is the exception: its optional service hooks rescan on push and start a pull request review when a pull request is opened or updated.

For the plan in this post, that means a scheduled rescan of main and of each supported release, weekly for instance, on top of the CI step on every push. Connect those branches with Deep, or, to set the depth on each run, schedule a CI job that runs safeguard ci scan --scan-mode deep --wait.

This post does not assume that a new advisory starts a rescan on its own. When a high profile advisory lands, choose Rescan on the affected projects instead of waiting for the next scheduled run.

Step 9: route the results and fix them

To get findings in front of their owners, give a policy the Notify action, which sends its matches to the workspace's configured channels. For fixes, autonomous remediation (GA) opens tested, policy gated pull requests; it ships switched off with every category set to Manual, so you decide what runs on its own. Reachability analysis (GA) ranks which findings to fix first.

How do you prove to auditors that the deep scan ran before release?

An auditor will sample releases and ask, for each one: which check ran, on what, when, what it decided, and who approved any exception. Design so each answer comes from a record, not a recollection.

The evidence for each sampled release

QuestionEvidenceWhere it comes from in a Safeguard setup
Which commit was released?Release tag and commit hashYour source control and CI
Did a full scan run on that commit?Project version named after branch and commit, with its scan resultThe CI step creates a version per push, named <branch>-<commit>
What did the gate decide?Gate outcome and each policy's outcome, Action and matchesThe gate's expanded view and its evaluation history, with policy outcomes on
Did the pipeline obey the gate?CI job log showing the step's exit statusYour CI, from safeguard ci scan --wait --fail-on-gate
Was there an exception?Request, justification, expiry, approverThe policy exception workflow, which records an audit trail
Is the artifact the one scanned?Build provenanceYour build system; Safeguard's attestation and signing features (GA) where you use them

Three habits that make the evidence hold up

  1. Bind the scan to the commit, not the branch. "Main was scanned on Tuesday" does not prove the Thursday release was.
  2. Keep the CI log and the gate history together. One shows what Safeguard decided, the other that the pipeline acted on it.
  3. Make exceptions expire. The documented exception flow asks for a justification and an expiry date.

What Safeguard does not do

  • It does not make a slow build fast. Scan time depends on repository size, ecosystems, network and where the scan runs, and a pipeline that is slow for other reasons stays slow.
  • It does not publish scan time guarantees, and the docs do not define what Fast leaves out compared with Deep. This post's budgets are illustrative; measure your own.
  • Progressive is generally available. Keep a Deep scan on the release commit for the gate.
  • Most connections have no push webhook. Outside Azure DevOps service hooks, new commits are rescanned by your schedule, a manual Rescan or your CI step, so set at least one of them for every repository.
  • SAST does not gate builds through --fail-on yet. SAST is generally available, and the docs say its findings are shown but not part of the severity check.
  • KEV and reachability are not documented gate conditions. They help you triage in the Vulnerabilities view; your release gate should use the documented conditions.
  • Policy outcomes are off until Safeguard turns them on for your workspace, and --fail-on-gate depends on them.
  • The scanning REST API is not available yet. The Continuous Scanning docs page's /v1 endpoints return not found today, per its own banner, so use the app, the CLI and CI.
  • It does not run your pipeline. Job timeouts, path filters, caching and release branch rules are your CI system's settings. The only schedule Safeguard runs is the one you set under Integrations › Scheduled.
  • It does not decide your risk appetite. Which severities block, which warn, and who approves an exception are your policy choices.

Common mistakes

  • One scan profile for every stage. Either pull requests crawl or releases ship on a shallow scan.
  • No recovery point for what the fast scan skips. Every shortcut needs a named, automatic later scan.
  • Blocking pull requests on findings the author did not introduce. People learn to rerun or bypass.
  • Treating a timed out release scan as a pass. No result is not a pass. Fail closed.
  • Path filters on the release scan. Filters belong on pull requests; the release scan always runs.
  • Scanning main and shipping a tag. Scan the release commit or the built artifact you actually ship.
  • Leaving CI job timeouts at the platform default. A six hour default is not a budget.
  • Assuming gate failure works before policy outcomes are on. Check the Outcome column exists before you rely on --fail-on-gate.

FAQ

How do I run security scans in CI without slowing every build?

Split scans by stage. Run a fast, cached, report first dependency scan on pull requests, a full depth scan on main and on release candidates, and rescans on a schedule you set afterwards. Put each stage's budget in the pipeline as a job timeout and decide in advance what a timeout means at each stage.

What is the difference between a fast scan and a deep scan?

A fast scan trades completeness for speed: it resolves from lockfiles, relies on cached results and usually skips reachability and full transitive depth. A deep scan walks the whole transitive graph and does the slower analysis. In Safeguard's docs the connect options are described as "Fast scan (quicker)" and "Deep scan (full)", plus Progressive, which gives fast results now and full depth after and is generally available. In CI, safeguard ci scan --scan-mode picks one per run.

How long should a security scan take in a pull request?

There is no standard number. Measure what your scans take today, then set a budget your developers accept, often a few minutes. Any figure in this post is illustrative. What matters more is that the deeper scan runs later, automatically.

Should a pull request scan block the merge?

Block only on rules that are important, unambiguous and caused by the change, such as a newly added dependency with a critical vulnerability and a fix available. Warn on the rest. Keep blocking strictness for the release gate.

Do I still need a deep scan if I scan every pull request?

Yes. Pull request scans are usually incremental and shallow, and new advisories arrive after merge. NIST SP 800-204D says dependency detectors should find transitive dependencies with preferably no depth limit, and that more recent scans give better assurance. Somewhere before and after release, the full graph has to be checked.

How do I make sure the deep scan ran before a release?

Run the release scan on the release commit, make the release job wait for it, and fail the job if the gate is Failed, Error or Not evaluated. With Safeguard, safeguard ci scan --wait --fail-on-gate <gate> does that once policy outcomes are on for your workspace.

Can Safeguard fail a build on SAST findings?

Not through --fail-on today. The docs say SAST findings are scanned and shown in Safeguard but are not part of --fail-on yet, and SAST is generally available.

Can I gate on KEV status or reachability?

Safeguard's docs do not list them as gate conditions. Use them as filters when triaging in the Vulnerabilities view, and gate on documented conditions such as severity, CVSS, EPSS, license, component age, maintenance and SLSA level.

Related reading

A well staged pipeline tells you sooner and more reliably that a vulnerable dependency is in your build, but finding it is only half the job. The faster your scans surface a problem, the more the time to an actual fix matters, and that is what the section below covers.

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. 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 with your approval, prints a summary and pushes nothing. It ends with /permissions, /model and /scan ., the same scan you would run as the developer machine stage in this post.

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

The second demo sets up the local runner on your own machine, approves it with its device code, and then scans from the terminal. It is how you would run the developer machine stage, or scan private code without a cloud clone.

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

To try the stage plan on your own repositories, get the CLI from safeguard.sh/download and read the CI and policy guides in the Safeguard docs.

Quick check

A release scan hits its timeout before finishing. Should the release gate pass?

No. At release, no result must be treated as a failure. Fail closed, find out why the scan was slow, and rerun. With Safeguard, safeguard ci scan --wait --fail-on-gate exits non zero when the gate's policies were not evaluated before --timeout.

Your pull request scan skips transitive depth and reachability. What must be true for that to be safe?

A deeper scan must run automatically later, on main and on the release commit, and the release gate must require its result. Rescans on a schedule you set then catch new advisories against code you already shipped.

Sources

Last reviewed: 24 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.