Skip to content
Blog AI

How AI Triage Reduces PR Noise in Dependency Scanning

Raw CVSS scores generate too much noise. Reachability-aware triage cuts the signal-to-noise ratio from 1:8 to roughly 1:1.5.

Abstract concept of noise filtering: many signals reduced to few clear signals

Dependency scanners report what they find. NVD and OSV contain tens of thousands of CVE records. Any meaningful codebase will have dozens of packages matching those records at any given time. The problem isn't finding vulnerabilities. The problem is figuring out which ones actually require developer attention, and which ones are theoretical risk that doesn't map to anything in your running code.

This is the noise problem. An unfiltered SCA scan on a reasonably sized Node.js application might surface 40 or 50 CVEs. A developer looking at those 50 findings has no efficient way to know that 38 of them are either in code paths never executed in production, or in packages used only in test environments, or in transitive dependencies where the vulnerable function is not called by any package above it in the chain. Treating all 50 equally breaks the triage workflow and, over time, trains developers to ignore the alerts.

CVSS Is a Universal Score, Not a Contextual One

CVSS (Common Vulnerability Scoring System) scores a vulnerability in the abstract: attack vector, attack complexity, privileges required, user interaction, confidentiality/integrity/availability impact. A CVSS 9.0 score means the vulnerability is severe in a context where it can be exploited. It says nothing about whether your code is in that context.

Consider a deserialization vulnerability in a Java library with a CVSS of 9.1. In an application that deserializes attacker-controlled input at a network boundary, that's a genuine critical. In an application that uses the same library only for serializing configuration files written by internal tooling, the attack vector described by the CVE doesn't exist in the deployment. CVSS can't distinguish those cases. It's designed to be universal, not contextual.

EPSS (Exploit Prediction Scoring System) adds one more dimension: the probability that a vulnerability will be actively exploited in the wild within the next 30 days, based on threat intelligence data. EPSS is useful for prioritization at the policy level but still doesn't tell you whether your specific codebase's usage of the vulnerable package is in scope of the exploit.

What Reachability Analysis Does

Reachability analysis attempts to answer the contextual question. Given the entry points of your application and the call graph of your code, does any execution path from an entry point reach the vulnerable function in the CVE-bearing package?

If the answer is no, the CVE is a theoretical risk in your deployment. It may still be worth remediation eventually (static dependency debt is real), but it's not an emergency PR that requires developer attention today.

The practical effect on noise is significant. In a typical mid-size backend service, a raw SCA scan might report 35 CVEs. After reachability filtering, the actionable set shrinks considerably. In our own measurements across a sample of repositories we've processed, the ratio of raw findings to reachability-confirmed actionable findings typically runs somewhere between 5:1 and 10:1, depending on how the project uses its dependencies. Your numbers will vary, but the directional impact of adding reachability context is consistent.

The Limits of Current Reachability Analysis

We want to be clear about where reachability analysis works well and where it doesn't.

Static call graph analysis is more reliable for statically typed, compiled languages (Java, Go, Kotlin) than for dynamic languages (Python, JavaScript, Ruby). Dynamic dispatch, monkey-patching, and runtime code generation can introduce execution paths that static analysis can't see. A JavaScript application that uses require() with a dynamically computed module name, or a Python service that uses importlib dynamically, may have call paths to vulnerable code that a static analyzer cannot trace.

This means reachability context should lower the priority of a finding, not eliminate it. We surface reachability-negative findings in Repohelm, but we distinguish them from reachability-confirmed findings. A developer can choose to defer reachability-negative findings under a scheduled policy window rather than treating them as immediate action items. That's a different disposition than silently discarding them.

There's also the question of indirect reachability. A package may expose a vulnerable code path that isn't called by your application directly but is callable through a documented API by users of your library. If you're building an SDK, your reachability analysis needs to account for that outward-facing call surface, not just your internal call graph.

How the Triage Layer Works in Repohelm

Our triage pipeline runs in multiple passes after the initial SCA scan. First pass: basic filtering by scope (dev-only dependencies get a different policy track than production runtime dependencies). Second pass: CVSS and EPSS scoring to bucket severity. Third pass: reachability analysis where the ecosystem supports static call graph tracing. Fourth pass: context synthesis, which produces a triage classification for each finding.

The triage classification determines the PR strategy. Reachability-confirmed criticals open an immediate fix PR with a blocking status check. Reachability-confirmed highs open a fix PR without a blocking check by default, but this is configurable. Reachability-negative findings of any CVSS score get logged and included in a weekly digest PR rather than individual alerts.

The digest PR is intentional. It keeps the finding visible without generating individual PR notifications that train developers to dismiss alerts. Weekly digest PRs have a review cadence attached to them, so reachability-negative debt doesn't accumulate indefinitely.

What This Changes for Developers

The direct effect is fewer PRs from Repohelm compared to a raw-scan-driven system. But the more important effect is that the PRs that do appear are higher quality. When a developer sees a Repohelm fix PR, the description includes whether the finding was reachability-confirmed, what the call chain looks like, and why the CVSS and EPSS scores place it in the priority tier it's in.

That context changes how developers process the PR. Instead of a generic "bump dependency from 1.4.3 to 1.4.4 because CVE-XXXX-YYYY," the PR says "this CVE affects a code path your HTTP handler calls on authenticated requests; the exploit requires network access; here's the specific function." That's an actionable brief, not a scan output.

Over time, this changes how security is perceived inside an engineering team. Alerts that are rarely wrong start getting taken seriously. Alerts that are frequently wrong (false positives, theoretical risks, scope mismatches) train developers to build psychological filters that become hard to override when a real critical lands. Triage quality is a long-term investment in developer trust, not just a short-term noise reduction metric.

Ready to get your dependency risk under control?

Connect GitHub, get a full audit, and start remediating in minutes.