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'tIt 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) documentSBOM 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:
| Ecosystem | Manifests and lockfiles parsed |
|---|---|
| Go | go.mod |
| npm (JavaScript/TypeScript) | package-lock.json, pnpm-lock.yaml, yarn.lock, bun.lock, deno.lock, package.json |
| Python | Pipfile.lock, poetry.lock, uv.lock, pdm.lock, requirements.txt |
| Rust | Cargo.lock |
| Ruby | Gemfile.lock |
| PHP | composer.lock |
| Java | gradle.lockfile, pom.xml |
| .NET | obj/project.assets.json, packages.lock.json, packages.config, *.csproj / *.fsproj / *.vbproj |
| Dart | pubspec.lock |
| Elixir | mix.lock |
| Erlang | rebar.lock |
| Swift | Package.resolved |
| CocoaPods | Podfile.lock |
| C/C++ (Conan) | conan.lock, conanfile.txt, conanfile.py |
| C/C++ (vcpkg) | vcpkg.json, vcpkg_installed/vcpkg/status |
| Conda | environment.yml, environment.yaml |
| Julia | Manifest.toml |
| R | renv.lock |
| Haskell | cabal.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 installbefore scanning. xgrep readsvcpkg_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. Aversion>=entry is not a pin — it is a minimum, and vcpkg routinely resolves above it — so it is reported as aversion_minqualifier 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
- Generating an SBOM — formats, flags, and CI usage.
- Querying dependencies —
list,tree, andwhy. - Scanning for vulnerabilities — match
dependencies against known CVEs, with
xgrep scanor an existing SBOM. - Reachability — rank those vulnerabilities by whether your code actually reaches them, down to the vulnerable function, with the call path that proves it.
- License compliance — classify dependency licenses and gate on a deployment-aware policy, offline.