Who depends on this library?
A critical vulnerability is published on a Friday evening. Within hours, every team asks the same question: do we use it, and where? Many took days to answer in December 2021, when Log4Shell dropped, and the difficulty was never the flaw itself. It was that few teams knew the real structure of their own system.
ast-metrics who-uses answers that question from the dependency graph:
ast-metrics who-uses log4j ./src
Why a grep is not enough
A grep on the library's name finds the files that import it. That is the right first move, and it is often wrong by an order of magnitude.
Picture a team that wrapped its logging library in a Logger class six years
ago, to be able to swap it one day. Good engineering. The grep returns one
file, and everyone concludes the library is barely used. In fact three
hundred files go through that wrapper, including the ones that log incoming
HTTP headers. The grep found the name; the chain of dependencies is written
nowhere as text, so it never saw it.
A software bill of materials (SBOM) and a composition scanner answer do we have this component? in thirty seconds, and you should have them. They give you an inventory. The graph answers who, in our code, depends on it, how far does that go, and which parts of the system are exposed? That is a topology. The two are complementary, and the second one is what decides where to look first when you cannot audit everything.
The same question comes up outside of security. Knowing what your system stands on, and how much of it, is a matter of sovereignty: a framework or a vendor SDK that eighty percent of the code reaches is a dependency you do not choose anymore. The answer is worth having before the licence changes, the maintainer leaves or the region it is hosted in becomes a problem.
How it works
Every engine records the modules a file imports, spelled the way the import
spells them: org.apache.logging.log4j in Java, github.com/sirupsen/logrus
in Go, react in TypeScript, Monolog\Logger in PHP, tracing in Rust,
loguru in Python, Serilog in C#. The ones that resolve to no file of the
project are its libraries.
The name you give is matched anywhere in those modules, case-insensitively:
log4j finds org.apache.logging.log4j and org.apache.logging.log4j.core
alike. From the files importing them, AST Metrics follows the dependents
level by level on the file graph: the files depending on an importer, then
the files depending on those, until nothing new is reached. Each file is
reported once, at the shortest distance found.
$ ast-metrics who-uses --limit 3 psr ./src
21 imported modules matching
Psr\Http\Message\RequestInterface
Psr\Http\Message\ResponseInterface
...
38 of 41 files depend on it (93%)
A file that imports it is one step away; a file depending on that file is two steps away, and so on.
Import it directly · 29 files
BodySummarizer.php
Client.php
...
Depend on a file that imports it · 9 files
Cookie/FileCookieJar.php
...
Communities reached
GuzzleHttp ██████████ 21 of 21 files (100%)
Cookie ████████░░ 4 of 5 files (80%)
The distance matters more than the total. In a monolith, the closure from any widely used module covers most of the project, and a total alone would say that everything depends on everything. What the levels tell you is where the exposure is concentrated: the files importing the library, and the ones one step behind them, are where a review starts.
The last block maps the reached files onto the communities the code forms, the most exposed first. This is the answer in the words of an architecture diagram rather than a file list: billing is entirely exposed, the catalog is not.
Options
| Option | Effect |
|---|---|
--format json |
The full answer as JSON: matched modules, files per level, scope, communities. |
--max-depth N |
Stop N steps past the importing files. 0 (the default) follows to the end. |
--limit N |
Files listed per level in the text output, 20 by default. 0 lists them all. |
--exclude, --config, --*-extensions |
The same file selection options as analyze. |
Flags go before the library name and the paths. The command exits with a non-zero status when no imported module matches, so a script can tell "nobody depends on it" from "the analysis did not run".
Where else the answer lives
The JSON report of ast-metrics analyze --report-json carries a
libraries section: every imported module with the number of files
importing it (importers), the number of files depending on it at any
distance (reach), and standard: true for the modules of the language
itself (fmt, java.util, System.IO, std::collections, PHP's global
classes). A job can watch it over time:
$ jq '.libraries[] | select(.standard | not) | select(.reach > 100) | .module' report.json
The MCP server exposes the same question as the who_uses tool, so an
AI assistant wired to the server can answer what
would be exposed if this package had a flaw? from the code rather than from
a guess.
What it does not do
The graph describes the code, not its execution: a possible path is not a path taken. Static analysis also misses reflection, dependency-injection containers and dynamic loading, so the answer is an upper bound of what a flaw can touch, and an incomplete one where the wiring is dynamic. An import is not a data flow either: the command tells you which files stand on the library, not whether the tainted input reaches it.
Versions are out of scope on purpose. Whether the log4j you depend on is the vulnerable release is the job of your SBOM and your composition scanner; this command tells you how much of your code stands behind the answer.
Forbid a library where it does not belong
The coupling rule of the linter can
forbid a dependency from a part of the code, by class or by package:
requirements:
rules:
architecture:
coupling:
forbidden:
- from: "Domain"
to: "org\\.apache\\.logging"