You ship a small application. A few hundred lines of code, a handful of dependencies, running in a container on your cloud platform. Your security team runs a scanner against it and comes back with 347 CVEs. Some critical. Some in KEV.
Your engineering team looks at the report and says: we didn't write any of this. Where is it all coming from?
Your security team looks at the same report multiplied across every workload in the fleet and says: we have 12,000 findings. We know most of these aren't real problems. But we can't explain why, and we can't ignore them.
The answer is structural. CVEs describe vulnerabilities in software as authored: the full, general-purpose codebase as the developer wrote it, covering every feature, every platform, every configuration option. Your production workload consumes a narrow slice of that software: one platform, one configuration, one set of features, behind one specific network topology.
The distance between those two things is where all the noise comes from. Seven structural forces create that distance. Understanding them is the difference between drowning in findings and knowing which ones matter.
Nobody installs just the functions they need. Software arrives in bundles.
npm install express brings in 60+ packages. Your application calls maybe 10. The rest are transitive dependencies sitting in your node_modules because something in the tree declared them. pip install flask pulls in Werkzeug, Jinja2, MarkupSafe, itsdangerous, click, blinker. Your API uses Flask's routing. A CVE in Jinja2's sandboxed template execution doesn't apply if you never render untrusted templates.
Your base image does the same thing at the OS level. FROM python:3.12 ships with libxml2, libffi, libgmp, sqlite3, openssl, curl, perl. Your application needs the Python runtime and maybe libpq for Postgres. Everything else is along for the ride.
Why this happens: Package managers optimize for compatibility and ease of use, not minimal surface area. Distro maintainers ship broad images because they can't predict what each user needs. Container base image authors choose general-purpose bases (debian:bookworm, ubuntu:22.04) because slim alternatives require more effort and break more things.
The result: every workload carries a large surface area of code it never executes. CVEs filed against that code show up in your report even though your application never touches it.
When a researcher finds a bug in OpenSSL's DTLS implementation, the CVE says "OpenSSL versions X through Y are vulnerable." It describes the bug at maximum potency: assuming DTLS is enabled, network-accessible, and processing attacker-controlled input.
This is correct from the researcher's perspective. They found a real bug. It really can be exploited, if the right conditions hold. But most OpenSSL deployments don't use DTLS at all. They use TLS for HTTPS. The DTLS code is compiled in because the distro compiled OpenSSL with default flags, but it's never called.
Why this happens: CVEs are filed against the upstream project, not against specific deployments. The NVD has no mechanism to encode "this only matters if you compiled with enable-dtls and have a DTLS listener." CVSS Attack Complexity tries to capture this, but it's a single Low/High value. It can't represent "requires DTLS to be enabled AND attacker must reach the DTLS port AND must send a crafted handshake."
The result: every CVE describes the vulnerability at its theoretical maximum. Your deployment doesn't run the theoretical maximum. The gap between what the CVE assumes and what your environment provides is invisible to the scoring system.
Modern cloud infrastructure is deeply layered, and each layer adds software that the layer above doesn't choose or control:
Your application code <- you wrote this
|
Application dependencies <- your package manager chose these
|
Language runtime <- you picked the version, not the internals
|
OS packages in container <- base image author chose these
|
Host OS / kernel <- cloud provider or platform team chose this
|
Hypervisor / hardware <- cloud provider chose this
A CVE against a kernel module affects the host kernel layer. But your container workload doesn't load that module, doesn't use the syscalls that trigger it, and may not even have the capability to interact with it due to seccomp profiles, namespace isolation, or dropped capabilities. The vulnerability exists in a layer that's structurally beneath your workload and inaccessible from it.
Why this happens: Layered abstraction is the whole point of cloud infrastructure. You don't choose your kernel, your hypervisor, or your host OS packages. But CVEs are filed against those components, and some scanners report them against your resource because the version string matches the affected range.
General-purpose software ships with many features. Production deployments enable a few.
Nginx has modules for WebDAV, XSLT transforms, mail proxying, gRPC, Perl scripting. A typical deployment uses http_ssl and proxy_pass. A CVE in the mail proxy module doesn't apply if the module isn't loaded.
PostgreSQL supports dozens of extensions. A production instance runs 3 or 4. A CVE in pg_trgm is irrelevant if the extension isn't loaded.
OpenSSH can be configured for password auth, key auth, certificate auth, Kerberos. A CVE that requires password authentication doesn't apply when only key auth is enabled.
The Linux kernel has roughly 15,000 configuration options. A production kernel is built with maybe 2,000 enabled. CVEs in disabled subsystems: Bluetooth on a headless server, InfiniBand on a machine with no RDMA hardware: are noise.
Why this happens: Software authors build for the general case. Configuration narrows it to the specific case. CVEs are filed against the general case. The NVD's CPE system has no mechanism to encode "only applies when configured with X." So every CVE in the software shows up in your report, regardless of whether the affected feature is active in your deployment.
A CVE with CVSS Attack Vector AV:N means "exploitable from the network." It doesn't mean "exploitable from the internet." In your deployment:
The CVE assumes network reachability because it describes the software in isolation. Your deployment puts the software behind layers of network controls. The vulnerability is real in the abstract. The attack vector doesn't exist in your environment.
Why this happens: Network topology is a deployment-time decision, not a software-authorship decision. The CVE describes the software. The network topology is infrastructure. They're in completely different domains. No CVE database can know where you deployed the software, what's in front of it, or what routes exist to reach it.
Red Hat, Debian, Ubuntu, and SUSE backport security fixes into older versions instead of upgrading to the latest upstream release. Red Hat's openssl-3.0.7-24.el9 may contain the fix for a CVE that upstream fixed in 3.1.5. The version string says "3.0.7" which looks vulnerable. The code is patched.
Why this happens: Distros prioritize stability. Upgrading to a new major version can break APIs, change behavior, and force application changes. Backporting is the compromise: fix the bug, keep the version. But the NVD records the upstream affected version range, and version-matching tools compare your installed version against that range. It matches. The finding is reported. It's a false positive caused by a version string that doesn't reflect the actual code running on the resource.
This is a well-known problem. Some scanners account for it better than others. But it's a persistent source of noise, especially in enterprise Linux environments where backporting is standard practice.
Multi-stage Docker builds compile your application in one stage and copy the binary to a clean final image. The build stage has compilers, dev tools, test frameworks, build dependencies. The final stage has only the runtime.
But some scanners inspect all layers of the image, not just the final filesystem. Build-time dependencies show up as findings even though they don't exist in the running container. gcc, make, test frameworks, development libraries: all flagged, none present at runtime.
Similarly, devDependencies in package.json or entries in requirements-dev.txt get flagged if the scanner examines the dependency manifest without checking what's actually installed in the production image.
Why this happens: The container image format preserves layer history. Scanners that inspect layers rather than the running filesystem pick up artifacts that were part of the build process but explicitly discarded before the image shipped. The code isn't in your runtime. It can't be exploited.
These seven forces aren't a data quality problem the NVD could solve with better records. They're a category mismatch.
The NVD catalogs bugs in code. Exploitability depends on code, configuration, deployment, network topology, and runtime state. The NVD can describe the first dimension. The other four are determined at deployment time, are different for every organization, and change continuously. No static database can capture them.
That's why the gap exists and why it's persistent. The information needed to close it doesn't exist until the software is deployed and running in a specific environment.
Industry data consistently shows 80-90% of scanner findings aren't actionable in a specific environment. Not because scanners are wrong about the package being present. Because one or more of the seven forces means the conditions for exploitation aren't met in your deployment.
Understanding the seven forces doesn't make the findings go away. But it reframes the problem from "we have 12,000 vulnerabilities" to "we have 12,000 version matches, and we need to determine which ones are actually exploitable given our configuration, our deployment, and our network."
That's a different problem with a different solution. The question isn't how to patch faster. It's how to figure out which of these 12,000 actually need patching at all.