Safeguard
Product•

SCA for Rust in Safeguard: Cargo.lock, Reachability and Fix PRs

Safeguard SCA reads Cargo.toml and Cargo.lock, resolves every transitive crate, ranks findings by reachability, EPSS and KEV, and opens Cargo fix PRs.

Safeguard Security Team
39 min read

Safeguard's software composition analysis (SCA, GA) handles Rust by reading your Cargo.toml manifest and Cargo.lock lockfile, resolving every direct and transitive crate, and matching each exact version against vulnerability advisories enriched with EPSS and CISA KEV. Safeguard's docs list static and dynamic reachability for Rust as GA, and AI Remediate support for Cargo as full, so a reachable finding can become a reviewed fix pull request. Autonomous remediation ships switched off, with every category set to Manual.

Key takeaways

  • SCA for Cargo projects is GA. Safeguard's CLI support matrix lists Cargo with Cargo.toml as the manifest and Cargo.lock as the lockfile, marked Full Support. The SCA docs say manifests and lockfiles are both honoured, so resolution matches what actually builds.
  • Deep dependency scanning is GA. Safeguard walks the resolved graph and keeps the full path from your crate to the vulnerable one. Safeguard markets this as resolution up to 100 levels deep.
  • Vulnerability management is GA. Findings come from NVD, GitHub Security Advisories, OSV and CISA KEV, among other sources Safeguard's docs list, with EPSS probabilities attached. The RustSec advisory database says it exports to OSV and is imported by the GitHub advisory database.
  • Reachability analysis and prioritisation is GA, and Rust is GA for both static and dynamic reachability in Safeguard's language table, described as "Cargo-aware call-path extraction". Native code behind -sys crates and FFI is where the analyser is most likely to return Unknown.
  • Autonomous remediation and Griffin fix pull requests are GA but ship off. Every category defaults to Manual. Safeguard's AI Remediate docs list Rust with Cargo as Full support.
  • License compliance is GA. Safeguard reads the license field in Cargo.toml and understands full SPDX expressions such as MIT OR Apache-2.0.
  • Policies and gates are GA. Gates are evaluated in the platform, not by the CLI. safeguard scan exits 0 whatever it finds.
  • The CLI, the local runner and the MCP server are GA. SAST is generally available and is not covered here.

Why do Rust dependencies still need SCA?

Rust removes whole classes of memory bugs from the code you write, but it does nothing to the code you pull in. A typical Rust service declares a few dozen crates in Cargo.toml and ends up compiling hundreds once Cargo resolves the graph. Those crates have their own advisories, their own maintainers, and in some cases their own C code compiled through a build script. "We write Rust" is a statement about your first party code. It is not a statement about the dependency graph that ships in your binary.

Five Cargo behaviours make the problem concrete.

Cargo.lock is the truth, Cargo.toml is the intent

The Cargo book is direct about the split. Cargo.toml describes your dependencies in broad terms, written by you. Cargo.lock holds exact information about your dependencies and is maintained by Cargo. A requirement such as serde = "1.0" in the manifest is a caret requirement that admits any semver compatible 1.x version. The lockfile records the exact version that was selected, along with its checksum or, for git dependencies, the exact commit.

That distinction decides whether a scan is right. A tool that reads only Cargo.toml has to guess which version of each crate is in your build. A tool that reads Cargo.lock knows. The Cargo book's advice is "When in doubt, check Cargo.lock into the version control system", and the related post on whether a Rust service should commit Cargo.lock walks through why that matters for services and binaries.

Version unification hides where a crate comes from

Cargo's resolver documentation explains that Cargo assumes packages follow SemVer and will unify dependency versions when they are SemVer compatible. Two crates that both depend on bitflags with compatible requirements share one resolved version. Two crates that need incompatible major versions of the same dependency get two copies in the graph. The resolver also generally prefers the highest version currently available.

For security work this means a single vulnerable crate can sit under many parents, and fixing it may mean moving one shared version or untangling two. You need the full path from your root crate to the vulnerable node, not just its name. The Cargo command line answer is cargo tree --invert, covered in find what pulls in a vulnerable crate.

Features change what compiles

Cargo's features reference describes feature unification: when a dependency is used by multiple packages, Cargo uses the union of all features enabled on it. A vulnerable code path that sits behind an optional feature may be compiled in because a different crate in the graph switched that feature on. The version 2 resolver avoids unifying features in a few situations, which the Cargo book lists, but the general rule holds. Whether a vulnerable function exists in your build depends on the whole graph, not on your own manifest alone.

Build scripts and -sys crates bring native code

The Cargo book's build scripts chapter explains that placing a file named build.rs in a package root causes Cargo to compile and execute it just before building the package, often to compile or link C libraries. Crates with a -sys suffix commonly wrap a native library this way. A vulnerability can therefore live in C code that a Rust crate builds and links. The RustSec site's own example output shows exactly this pattern: a memory corruption advisory against lz4-sys 1.9.3, which wraps the C liblz4 library, with the solution "Upgrade to >=1.9.4". The related post on whether Cargo runs code from dependencies covers the build time side.

What the standards ask for

None of the standards below mention Cargo by name. They describe obligations that a Rust estate meets through dependency inventory and vulnerability matching.

  • NIST SP 800-218, the Secure Software Development Framework (SSDF). Practice PW.4.1 asks organisations to "acquire and maintain well-secured software components" from third parties. PW.4.4 asks them to verify that acquired commercial, open source and other third party components comply with the organisation's requirements throughout their life cycles, and its first example is to regularly check for publicly known vulnerabilities. RV.1.1 asks them to gather information from acquirers, users and public sources on potential vulnerabilities in the software and the third party components it uses.
  • CISA's Known Exploited Vulnerabilities catalog. CISA describes KEV as the authoritative source of vulnerabilities exploited in the wild and says organisations should use it as an input to their vulnerability management prioritisation framework.
  • CISA BOD 26-04, Prioritizing Security Updates Based on Risk. For US federal civilian agencies, BOD 26-04 replaced the older BOD 22-01 regime. It sets remediation urgency from four variables: asset exposure, KEV status, whether exploitation can be automated, and technical impact. BOD 22-01 is history now; its fixed deadlines are no longer the current rule.
  • FIRST's Exploit Prediction Scoring System (EPSS). EPSS publishes a daily probability that a CVE will be exploited in the wild. Safeguard's reachability docs describe it as the probability of exploitation within 30 days and say Safeguard ingests the daily feed.

Which Cargo files does Safeguard read?

Safeguard's documentation names the files in three places, and they agree.

Docs pageWhat it says about Rust
CLI support matrixPackage manager Cargo, manifest Cargo.toml, lockfile Cargo.lock, status Full Support
Deep dependency scanning"Cargo: resolves Cargo.toml and Cargo.lock"
Software Composition AnalysisSupported ecosystems include Cargo; manifests and lockfiles are both honoured

That is the complete list for Cargo in the docs. Safeguard's docs do not list any other Rust specific file for SCA, so this post does not claim one.

Two practical consequences follow.

Commit the lockfile for anything you ship. If a repository has only Cargo.toml, a scanner cannot know which exact versions you built. Libraries have historically left Cargo.lock out of version control, although the Cargo book now says to check it in when in doubt. For services, CLIs and anything packaged into an image, a committed lockfile is what makes the scan describe the binary.

Workspaces have one lockfile at the root. The Cargo workspaces reference says all packages in a workspace share a common Cargo.lock that resides in the workspace root. Scan from the workspace root so that file is in scope. Safeguard's docs do not describe how findings are split per workspace member, so check the project view on your own repository before you promise per crate ownership to teams. The post on workspace dependencies and one shared lockfile explains the Cargo side.

How does Safeguard resolve the transitive crate graph?

A Cargo.lock already contains the whole resolved graph: every package, its exact version, its source and the packages it depends on. Safeguard's deep dependency scanning (GA) walks every edge in that resolved graph. Safeguard's docs say it does four things at each node:

  1. Resolves the package URL and version against its vulnerability knowledge graph.
  2. Computes license and provenance data.
  3. Feeds the node into reachability analysis.
  4. Captures the full path from root to leaf, so you can see why a dependency is present.

Shared subgraphs are deduplicated, so a crate used by many parents is resolved once. Safeguard markets the depth limit as 100 levels. Rust graphs are rarely that deep, but the path capture is what matters for unification: when one vulnerable crate sits under several parents, the finding shows the chain.

In the app, clicking a finding shows the transitive path, whether the chain is reachable, whether any alternative path exists, and what the safe version would be along with the other transitive changes that version would introduce. That last point matters in Rust, where bumping one crate can pull new versions of its own dependencies into the lockfile.

What a path looks like

The docs show the path format with a JavaScript example. For a Cargo project the shape is the same. The tree below is illustrative, built from the RustSec example advisory above and a made up application name, to show how a native wrapper crate a few levels down appears:

billing-api 0.4.0
└─ telemetry-export 1.2.0
   └─ compress-adapter 0.9.1
      └─ lz4-sys 1.9.3   ← vulnerable here (RUSTSEC-2022-0051)

Read it bottom up. The vulnerable crate is lz4-sys at 1.9.3, three hops from your root. RustSec's solution is to move to 1.9.4 or later. Whether that is a lockfile only change depends on what compress-adapter requires, which is exactly what the "safe version and other transitive changes" panel is for.

Where do Rust advisories come from?

Safeguard's vulnerabilities docs list its data sources as the National Vulnerability Database, GitHub Security Advisories, OSV, CISA KEV, vendor security bulletins and security researcher disclosures. Safeguard's docs do not name the RustSec database directly.

RustSec's own site explains how its data travels. RustSec says it exports all of its data to Open Source Vulnerabilities (OSV) in real time, and that the GitHub Advisory Database imports its advisories. OSV's data page lists crates.io among the ecosystems it covers. So Rust advisories reach the sources Safeguard says it reads, through OSV and GitHub Security Advisories.

Many Rust advisories have no CVE. RustSec issues its own RUSTSEC-YYYY-NNNN identifiers, and not every one carries a CVE alias. Safeguard's SCA docs say each finding carries "the CVE/GHSA identifiers". A finding that only has a RustSec or GHSA identifier will not have an EPSS score or a KEV entry, because both of those are keyed by CVE ID. That is a property of the data, not of the scanner, and it changes how you read the ranking (more on that below).

Continuous scanning is part of the vulnerability management capability (GA). Safeguard's docs say all projects are rescanned daily, and that new CVEs trigger automatic re-evaluation of projects to see if they are affected. You can also add triggers on push, on a schedule, and on release. A crate that was clean when you shipped can become a finding without anyone touching the repository.

How does reachability work for Rust in Safeguard?

Safeguard's reachability analysis and prioritisation capability is GA. Its docs carry a language table, and the Rust row reads: static reachability GA, dynamic reachability GA, "Cargo-aware call-path extraction". Rust sits with the languages at full GA status.

The docs describe three kinds of reachability: static, dynamic and configuration. The first two are the ones that matter for Rust today:

  • Static reachability builds a call graph from your source, lockfile and bytecode, and checks whether any path leads to the vulnerable sink.
  • Dynamic reachability records, at runtime, which modules and functions are actually loaded and invoked in production workloads, and flags never executed vulnerable paths as cold. This depends on Safeguard's runtime collector being deployed. The runtime engine that collector belongs to is generally available, but it has to be deployed, so for most Rust teams static reachability is the part to plan around first.

Every finding lands in one of four classes:

ClassMeaning in Safeguard's docsWhat to do with it in a Rust codebase
ReachableA real call path exists and is instantiated at runtimeFix first, in priority order
Conditionally reachableA path exists but needs a feature flag, route or configuration not currently enabledCheck Cargo features: a feature you do not enable today may be switched on by another crate tomorrow
UnreachableNo call path exists in your compiled codeDefer with a recorded, expiring decision, never silently
UnknownThe analyser could not decide, for example dynamic dispatch, reflection or native codeTreat as potentially reachable, which is how Safeguard marks it

Where Rust reachability is hardest

Safeguard's docs name native code as a reason for an Unknown verdict, and that matters more in Rust than most people expect. A -sys crate's vulnerable function usually lives in C that is compiled by build.rs and called through extern declarations. A Rust call graph can show that your code calls the safe wrapper; whether the wrapper reaches the vulnerable C path is a different analysis. Expect more Unknown verdicts on FFI heavy graphs, and do not read Unknown as "probably fine".

The docs also list cases where reachability is weak in any language: data driven vulnerabilities, such as a parser that misbehaves on crafted input even though the specific function looks unreachable, and code paths that only run under configurations the analyser cannot enumerate. For those, Safeguard says to rely on the other signals, KEV, EPSS and runtime observation, and conservative defaults.

How do EPSS and KEV rank Rust findings?

Safeguard's prioritisation engine combines four signals: reachability, exploitability (EPSS and KEV), runtime context, and business context. Every finding gets a Priority Score from 0 to 100. The docs list the inputs: CVSS 3.1 base severity, EPSS probability, KEV listing, public proof of concept availability, reachability class, environment, exposure, data sensitivity and business criticality. Weights are configurable per tenant under Settings › Prioritization.

The defaults map to an ordering you can explain to an auditor:

CombinationDefault priority band
Reachable, KEV listed, production90 to 100 (P0)
Reachable, EPSS above 0.5, production75 to 89 (P1)
Reachable, high CVSS, staging50 to 74 (P2)
Unreachable, any severity0 to 30 (backlog)

The Rust twist: advisories without a CVE

EPSS and KEV are both keyed by CVE ID. A Rust finding that only carries a RustSec or GHSA identifier has neither signal, so its score leans on severity, reachability and asset context. Two habits follow:

  1. Do not read a missing EPSS score as low risk. It is missing data. Filter such findings separately and review them on reachability and severity.
  2. Weight reachability more heavily for crates. In a Rust graph, the reachability verdict is often the strongest signal you have, which is one reason the GA status of Rust static reachability matters.

The related post on prioritising with reachability, EPSS and KEV in Safeguard covers the score in full, including how to defend a deferral.

Filters that work for a Cargo backlog

The vulnerabilities table has Priority and Reachability columns, sorted by Priority by default. The docs list these filter forms:

reachable:true
kev:true
epss:>0.3
env:production reachable:true kev:true

The vulnerabilities page also supports filtering by severity, CVSS range, EPSS range, exploitable status, component name, fix availability, reachability status and date range. For a Rust estate, combining "fix available" with "reachable" gives you the list that AI Remediate can act on immediately.

How do fix pull requests work for Cargo?

Safeguard's autonomous remediation (GA) uses Griffin to open fix pull requests. It ships switched off, with every category set to Manual, and strategy choices are saved but not applied until the master toggle is on. Nothing changes in your repositories until someone turns it on.

Safeguard's AI Remediate docs list supported ecosystems, and the Rust row reads: package manager Cargo, AI Remediate support Full. The documented flow has five stages: analysis of the vulnerability and your codebase, solution generation to find the safest upgrade path, impact assessment for breaking changes, pull request creation, and validation by running automated tests.

For dependency upgrades, the docs say Griffin can upgrade vulnerable packages to fixed versions, handle transitive dependency conflicts, update lock files, and maintain compatibility with existing code.

Three ways to start a fix

  • Single vulnerability. Open the vulnerability, click AI Remediate, review the proposed changes, click Create Pull Request.
  • Bulk. Select vulnerabilities, click AI Remediate Selected. Griffin batches compatible fixes and creates one pull request per repository.
  • Page level. Click AI Remediate at the top of the Vulnerabilities page, review the plan for all fixable vulnerabilities, then approve and create pull requests.

Before any of that, you can use View Fix on a vulnerability to see upgrade options and breaking change warnings without opening anything.

What a Cargo fix actually changes

Cargo semantics decide the size of a fix, and it helps to know them before you review one.

  • A semver compatible fix is a lockfile change. If the fixed version satisfies the existing requirement in Cargo.toml (for example the fix is 1.9.4 and the requirement admits any 1.x), only Cargo.lock needs to move. The Cargo equivalent is cargo update with --precise for one package, covered in fix one vulnerable crate with a precise cargo update.
  • A fix in a new major version (or a new minor version for 0.x crates) needs a manifest change. Cargo treats 0.9 to 0.10 as incompatible under caret rules. The direct dependency requirement in Cargo.toml has to move, and your code may have to change.
  • A transitive fix your direct dependency blocks is the hard case. If a direct dependency pins an old range of the vulnerable crate, the clean fix is a new release of that direct dependency. Cargo's [patch] section is the documented override for pointing a dependency at a patched copy, explained in the patch section for a transitive dependency. Safeguard's docs do not say whether Griffin writes [patch] entries, so do not assume it does.

Griffin's breaking change classes give you a quick read of which case you are in: Safe, Minor, Major, or Critical (high risk of breaking, manual review required).

Settings to review before turning anything on

Under Settings › AI Remediate the docs list: Auto-Create PRs, Require Approval, Test Integration (run tests before creating pull requests), Breaking Change Policy, and Branch Naming. The autonomous remediation categories that matter most for Rust SCA are Vulnerabilities and Dependencies, each with four strategies:

StrategyDefinition from Safeguard's offering filesCargo reading
SafestBackward compatible fixes onlyLockfile moves within existing requirements
BalancedMinor version updates and patches, minimal breaking changesMostly lockfile changes, occasional manifest bumps
AggressiveAll available fixes including major version updatesManifest changes and possible code changes
SmartThe model picks per item based on context, risk and impactReview the first batch closely

Safeguard's own best practice list says to enable tests, start with safe fixes, review major changes and not auto merge breaking changes. For Rust that means making sure the pull request pipeline runs cargo build and cargo test across the feature combinations you ship, because a version bump can change which features unify.

Pull requests include a title with the CVE reference, a description of the vulnerability, a summary of changes, breaking change warnings, test results if configured, and links to advisories. The batch post on autonomous remediation categories and strategies covers the full matrix.

Where does Safeguard get license data for crates?

Safeguard's license compliance capability (GA) reads licenses from several places and reconciles them. The docs name Cargo.toml explicitly, alongside package.json and pyproject.toml, as a package metadata source. They also list SPDX identifiers, full text matching of LICENSE, LICENCE, COPYING and NOTICE files, source header detection, and extraction from container layers.

This fits Rust well. The Cargo manifest reference says crates.io interprets the license field as an SPDX 2.3 license expression, with the license-file field available for a nonstandard license. A large part of the crates ecosystem is dual licensed as MIT OR Apache-2.0.

Safeguard's docs say they understand full SPDX expression syntax. For OR, the most permissive option that matches your policy is chosen automatically. For AND, both apply. WITH exceptions are parsed too. When signals disagree, for example the manifest says MIT but the LICENSE file is a copyleft text, Safeguard raises a license discrepancy finding.

A policy is YAML. This one is taken from Safeguard's license compliance docs:

apiVersion: safeguard.sh/v1
kind: LicensePolicy
metadata:
  name: default
spec:
  allow:
    - MIT
    - BSD-2-Clause
    - BSD-3-Clause
    - Apache-2.0
    - ISC
    - MPL-2.0
  deny:
    - GPL-3.0-or-later
    - AGPL-3.0-or-later
    - SSPL-1.0
  require_review:
    - GPL-2.0-only
    - LGPL-3.0-or-later
    - "Custom/*"

Policies can be scoped by asset labels, so a library you distribute can have a stricter policy than a service you only run. Enforcement plugs into gates at CI time, at admission and at pull request time.

Two limits from the docs: there is no CLI command for license listing, reports or policy evaluation yet, and safeguard scan writes a license.json with the licenses it found. Attribution files (NOTICE and a licenses directory) are generated in the platform for redistributed artifacts. For a Rust binary that statically links every crate, that attribution output is worth having. The batch post on open source license obligations and attribution goes deeper.

Step by step: scan a Cargo project, triage, fix and gate

The walkthrough below uses only commands, screens and settings shown in Safeguard's docs. Where the docs do not show a screen, it describes the outcome instead.

Part A: scan locally with the CLI

  1. Install the CLI. On macOS and Linux:

    curl -fsSL https://cli.safeguard.sh/install | bash
    

    On Windows:

    irm https://cli.safeguard.sh/install.ps1 | iex
    

    The CLI is a single static binary with no runtime to install alongside it. It downloads its scanners on first use.

  2. Make sure the lockfile is current. From the workspace root, confirm Cargo.lock exists and is committed. If you have changed Cargo.toml since the last build, run a build or cargo generate-lockfile first so the lockfile matches the manifest.

  3. Open the interactive terminal and sign in.

    safeguard
    

    Type /login. Authentication is an OAuth 2.0 device code flow. On a machine without a browser, the command line form is safeguard login --no-browser.

  4. Scan the workspace. Type /scan . from the workspace root. A scan from the interactive terminal while signed in uploads the SBOM and findings to the platform. Your source never leaves the machine.

  5. Or scan from the command line and keep results local.

    safeguard scan --dir .
    

    safeguard scan builds an SBOM for the target, matches it against vulnerability databases and writes JSON results under ./output by default, in a folder per scan. Results include vuln.json, license.json and collection.json, among others.

  6. Read the exit code correctly. The docs are explicit: safeguard scan does not fail on findings. It exits 0 when the scan ran, whatever it found, 1 when the command itself failed, and 2 when results were written but the vulnerability scan did not actually run. There is no severity threshold flag on safeguard scan. Gating is done by policy gates in the platform.

If you would rather not grant repository access, the local runner built into the CLI executes the platform's scan jobs on your machine and uploads only SBOMs and findings. The batch post on choosing between the CLI and the local runner compares the routes.

Part B: connect the repository for continuous scanning

  1. Add the repository. Go to Integrations in the sidebar, open Add Project/Product, and pick the source: GitHub, GitLab, Bitbucket or a generic Git URL. For a private repository you provide credentials, verify them, then select repositories. For each one you set the branch or tag, the project name and the version.

  2. Choose the generation options. The docs list Project Name, Version (auto detected from git tags), Branch/Tag, Include Dev Dependencies and Deep Analysis (Griffin AI deep analysis). For Rust, decide deliberately about dev dependencies. Crates under [dev-dependencies] do not ship in your release binary, but they do run on developer machines and CI.

  3. Turn on the triggers you need. Daily continuous scanning is on for all projects. Add On Push for monitored branches, Scheduled, and On Release for new tags.

  4. Check the project. Projects in the sidebar lists each project with its version, status, findings summary and vulnerability count.

Part C: add the scan to CI

  1. Create a key and a repository secret. In Safeguard, create a secret key under Settings › Developer with the scopes scans:trigger and scans:read. In GitHub, add it as a repository or organisation secret named SAFEGUARD_API_KEY.

  2. Add the workflow. This is the workflow from Safeguard's GitHub Actions docs, saved as .github/workflows/safeguard.yml:

    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 because Safeguard clones the repository. To scan on the runner instead, the docs say to add actions/checkout@v4 before the step and --local to the command. Change the threshold with --fail-on high, drop --fail-on to report only, or add --fail-on-gate all to fail on a policy gate. Pin the CLI with SAFEGUARD_VERSION in env.

Part D: triage the Rust findings

  1. Open the project's Vulnerabilities tab. Findings sit in five categories: Critical, High, Medium, Low and Exploitable. Each row shows the CVE ID, CVSS, EPSS, exploitable status, affected component and version, fixed version, description, references and reachability.

  2. Work the P0 list first. Use env:production reachable:true kev:true, then widen to reachable findings with a fixed version available.

  3. Review findings without a CVE separately. Filter by component or severity to catch RustSec and GHSA only findings that have no EPSS or KEV signal.

  4. Record decisions. For a finding that does not apply, use Mark False Positive with a justification, submitted for approval if policy requires it. For a finding you will fix later, use Accept Risk with an expiry date and a business justification. Both leave an audit trail.

Part E: fix

  1. Preview the fix. Use View Fix to see upgrade options and breaking change warnings.

  2. Open the pull request. Click AI Remediate, review the proposed Cargo.toml and Cargo.lock changes, then Create Pull Request. For many findings across repositories, use AI Remediate Selected for one pull request per repository.

  3. Review it like any other Rust change. Check whether only the lockfile moved, or whether a requirement in Cargo.toml changed. Run the full feature matrix in CI. Merge only when tests pass.

  4. Decide on automation later. Once the team trusts the output for a few weeks, consider turning on the Vulnerabilities and Dependencies categories with the Safest strategy and Require Approval on. Until someone does, everything stays Manual.

Part F: gate

  1. Create a policy. Go to Settings › Policies, click Create Policy, define conditions with the visual builder or YAML, set severity and action, then save and enable. Conditions include vulnerability severity, CVSS, EPSS, license, component age and maintenance.

  2. Choose the action. Block fails the gate. Warn passes it with a warning naming the policy. Notify passes it and sends a notification.

  3. Wire the gate into CI. With policy outcomes on for the workspace, safeguard ci scan --wait --fail-on-gate <gate> fails the CI job when the gate fails. The batch post on plain English policies to release gates covers policy design.

Reachability also feeds guardrails. Safeguard's reachability docs show a production admission rule that blocks when any vulnerability is reachable or conditionally reachable and KEV listed:

- id: no-reachable-kev-in-prod
  condition: |
    env == "production"
    AND any(vulnerabilities,
      reachability_class in ["reachable", "conditional"]
      AND kev == true
    )
  effect: BLOCK

Requirement, what Safeguard does, and what you still own

RequirementWhat Safeguard does for a Cargo projectWhat you still own
SSDF PW.4.1: acquire and maintain well secured componentsSCA (GA) inventories every crate in Cargo.lock; license compliance (GA) classifies each oneChoosing crates, reviewing new dependencies, keeping the lockfile committed
SSDF PW.4.4: verify third party components throughout their life cycleDaily continuous scanning plus push, schedule and release triggers; policies (GA) for severity, license and maintenanceSetting the policy thresholds and owning exceptions
SSDF RV.1.1: gather vulnerability information from public sourcesMatching against NVD, GitHub Security Advisories, OSV and CISA KEV, with EPSSWatching advisories for crates outside a scanned project, such as vendored copies
CISA KEV as a prioritisation inputKEV status on every CVE linked finding and in the Priority ScoreDeciding your remediation timelines
BOD 26-04 risk factors (federal agencies)KEV status, exposure and environment labels feed the scoreAnswering asset exposure and confirming the four variables for each asset
Evidence of fixesFix pull requests with CVE reference, change summary and test resultsCode review, merge and release

Decision table: which Safeguard route fits your Rust situation

Your situationRouteLabel
One developer wants to check a crate graph todayCLI safeguard scan --dir . or /scan . in the terminalGA
You cannot grant Safeguard repository accessLocal runner in the CLI; only SBOMs and findings leave the machineGA
You want every push and release rescannedConnect the repository under Integrations and add triggersGA
You want builds to fail on new critical findingsGitHub Actions workflow with --fail-on criticalGA
You want builds to fail on your own rulesPolicy with Block action, plus --fail-on-gateGA
You need to know if a vulnerable crate is actually calledReachability column and filters; Rust static reachabilityGA
A fix exists and fits the existing requirementAI Remediate, Safest strategy, lockfile only changeGA, ships off
A fix needs a major version bumpAI Remediate with review; expect Major or Critical breaking change classGA, ships off
No fix exists upstreamAccept Risk with expiry; fork and patch requestFork and patch is generally available
You need license attribution for a shipped binaryLicense compliance attribution output in the platformGA

What Safeguard does not do

These limits are specific to Rust and Cargo, and they come from Safeguard's docs and offering files.

  1. It does not read anything beyond Cargo.toml and Cargo.lock for Cargo SCA, as far as the docs say. Vendored crates in a vendor/ directory, source replacement in .cargo/config.toml, and private registry indexes are not described in the SCA or support matrix docs. If your build resolves crates in an unusual way, scan the committed lockfile and confirm coverage on a sample repository before rollout.
  2. It cannot see a version you did not lock. A repository with only Cargo.toml gives a scanner intent, not fact. Safeguard honours manifests too, but the exact versions in your release binary come from the lockfile that built it.
  3. Reachability is not proof for native code. Safeguard marks native code, dynamic dispatch and reflection as Unknown and does not suppress those findings. That is the right default, but it means -sys crates and FFI heavy graphs will carry more Unknown verdicts. Dynamic reachability needs a runtime collector, which belongs to a runtime engine you deploy.
  4. There is no CLI severity gate on safeguard scan. It exits 0 on findings. Gates live in the platform, and the CI route is safeguard ci scan with --fail-on or --fail-on-gate.
  5. Several views have no CLI or working API yet. The docs say there is no CLI command for querying findings by priority, for querying the dependency graph, or for license reports. The /v1 REST endpoints on the reachability page are not yet available. Use the app, safeguard ci scan in CI, or an assistant connected through the connector directory.
  6. It does not merge for you by default. Autonomous remediation ships off with every category on Manual. Even when enabled, a fix pull request still needs your review, your CI and your merge.
  7. It does not decide how fixes interact with your features. A crate bump can change which features unify across the graph. Griffin's breaking change classes help, but only your test matrix proves a release works.
  8. It does not replace upstream. When a crate has no fixed release, a lockfile change cannot help. The options are a fork, a [patch] override you own, risk acceptance, or Safeguard's fork and patch flow, which is generally available.

Common mistakes

Scanning a library without a lockfile and trusting the result for the binary. The binary that ships was built from some lockfile. Scan that one, from the repository that produces the artifact.

Scanning one workspace member instead of the root. In a Cargo workspace, the shared Cargo.lock lives at the root. Scanning a member directory can miss the file that actually describes the build.

Treating a missing EPSS score as a low score. Many Rust advisories have no CVE, so they have no EPSS or KEV signal. Review them on severity and reachability rather than letting them sink.

Reading Unknown reachability as Unreachable. Safeguard deliberately marks native code paths as potentially reachable. Closing those as "not reachable" removes the one safety margin the analyser gives you.

Expecting safeguard scan to fail the build. It exits 0 when the scan ran. Use safeguard ci scan --fail-on critical or a gate.

FAQ

Does Safeguard support Rust and Cargo for SCA?

Yes. SCA is GA, and Safeguard's CLI support matrix lists Cargo with Cargo.toml as the manifest and Cargo.lock as the lockfile at Full Support. The SCA docs list Cargo among supported ecosystems and say manifests and lockfiles are both honoured.

Do I need to commit Cargo.lock for Safeguard to scan a Rust project?

You should. Safeguard honours manifests, but only Cargo.lock records the exact versions Cargo selected. The Cargo book itself says to check Cargo.lock into version control when in doubt. For anything you ship, the lockfile is what makes the scan describe the real binary.

Is reachability analysis available for Rust in Safeguard?

Yes. Safeguard's reachability language table lists Rust as GA for both static and dynamic reachability, with "Cargo-aware call-path extraction". Dynamic reachability needs a runtime collector deployed in your environment. Native code behind -sys crates is a common source of Unknown verdicts, which Safeguard treats as potentially reachable.

Why do some Rust findings have no EPSS score?

EPSS and KEV are keyed by CVE ID. Many Rust advisories carry only a RustSec or GHSA identifier with no CVE alias, so there is nothing for EPSS or KEV to score. Treat a missing score as missing data and review those findings on severity and reachability.

Can Safeguard open fix pull requests for Cargo dependencies?

Yes. Safeguard's AI Remediate docs list Rust with Cargo as Full support. Griffin can upgrade vulnerable crates, handle transitive conflicts and update lockfiles, then open a pull request with a change summary and test results if configured. Autonomous remediation ships switched off, with every category set to Manual.

Will a Safeguard fix change Cargo.toml or only Cargo.lock?

It depends on the fix. If the fixed version satisfies the existing requirement in Cargo.toml, only Cargo.lock needs to move. If the fix is in a new major version, or a new minor version of a 0.x crate, the requirement in Cargo.toml has to change too. Griffin's breaking change class tells you which case you are in.

Does safeguard scan fail the build when it finds a vulnerable crate?

No. safeguard scan exits 0 whenever the scan ran, whatever it found. To fail a CI job, use safeguard ci scan --wait --fail-on critical as Safeguard's GitHub Actions docs show, or add --fail-on-gate to fail on a policy gate.

How does Safeguard handle the MIT OR Apache-2.0 licenses common in Rust?

Safeguard reads the license field in Cargo.toml and parses full SPDX expressions. For an OR expression, it picks the most permissive option that matches your policy automatically. For AND, both licenses apply. Mismatches between the manifest and the LICENSE file raise a license discrepancy finding.

Fix the reachable crates first, then close the gap upstream

For most Rust findings the fix is a lockfile or manifest bump, and the sections above show how Safeguard turns that into a reviewed pull request. The block below covers the wider options Safeguard offers when the fix is slow to arrive or does not exist yet, including the fork and patch flow.

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. It installs the CLI, opens the interactive terminal, and asks it to "fix the lodash CVE in this repo": the assistant 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 approval, prints a summary and pushes nothing, then shows /permissions, /model and /scan. The demo uses an npm project, but the flow is the same shape for a crate: the change lands in Cargo.toml or Cargo.lock, and the commands it asks to run are your Cargo build and test commands.

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. This is the route for a Rust repository you cannot connect to Safeguard directly: the runner scans the workspace locally and uploads only the SBOM and findings, never the source.

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

The third demo connects Claude to Safeguard with OAuth and shows how you choose which tools Claude can use. Add Safeguard from Claude's connector directory rather than configuring a custom server. Once connected, you can ask about the vulnerable crates in a project in plain language, which fills the gap left by the missing CLI command for querying findings by priority.

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

To try this on your own Cargo workspace, download the CLI and apps at safeguard.sh/download and follow the setup guides at docs.safeguard.sh/docs.

For more depth, read the batch posts on prioritising with reachability, EPSS and KEV, SBOM generation and analysis in Safeguard and using Gold Open Source in your workflow.

Quick check

A Rust finding has no EPSS score and no KEV status, and sits low in the list. Is it safe to ignore?

No. EPSS and KEV are keyed by CVE ID, and many Rust advisories carry only a RustSec or GHSA identifier. The missing score is missing data, not a low score. Review the finding on severity and reachability, and record a decision either way.

Safeguard proposes a fix where only Cargo.lock changes. What does that tell you about the upgrade?

The fixed version already satisfies the requirement in Cargo.toml, so it is a semver compatible move within the range you declared. It is usually the lowest risk kind of fix, but still run your full feature matrix in CI, because a new version can change which features unify across the graph.

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.