Dependencies

Software Composition Analysis (SCA)

xgrep is a Software Composition Analysis (SCA) tool — rank dependency CVEs by whether your code can actually reach them, generate a CycloneDX or SPDX SBOM, trace dependency usage offline, and upgrade what matters, across 21 ecosystems.

Software Composition Analysis (SCA)

Most of the code you ship, you didn't write. Software Composition Analysis (SCA) is how you find out what is in there and which of it is dangerous — and xgrep does it in the same pass as your code scan, so one xgrep scan . reports vulnerabilities in your own code and your dependencies together.

Start with what your code can actually reach

Point any scanner at a real project and it returns hundreds of CVEs. Most of them are in code your application never executes — a transitive package pulled in for one function you don't call, a dev tool that never ships. Upgrading all of them is weeks of churn and breakage; ignoring all of them is how the real one gets missed.

Reachability is xgrep's answer. It ranks every dependency vulnerability by whether your code reaches the affected code — down to the individual vulnerable function, with the call path that proves it — so the findings that can actually hurt you are the ones at the top.

xgrep scan --reachability .   # rank dependency CVEs by whether your code reaches them
xgrep scan --reachable-only . # or hide the ones it demonstrably can't

It never demotes on a guess: where it cannot establish that nothing reaches a package, the finding stays exactly where it was. See Reachability for what each classification means and what your project needs to give it.

Everything else SCA answers

  • What's in here? — a complete Software Bill of Materials (SBOM) in CycloneDX or SPDX format, or an AI Bill of Materials (AIBOM) inventorying the AI SDKs, frameworks, runtimes, and models the code uses.
  • Which dependencies are vulnerable?vulnerability scanning that matches your packages against known CVEs, reported inline with your code findings.
  • How do we fix them?dependency upgrades through your own package manager, verified by re-detecting the vulnerability afterwards.
  • What actually uses this package?dependency queries that list, group, and trace dependencies against the code that imports them.
  • Is it legally compliant?license compliance that classifies each dependency's license and checks it against a deployment-aware policy.
  • Who do we credit? — an attribution / NOTICE document listing every component, its license, the copyright holders and the license texts, with a CI gate that catches it going stale.
xgrep scan .                 # code (SAST) AND dependency vulnerabilities (SCA) in one run
xgrep sbom .                 # generate an SBOM (CycloneDX JSON by default)
xgrep deps reachability .    # which packages your first-party code actually imports
xgrep deps list .            # flat, scope-tagged dependency inventory
xgrep deps notice .          # third-party attribution (NOTICE) document

SBOM generation and dependency queries are fully offline: xgrep reads manifest files only, never invoking a package manager or reaching the network. Vulnerability scanning is the one networked step — it matches your SBOM against the CVE database via Mondoo Platform and needs a service account; see Scanning for vulnerabilities.

Supported ecosystems

xgrep parses the dependency manifests and lockfiles of 21 ecosystems. For most, the highest-priority file present in a directory wins (lockfiles are preferred over manifests, so a project with both is never double-counted). A few ecosystems are many-per-project — GitHub Actions has one file per workflow, OCaml one *.opam file per package — and every matching file is parsed and merged:

EcosystemManifests and lockfiles parsed
Gogo.mod
npm (JavaScript/TypeScript)package-lock.json, pnpm-lock.yaml, yarn.lock, bun.lock, deno.lock, package.json
PythonPipfile.lock, poetry.lock, uv.lock, pdm.lock, requirements.txt
RustCargo.lock
RubyGemfile.lock
PHPcomposer.lock
Javagradle.lockfile, pom.xml
.NETobj/project.assets.json, packages.lock.json, packages.config, *.csproj / *.fsproj / *.vbproj
Dartpubspec.lock
Elixirmix.lock
Erlangrebar.lock
SwiftPackage.resolved
CocoaPodsPodfile.lock
C/C++ (Conan)conan.lock, conanfile.txt, conanfile.py
C/C++ (vcpkg)vcpkg.json, vcpkg_installed/vcpkg/status
Condaenvironment.yml, environment.yaml
JuliaManifest.toml
Rrenv.lock
Haskellcabal.project.freeze, stack.yaml.lock
Terraform.terraform.lock.hcl
OCaml*.opam, *.opam.locked
GitHub Actions.github/workflows/*.yml, .github/workflows/*.yaml

Restrict any command to specific ecosystems with --ecosystem, e.g. --ecosystem go,npm.

.NET dependencies come from the restore

An SDK-style .NET project states only its direct dependencies, as <PackageReference> entries in the .csproj, and only writes a packages.lock.json if the author opts in. Read on its own, a project file gives you a handful of packages and no transitive closure — and where it states a range or a floating version (12.0.*), no exact version either.

Run dotnet restore before scanning. The restore writes obj/project.assets.json: every package the build resolved, at the version it resolved to. xgrep reads it and prefers it over the project file beside it, so a restored project reports its full closure with exact versions instead of its declarations. Nothing is invoked and nothing is downloaded — it is a file on disk, read like any other manifest.

That also decides how many licenses you get. A .csproj carries no per-dependency license field, so xgrep reads each package's .nuspec from the NuGet global-packages folder your restore populated (~/.nuget/packages by default, or $NUGET_PACKAGES). A package the inventory never names is a package whose license is never looked up — which is why the restore, not the project file, is what makes a transitive AGPL dependency visible.

Without a restore, the project file is still read and nothing changes.

C/C++ versions come from the install tree

Conan and vcpkg are listed separately above because they are separate package managers, and --ecosystem conan and --ecosystem vcpkg select them independently. --ecosystem cpp (or c) selects both.

vcpkg is the one ecosystem whose manifest states no versions at all, by design. A vcpkg.json names ports and leaves their versions to the registry's builtin-baseline, so a scan of the manifest alone reports your dependencies without versions — and a component with no version matches no advisory. Two things change that:

  • Run vcpkg install before scanning. xgrep reads vcpkg_installed/vcpkg/status, which is vcpkg's own record of what it put on disk, and fills in each dependency's resolved version. It also reports the ports vcpkg pulled in that your manifest never named, which is the only way a vcpkg scan sees transitive dependencies. The install tree is where the per-dependency licenses come from too.
  • Pin with overrides. A version you pin in the manifest is used as-is. A version>= entry is not a pin — it is a minimum, and vcpkg routinely resolves above it — so it is reported as a version_min qualifier rather than as the version.

Conan is the mirror image: conanfile.txt and conanfile.py are read, so a project reports its dependencies whether or not a conan.lock is committed. Where both exist the lockfile wins, because it resolves the whole graph. Two kinds of requirement resolve to no version rather than a guess — a version range (fmt/[>=9.0]) and a requirement a requirements() method computes at install time. Commit a conan.lock if you want those answered.

Which manifests count

Dependency files under test, fixture, example and documentation trees are skipped. They describe fixture data, not what your project depends on — a lockfile that exists only as test input would otherwise add its contents to your inventory as though you depended on them, and those are often real package names that real advisories match.

The skipped path components are test/, tests/, __tests__/, spec/, specs/, e2e/, t/, testdata/, fixtures/, mock(s)/, unit-tests/, unit_tests/, fuzz(ing)/, benchmark(s)/, eval(s)/, example(s)/, example_dags/, docs/ and doc/, matched anywhere in the path. Your repository root is always in scope, whatever the checkout is named.

One consequence is worth knowing: a genuine sub-project under such a directory — a documentation site with its own package.json, say — is not inventoried as part of the parent project. Pass --include-tests to xgrep sbom, xgrep deps or xgrep scan to parse everything:

xgrep deps list --include-tests .
xgrep sbom --include-tests .

Scanning a specific commit

Every dependency command takes --ref to read files from a git commit, tag, or branch without checking it out, so you can produce an SBOM for a release tag or compare the dependency set between two commits:

xgrep sbom --ref v1.4.0 .
xgrep deps list --ref main .

In this section

On this page