Jit- announcement icon

Announcing Jit’s AI Agents: Human-directed automation for your most time-consuming AppSec tasks.

Read the blog

In this article

How to Use a Dependency Graph to Analyze Dependencies

Jit Logo
By Jit Team

Published August 16, 2025

How to Use a Dependency Graph to Analyze Dependencies

Modern software rarely runs on just your code. Every release pulls in a mesh of third-party libraries, internal modules, plugins, and layers of transitive dependencies you might never see. Package managers and frameworks automate much of this, which means what ends up in production often extends far beyond the code your team wrote or manually reviewed.

In 2024, the number of new CVEs reported reached an all-time high of 40,009, marking a 38% increase over the previous year. Since 2016, the number of CVEs reported annually has grown by over 500% (up from 6,449 CVEs in 2016). 

But flat vulnerability lists from most scanners offer little context. They don’t show which packages are reachable, loaded, or relevant to your environment. Dependency graphs give you that visibility. They map out how packages connect across your systems so you can focus on risks that matter.

What Is a Dependency Graph, and Why Is It Needed Today?

A dependency graph outlines how the pieces of your software depend on one another. Each node is a module or package, and each connection shows the link between them. If A pulls in B, and B brings in C, the graph follows that entire chain.

You get a dependency map of the real structure behind your application; not just the code you wrote, but everything that ships with it. This includes direct dependencies and transitive ones that sneak in through other packages, the parts of the stack you rarely see until they become a problem.

Dependency typeWhat it is
DirectDeclared in your package or manifest fileexpress in package.json
TransitiveIndirectly included by another dependencymine-types via express

In real-world projects, dependency graphs quickly grow large and intricate. Microservices multiply the number of packages in play, while applications spanning multiple languages bring their dependencies, each resolved differently by their respective package managers. Even a small team shipping frequent releases will accumulate a deeply layered graph that shifts with every update. Without that graph, answering even basic security questions becomes guesswork:

  • Is this CVE reachable from our code? 

  • Which services does it touch? 

  • Is the vulnerable package loaded at runtime or only pulled in for testing?

A dependency graph turns that uncertainty into clarity. It reveals how packages are reused, how vulnerabilities can propagate across your system, and where your attention is most needed. A flat dependency list offers no such context. Conversely, a graph shapes an otherwise invisible layer of your software stack, making it far easier to act decisively on security risks.

a diagram of the different types of software


The Security Risks Hidden in Complex Dependency Trees

1. Hidden Transitive Vulnerabilities

Transitive dependencies are the indirect packages your software inherits through other libraries. They often go unreviewed, yet they ship with your code into production and with them, any flaws or malicious payloads they contain. When you don’t track these dependencies, a significant portion of your software’s attack surface remains invisible.

In 2024, researchers demonstrated exactly how dangerous this blind spot can be. They uncovered a Java Class Hijack technique in which a malicious class buried inside a transitive JSON library overrode legitimate code at runtime. Nothing had to be installed directly; the exploit targeted packages buried deep in the dependency tree, bypassing traditional checks and highlighting how easily hidden vulnerabilities can infiltrate production environments.

2. Malicious or Hijacked Packages

A library can be compromised or built to mimic a trusted one, with the malicious code buried where it’s unlikely to be noticed. Once you pull that package into a project, the attacker’s payload can run as part of the build, leak data, or open a backdoor before anyone realizes it’s there. Because these packages slot into standard development workflows, they can move into production without triggering warnings.

a screenshot of a web page with a description


3. Typosquatting

Attackers often exploit package managers by publishing libraries with names almost identical to trusted ones. Swapping a single letter or slightly reordering a word is sometimes all it takes for a developer or an automated build pipeline to pull in the malicious package instead of the real one.

In October 2024, researchers uncovered a large-scale npm typosquatting campaign. Attackers published malicious Ethereum-related packages mimicking popular modules (rearranging fetch-mock-jest into jest-fet-mock). These packages were downloaded thousands of times and designed to exploit CI systems that inadvertently pull from public registries instead of private ones.

4. Dependency Confusion

Dependency confusion attacks exploit build pipelines that prioritize public registries over internal ones. When an attacker publishes a package with the same name as an internal library, automated systems can mistakenly pull the public version, inserting malicious code directly into the build. The attack works because it leverages the automation and trust that modern development pipelines depend on.

It’s a failure of trust in automated supply chains, similar to the risks seen in automated dropshipping when fulfillment systems source from unverified suppliers. In both cases, automation magnifies the impact of a single compromised source, allowing malicious content to move downstream without manual review.

License Violations

Transitive dependencies can bring in libraries with restrictive or incompatible licenses, adding compliance risks that are easy to miss until they surface later in the build process. For closed-source software, these issues can escalate into legal exposure or require expensive rework if caught late in the release cycle. Because these licenses are often buried several layers deep in the dependency tree, they can slip into production unnoticed unless actively tracked.

7 Steps to Use a Dependency Graph to Analyze Dependencies

A dependency graph only becomes useful when it guides action. Here's how to move from a static view of your packages to clearly understand where risk lives, what needs fixing, and what you can safely ignore.

1. Generate the Graph from Your Codebase

Start by creating a complete snapshot of your dependencies. Ensure you do this from source, not runtime logs or post-build artifact scans, and use tools that work natively with your stack:

  • npm ls or yarn list for JavaScript
  • mvn dependency:tree for Java
  • go mod graph for Go
  • pipdeptree for Python
  • cargo tree for Rust
a screenshot of a computer screen with a black background


2. Include Transitive Dependencies

Always ensure your dependency graph captures every transitive package. Most vulnerabilities originate several layers deep, in libraries you never interact with directly, but that still ship with your build. Skipping them creates blind spots and a false sense of security. Use tools like OSV-Scanner to achieve full transitive coverage and map all nested dependencies.

3. Visualize the Graph

A flat dependency list can’t show how packages connect or interact. Mapping the graph exposes patterns you won’t see in text, such as deeply nested libraries, duplicate versions, circular references, or nodes at the centre of multiple services.

To build these maps, you can use tools such as Graphviz for custom views, Dependency Cruiser for JavaScript, and IntelliJ’s dependency analyzer for JVM projects. These tools show how a vulnerability might spread through the system and where it would land if exploited. Here’s an example of a map:

a diagram of a computer system


4. Analyze the Graph for High-Impact Nodes

Identify nodes with high fan-in. These packages sit at the centre of your dependency graph and are relied on by multiple upstream components. They are often shared libraries or core utilities reused across microservices, making them critical points of failure.

A vulnerability in one of these nodes can cascade through large portions of your environment. Prioritize patching these dependencies first, and use runtime enforcement or isolation controls to contain the blast radius until a fix is available.

5. Trace Vulnerabilities and Scope Their Impact by Context

A flagged vulnerability doesn’t always translate into a real risk. Tools like Trivy, Semgrep, or CodeQL can show whether your codebase's affected functions are called. Context matters: pair CVSS scores with exploitability, where and how the package is used, its runtime behaviour, and the systems it touches. A flaw buried in a development tool does not carry the same urgency as one running in a production-facing service.

During triage, your analysis should answer key questions:

  • Is this vulnerability present in production workloads?

  • Does it impact customer-facing services or internal-only systems?

  • Can it be contained in a single environment or an isolated path?

Jit automates this process through its Context Engine, which builds a live knowledge graph spanning code, pipelines, and cloud resources. It correlates vulnerable packages with runtime and business context to determine whether a vulnerable code path is reachable in production. The platform also tracks every environment where an affected package is deployed and prioritizes findings based on real-world impact rather than static severity alone.

a diagram of a web application


Incorporating Adversarial Exposure Validation (AEV) can further confirm whether those code paths can be exploited under real attack conditions, helping teams focus on issues that genuinely matter in production.

6. Spot Redundant or Outdated Dependencies

Over time, projects often pick up duplicate or outdated versions of the same packages. That adds complexity and widens the attack surface. Tools like npm dedupe or pipdeptree can help uncover overlapping versions so they can be consolidated.

Version drift is another frequent problem in multi-service setups. Teams may use different releases of the same shared library, leaving services out of sync and, in some cases, running outdated code. A dependency graph shows where versions have split and where standardizing can cut both risk and maintenance effort.

7. Automate Graph Analysis in Your CI/CD

Dependency graphs only deliver value when they surface issues inside the development workflow. Embedding analysis into CI/CD ensures developers see problems during pull requests, builds, and ticketing, when fixes are most actionable.

Jit runs as code within GitHub and AWS environments to provide live, automated insights without adding extra tooling overhead. Unlike static reports from tools like Grape or OWASP Dependency-Check, this approach keeps the graph tied to every release cycle and aligns with how teams ship software.

The Challenges in Understanding Dependency Risks

Even with scanners and Software Bills of Materials (SBOM) in place, many teams still struggle to determine which vulnerabilities matter. A flat list of dependencies shows installed packages, but not how packages link together, whether they’re loaded at runtime, or if the vulnerable code can even be reached. Without that context, every CVE looks identical, and prioritization becomes guesswork.

The problem mirrors gaps often seen in data center management: knowing which assets exist isn’t enough without understanding how they interact, where they run, and what’s exposed in production. The same applies to software dependencies; context drives effective triage and remediation.

You can know a package is vulnerable, but it's hard to justify the effort to fix it unless you know if that code path is executed in production. That gap often results in wasted cycles patching issues that aren’t exploitable, while more urgent risks may go unnoticed. Many tools also miss runtime behavior, flagging packages that only exist in test environments or unused code paths.

The disconnect often continues at the workflow level. You can likely ignore vulnerability data if it lives outside the development process (like in a separate dashboard or weekly report). Agentic product security platforms like Jit address this gap by mapping how packages relate, overlaying runtime, business, and compliance context, and bringing that insight directly into the CI pipeline and pull requests. 

Embedding the analysis in the workflow enables faster response and sharper prioritization and keeps security aligned with how engineering teams build and ship software.

From Graphs to Actionable Security

Many tools can generate a list of dependencies. Some can even flag the known vulnerabilities inside them, but that only goes so far. A dependency graph helps you take the next step to understanding how those packages relate to your code and which ones pose a direct risk that you need to mitigate.

The Jit Agentic Product Security Platform ties this all together. It integrates the tooling that builds a dependency graph with Trivy, Semgrep, Nancy, and OSV-Scanner. Then, Jit’s AI agents and Context Engine connect the dependency data to runtime signals and business context to score each issue in terms of actual impact. This results in an apparent, explainable reason for what to fix and when. 

If you're looking for a more straightforward way to manage dependencies within your existing workflow, Jit brings that visibility into your pull requests and builds. Learn more here.