C/C++ Is Where Vulnerability Programs Go to Guess
Most security tools skip C and C++ entirely, or pretend they don't. Read on to learn what it actually takes to see what's there.
Join the DZone community and get the full member experience.
Join For FreeWalk into most AppSec reviews, and you'll find a familiar pattern. Python dependencies: fully inventoried. npm packages: tracked and patched. C and C++ code powering the operating system, the embedded firmware, or the performance-critical core of the product? A blank space where the risk assessment should be.
This is not a tooling gap that's easy to paper over. C and C++ do have package managers, but adoption is still ramping, and they are dependent on the operating system and build environment. Libraries get vendored directly into repositories. Static linking buries third-party components inside compiled binaries with no labels and often no version information left to read. Build logic lives across Conan, CMake files, Bazel configs, Makefiles, Yocto recipes, and BitBake layers, and no two projects use the same way.
There is a compounding problem that rarely gets named directly: which libraries are present is often not determined until build time, or until the container or environment is assembled. There is no static manifest to read. The dependency graph is only fully real once the software is built, and by then, most tools have already finished their analysis and moved on.
Most tools handle this by doing their best with whatever they can find and returning results that look complete. The incompleteness tends to be silent. You get a list of components with no indication of how many were missed, and in many cases, a generous helping of components you don't actually have. When tools can't determine dependencies precisely, they guess, and those guesses show up in your inventory as findings that engineers spend time investigating before discovering the library in question was never there.
The Problem Isn't Complexity. It's Assumptions.
Most software composition analysis tools were designed around a reasonable assumption: that dependencies are declared somewhere machine-readable. In Python, that's a requirements file. In JavaScript, a package.json. In C and C++, that assumption fails immediately.
Build systems in the C and C++ ecosystems are diverse and project-specific. CMake, Bazel, Make, Yocto, BitBake, and custom shell scripts all encode dependency logic differently, and there is no common interface for tooling to parse across them. Static linking buries third-party components inside compiled binaries, stripping the version information that scanners rely on. Libraries get copied into codebases directly, with no record of their origin and no metadata attached.
Enterprise package managers like Conan exist and are improving, but they aren't a shortcut to solving this. Retrofitting an existing C/C++ project to use Conan is not a weekend task. It is an architectural undertaking that touches build infrastructure, CI pipelines, and dependency resolution logic that may have accumulated over the years. The migration cost is often far higher than the security team proposing it realizes, and the security case alone rarely wins the budget argument. Any security program that assumes package manager adoption is around the corner is building on a foundation that isn't there yet.
The practical consequence is that security teams making risk decisions about their most critical software, the code that runs the kernel, the device, or the real-time system, are doing so with fundamental gaps in their data. A CVE drops for a library shipped in three products. Without accurate visibility into which builds include which version of that library, triage becomes guesswork. And because dependencies are often resolved at build time or inherited from a base container or environment, the answer to "do we use this library?" is not in a manifest. It requires someone to trace back through build logs, environment configurations, and image layers by hand. That work falls on engineers, not tooling.
Start With What Shipped, Not What Was Declared
The correct approach to this problem does not start with a package manager. It starts with a different question: what is actually present in this build, this artifact, this container image?
This reframing matters because it accepts the reality of how C/C++ projects are actually built. Dependencies are resolved at build time. Libraries are pulled from the environment. Components are embedded in base images. The dependency graph that matters is the one that shipped, not the one that was planned, and the only way to recover it is to work backwards from the output rather than forward from what was declared.
In practice, this means parsing build system outputs across all major toolchains rather than expecting a common format. It means analyzing binaries and system images alongside source trees, not instead of them. For the cases where a dependency is genuinely obscured, such as a library vendored without documentation or a component embedded deep in a third-party SDK, it means applying language-aware inference to surface what rule-based tools miss.
None of this is simple. But the complexity is an argument for investing in it, not for treating C/C++ as a known unknown and moving on.
What Changes When You Can Actually See
The outcome of getting this right is not just a more accurate inventory. It is the CVE response measured in hours rather than days. It is compliance artifacts that reflect what is actually in production rather than what the tooling happened to find. It is AppSec teams that can answer "are we affected?" with confidence instead of a best guess followed by a week of manual investigation.
C and C++ power a disproportionate share of the software that runs the world: operating systems, embedded devices, automotive systems, industrial controls, and the performance-critical cores of applications that can't afford to be wrong. Security programs that treat this code as too hard to analyze are not avoiding complexity. They are accepting risk they cannot quantify, in the software they can least afford to get wrong.
Opinions expressed by DZone contributors are their own.
Comments