Cake.Grype 0.1.0-preview.1

This is a prerelease version of Cake.Grype.
dotnet add package Cake.Grype --version 0.1.0-preview.1
                    
NuGet\Install-Package Cake.Grype -Version 0.1.0-preview.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Cake.Grype" Version="0.1.0-preview.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Cake.Grype" Version="0.1.0-preview.1" />
                    
Directory.Packages.props
<PackageReference Include="Cake.Grype" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Cake.Grype --version 0.1.0-preview.1
                    
#r "nuget: Cake.Grype, 0.1.0-preview.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Cake.Grype@0.1.0-preview.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Cake.Grype&version=0.1.0-preview.1&prerelease
                    

Cake.Grype

A Cake add-in for Grype by Anchore: scan SBOMs, directories and container images for known vulnerabilities, manage Grype's vulnerability database, and read Grype's JSON report to gate a build in C#.

Installation

#addin nuget:?package=Cake.Grype

Grype itself must be installed and on PATH (or set ToolPath in any settings object):

Platform Install
Windows winget install Anchore.Grype
macOS brew install grype
Linux / CI curl -sSfL https://get.anchore.io/grype \| sudo sh -s -- -b /usr/local/bin

Tested with Grype 0.119.0. Use a recent Grype when scanning SBOMs from the CycloneDX .NET tool 6.x: they use CycloneDX spec 1.7, which older Grype versions (e.g. 0.110.0) reject with unable to decode sbom: sbom format not recognized. On GitHub Actions, anchore/scan-action/download-grype installs an older Grype by default; pin it with grype-version.

Scan an SBOM

Print the table in the terminal and write a JSON report as a CI artifact, in one run:

GrypeScanSbom("./artifacts/bom.cdx.json", new GrypeScanSettings
{
    Outputs = { GrypeOutput.Table(), GrypeOutput.Json("./artifacts/grype.json") },
    SortBy = GrypeSortBy.Risk,
});

Output files are written with absolute paths, missing directories are created, and existing files are deleted before the scan so a failed run never leaves a stale report behind. Other formats: GrypeOutput.Sarif(file), CycloneDx(file), CycloneDxJson(file), Template(file) (with Template = "report.tmpl").

Other sources

GrypeScanDirectory("./src");
GrypeScanFile("./bin/app.jar");
GrypeScanImage("myorg/api:1.2.3");        // Grype's default lookup (Docker daemon first)
GrypeScanRegistry("myorg/api:1.2.3");     // no container runtime needed

GrypeScan(GrypeSource.OciArchive("./image.tar"));
GrypeScan(GrypeSource.Purl("pkg:nuget/Newtonsoft.Json@12.0.1"));
GrypeScan("registry:alpine:3.20");         // any Grype source string, passed unchanged

// several sources, one loop
var sources = new List<GrypeSource>
{
    GrypeSource.Sbom("./artifacts/api.cdx.json"),
    GrypeSource.Sbom("./artifacts/web.cdx.json"),
};
foreach (var source in sources)
{
    GrypeScan(source, settings);
}

Fail the build

Let Grype decide

GrypeScanSbom("./artifacts/bom.cdx.json", new GrypeScanSettings
{
    Outputs = { GrypeOutput.Table(), GrypeOutput.Json("./artifacts/grype.json") },
    FailOn = GrypeSeverity.High,
});

Grype writes all outputs, then exits with code 2, which throws a CakeException. To keep going and decide yourself, accept the exit code:

HandleExitCode = code => code is 0 or 2,

Decide in C#

var report = GrypeReadJson("./artifacts/grype.json");

var blocking = report.Matches.Where(m =>
       m.IsKnownExploited                                      // KEV: exploitation observed
    || m.MaxEpssScore >= 0.5                                   // EPSS: likely to be exploited
    || (m.Severity >= GrypeSeverity.Critical                   // severity/CVSS: potential impact...
        && m.Vulnerability.Fix.State == GrypeFixState.Fixed)   // ...and a fix exists
    || m.Risk >= 50)                                           // Grype's combined risk score
    .ToList();

foreach (var m in blocking)
{
    Error("{0} {1} {2} ({3}, risk {4:0.0})", m.Artifact.Name, m.Artifact.Version, m.Vulnerability.Id, m.Severity, m.Risk);
}

if (blocking.Count > 0)
{
    throw new CakeException($"{blocking.Count} blocking vulnerabilities");
}
Signal Meaning Model
KEV Known to have been exploited m.IsKnownExploited, m.Vulnerability.KnownExploited
EPSS Likely to be exploited, according to the model m.MaxEpssScore, m.MaxEpssPercentile, m.Vulnerability.Epss
CVSS / severity Potential technical impact, not whether exploitation is occurring m.Severity, m.MaxCvssBaseScore, m.Vulnerability.Cvss
Grype risk Combined prioritization based on severity, EPSS and KEV m.Risk

The m.* helpers also look at RelatedVulnerabilities: a distro advisory (for example Debian's) often has no CVSS itself, only the related NVD record does.

Also available: report.CountBySeverity(), report.AtOrAbove(GrypeSeverity.High), report.IgnoredMatches (with the ignore rules that applied), report.Distro, report.Descriptor.

Unknown severity

GrypeSeverity.Unknown means not assessed yet — typically a reserved CVE without published analysis. Such matches have risk 0 and no EPSS, KEV or CVSS data, so severity, risk and EPSS thresholds skip them, and so does Grype's own --fail-on. If unassessed findings should block a build, say so explicitly:

var unassessed = report.Matches.Where(m => m.Severity == GrypeSeverity.Unknown).ToList();

Vulnerability database

var version = GrypeVersion();                  // version.Version, version.SupportedDbSchema
var status  = GrypeDbStatus();                 // status.Valid, status.Built, status.SchemaVersion, status.Error
var check   = GrypeDbCheck();                  // check.UpdateAvailable, check.Current, check.Candidate
GrypeDbUpdate();
GrypeDbImport("./cache/vulnerability-db.tar.zst");                   // offline / air-gapped
GrypeDbImport(new Uri("https://grype.anchore.io/databases/...?checksum=sha256:..."));
GrypeDbDelete();                               // start from a clean cache

GrypeDbStatus returns Valid == false (it does not throw) when no database is installed, and GrypeDbCheck reports an available update through UpdateAvailable (Grype's exit code 100 is expected).

CI: update once, then scan without network checks

var ciEnvironment = new Dictionary<string, string>
{
    ["GRYPE_DB_CACHE_DIR"] = MakeAbsolute(Directory("./.cache/grype")).FullPath,  // cache this directory in CI
    ["GRYPE_DB_AUTO_UPDATE"] = "false",
    ["GRYPE_CHECK_FOR_APP_UPDATE"] = "false",
};

GrypeDbUpdate(new GrypeDbUpdateSettings { EnvironmentVariables = ciEnvironment });
GrypeScanSbom("./artifacts/bom.cdx.json", new GrypeScanSettings
{
    EnvironmentVariables = ciEnvironment,
    Outputs = { GrypeOutput.Table(), GrypeOutput.Json("./artifacts/grype.json") },
});

CI artifact

Upload the report even when the gate fails, for example in GitLab:

grype:
  script: dotnet cake --target=Grype
  artifacts:
    when: always
    paths:
      - artifacts/grype.json

Global settings

Every settings class has Grype's global flags: ConfigFiles (-c), Profiles (--profile), Quiet (-q) and Verbosity (-v/-vv), plus Cake's standard ToolPath, WorkingDirectory, EnvironmentVariables, HandleExitCode, PostAction, … Relative paths are resolved against WorkingDirectory (or the Cake working directory).

Not supported (yet)

grype db list|providers|search|diff, grype config, grype explain (needs stdin; a prototype feature), parsing SARIF/CycloneDX output, and piping Syft output into Grype.

Building

The build is a Cake Frosting project in build/:

./build.ps1 --target All      # Windows
./build.sh --target All       # Linux/macOS
Target Does
Default / Build Builds Cake.Grype.sln in Release
Test Runs the unit tests
Pack Packs artifacts/Cake.Grype.<version>.nupkg and verifies its content
Dogfood Generates a CycloneDX SBOM of the solution with Cake.CycloneDX, scans it with this build of Cake.Grype, and fails on known-exploited or fixable High/Critical findings (report in artifacts/dogfood/)
All Test, Pack and Dogfood

Prerequisites: the .NET 10 SDK plus the .NET 8 and .NET 9 runtimes (the tests run on all three), Grype on PATH (or GRYPE_PATH set to the executable) and the CycloneDX tool (dotnet tool install -g CycloneDX --version 6.2.0). Versions come from git tags via MinVer; see docs/release-policy.md for how releases are made.

Releasing

Releases are made by pushing a version tag; the Release workflow then builds, publishes to NuGet and creates the GitHub Release. release.ps1 (PowerShell 7.2+, git and gh) does the tagging safely:

./release.ps1                                  # status and suggested next versions; changes nothing
./release.ps1 -Bump Minor                      # v1.2.4 -> v1.3.0
./release.ps1 -Bump Minor -Prerelease preview  # v1.2.4 -> v1.3.0-preview.1
./release.ps1 -Prerelease preview              # v1.3.0-preview.1 -> v1.3.0-preview.2
./release.ps1 -Promote                         # v1.3.0-preview.2 -> v1.3.0
./release.ps1 1.3.0 -WhatIf                    # dry run with an explicit version

It checks that you are on a clean, up-to-date main with a green CI run, shows what it will do and asks before creating and pushing the tag, then follows the Release workflow. See Get-Help ./release.ps1 -Full. The version logic is tested with Pester 5+: Invoke-Pester ./tests/Release.Tests.ps1.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0-preview.1 44 9/24/2026