In 2019, a Microsoft Security Response Center engineer disclosed a stat that has anchored every memory-safety argument since: roughly 70% of the CVEs Microsoft assigns every year trace back to memory-safety bugs — buffer overflows, use-after-free, double-free — and Google engineers reported a similar share in Chromium's own vulnerability history. In December 2023, CISA, the NSA, the FBI, and Five Eyes partners formalized that concern in "The Case for Memory Safe Roadmaps," stating that roughly two-thirds of vulnerabilities in memory-unsafe languages relate to memory-handling errors, and pushing vendors to publish concrete migration plans. NSA and CISA followed up in June 2025 with further guidance reinforcing memory-safe language adoption. None of this means C and C++ are going away — decades of embedded systems, kernels, and performance-critical libraries run on them — but it does mean teams still shipping C/C++ need CI pipelines that catch these bugs before merge, not after a CVE is filed. That means combining static analyzers, sanitizer-instrumented test runs, and dependency/composition scanning into one pipeline, since no single tool covers all three failure modes. This post walks through what belongs in that pipeline and where the limits are.
Which static analyzers actually belong in a C/C++ CI pipeline?
Three open-source tools cover most of the useful ground: Clang Static Analyzer, Cppcheck, and clang-tidy. Clang Static Analyzer performs path-sensitive symbolic execution across your actual build — it traces variables through branches to flag null-pointer dereferences and use-after-free paths that a purely lexical scanner would miss, and it plugs into scan-build so it runs your existing Makefile or CMake invocation unmodified. Cppcheck takes a different approach: it doesn't need a full, successful build, so it's cheap to run on every pull request as a fast first pass for out-of-bounds array access, uninitialized variables, and resource leaks. clang-tidy sits closer to a linter, applying named checks (bugprone-*, cert-*, clang-analyzer-*) that map to specific CWE categories and can auto-fix simple violations in place. The practical pattern most teams converge on: Cppcheck on every commit for fast feedback, Clang Static Analyzer as a heavier gated job on pull requests or nightly builds, and clang-tidy wired into the same job to enforce style and safety rules together.
Why aren't static analyzers enough on their own?
Static analyzers reason about code without running it, so they routinely miss bugs that only manifest at runtime under specific memory layouts or input sequences — which is exactly the gap sanitizers and fuzzers close. AddressSanitizer (ASan) instruments a binary to catch heap and stack buffer overflows and use-after-free the instant they happen during a test run, at roughly 2x runtime overhead, far cheaper than tracking the same bug down from a production crash dump. UndefinedBehaviorSanitizer (UBSan) catches signed-integer overflow, misaligned pointer access, and other undefined-behavior classes the C/C++ standard leaves unspecified but compilers are free to miscompile. Google's OSS-Fuzz project, running since 2016, pairs libFuzzer with ASan/UBSan to continuously fuzz hundreds of open-source C/C++ projects and has been credited with surfacing a large and well-documented volume of memory-safety bugs across that corpus — evidence that dynamic, sanitizer-driven testing finds classes of bugs static analysis structurally cannot. A CI pipeline that runs its test suite once under ASan+UBSan on every PR gets meaningful coverage without needing a dedicated fuzzing infrastructure.
How does dependency scanning fit into a C/C++ pipeline when there's no single package manager?
C/C++ dependency scanning is harder than in ecosystems with one dominant package manager because C/C++ code arrives through vendored source trees, git submodules, system packages, Conan, vcpkg, or bare Makefiles pulling tarballs — so composition analysis has to work from what's actually present in the build output and container layers, not just a manifest file. In practice this means generating a Software Bill of Materials (SBOM) from the built binary or the final container image — matching linked libraries and embedded version strings against known-vulnerable versions — rather than relying solely on a lockfile that may not exist. This matters because CVE-2021-3712, a read buffer overrun in how OpenSSL's ASN.1 string-printing functions assume NUL-terminated data, affected any C/C++ project statically linking a vulnerable OpenSSL 1.1.1/1.0.2 build regardless of how that copy was vendored in. A pipeline that only scans Cargo.lock-style manifests will miss a vendored OpenSSL entirely; one that scans the built artifact's linked libraries and embedded version strings will not.
What can't static analysis or SCA catch, and why does that matter?
Static analysis and composition scanning both assume the source code itself is trustworthy, which is precisely the assumption CVE-2024-3094 broke. In that incident, a contributor calling themselves "Jia Tan" spent roughly two years building maintainer trust on the xz-utils compression library before slipping an obfuscated backdoor into liblzma's build scripts and test artifacts — a backdoor that gave an attacker holding a specific private key the ability to execute code remotely via OpenSSH on affected systems, and one that carried a maximum CVSS score of 10.0. It was not caught by any CI security scanner at any point in that two-year window; developer Andres Freund discovered it independently on March 29, 2024, after noticing SSH logins were consuming unusually high CPU and taking longer than expected. Datadog Security Labs' subsequent writeup on the incident is worth reading for how the payload was staged. The lesson for a CI pipeline isn't that scanning is pointless — it's that static analysis and SCA catch known-pattern bugs and known-CVE dependencies, not a trusted maintainer inserting malicious logic on purpose, which is why build provenance, reproducible builds, and reviewing dependency updates (not just auto-merging them) remain necessary alongside scanning.
How should a team sequence these checks without breaking every build?
Sequence checks from fastest-and-loosest to slowest-and-strictest, gating merges only on the checks mature enough to have low false-positive rates. A practical order: Cppcheck and clang-tidy on every push as fast, non-blocking feedback; a full Clang Static Analyzer pass and an ASan+UBSan test run as a required check on pull requests before merge; and SBOM generation plus dependency-CVE matching as a separate job that can run asynchronously and open a ticket rather than block the build, since dependency databases update on their own schedule independent of your commits. Teams that make every new check merge-blocking from day one tend to see it disabled within a sprint once it produces a few noisy findings; teams that phase in enforcement — report-only for a few weeks, then blocking — get durable adoption. It's also worth deciding upfront what severity threshold blocks a merge (e.g., only Clang Static Analyzer's high-confidence checkers, or only CVSS 7.0+ dependency findings), since C/C++ static analyzers in particular are known for higher false-positive rates than their JVM or Python equivalents on unfamiliar codebases.
How does Safeguard fit into a C/C++ pipeline today?
Safeguard's software composition analysis and SBOM generation apply directly to C/C++ projects the same way they do to any other ecosystem: dependencies pulled in via a package manager, vendored into a repository, or baked into a container image are resolved, matched against known CVEs, and rolled into a queryable CycloneDX SBOM alongside container-layer scanning that inspects linked libraries and binaries inside built images. Dedicated first-party SAST for C/C++ source is not yet part of Safeguard's phase-1 static analysis coverage, which today spans JavaScript/TypeScript, Python, and Java — teams should keep Clang Static Analyzer, Cppcheck, and sanitizer runs in their CI for C/C++-specific memory-safety detection, and lean on Safeguard for the dependency, SBOM, and container-layer risk that sits alongside that source code. As Safeguard's static analysis coverage expands, C/C++ is a natural next target given how much of the industry's memory-safety CVE volume still originates there.