The Wild West of C/C++ Development & What It Means for SBOM Generation

Posted on September 23, 2025
Author: Kelli Schwalm

C and C++ give developers maximum flexibility and performance benefits, which is why they remain the dominant languages for embedded systems, firmware, and high-performance computing. But as any developer who’s worked on a C/C++ project can tell you, (myself included) that flexibility comes with a price. We’re working in a development ecosystem where “anything goes.”

Unlike modern languages with centralized package managers, standardized toolchains, and strict conventions, C/C++ operates like the Wild West. Developers can link in dependencies in a variety of ways, from copy-paste to remote fetches during compilation. When you need to understand what your software is made of for compliance and vulnerability management, things get sticky fast. 

Software Bills of Materials (SBOMs) have become a best practice for managing software supply chain risk as well as a regulatory requirement. But for C/C++, generating a complete and accurate SBOM is notoriously difficult due to the lack of a package manager.

How can developers report on what all is in a C/C++ build? In this article, I look at how C/C++ dependency chaos undermines SBOM accuracy and why a build-time, file-based approach is often the only way to generate trustworthy SBOMs for this legacy language ecosystem.

Listen to the Audio Overview

 

The Core Problem: No Standardized Dependency Management

Modern ecosystems like Python, JavaScript, and Rust have centralized package registries (e.g., PyPI, npm, crates.io) and well-defined manifest files (e.g., requirements.txt, package.json, Cargo.toml). This allows SBOM tools to easily detect:

  • Package name and version
  • Download location and hash
  • Licensing information
  • Dependency relationships

C/C++ has none of this.

Instead, developers rely on informal, ad-hoc methods that leave no clear metadata trail, making it nearly impossible for SBOM tools to determine what’s in your codebase, let alone which components are vulnerable or out of date.

Here are several examples I regularly encounter:

  • Copy-paste integration: Some C/C++ libraries officially recommend copying header files or source files directly into a project. Rather than traditional “packages,” the files become part of your project through direct integration.
  • Vendor/3rdparty directories: In some instances, developers may copy entire codebases into project trees. In these cases, you miss out on provenance and version information and have no update trail.
  • System-level dependencies:  Developers sometimes rely on OS-installed libraries, which are external to a repo and easy to miss.
  • Git Submodules: Git Submodules allow you to embed one repository within another, creating a nested structure of dependencies. While this provides some level of dependency management, it’s notoriously difficult to work with and is often considered “the best bad solution” by developers. The reliance on commit hashes and inconsistent versioning makes it difficult to reliably map dependencies to CVEs. For example, a submodule version could be “1.2.3” or “v1.2.3” or “tag_name,” which means there is no guarantee you are pulling in the correct information.
  • Build system integration: Build systems like Ninja, CMake, and Bazel all have independent ways of creating a notion of dependencies and what needs to be built within a build system. However, it is very open to interpretation of whoever is creating that build system.
  • Dynamic dependency acquisition: Perhaps most challenging are dependencies that don’t exist in your source code at all. Some build systems fetch files directly from the web during compilation using tools like wget or curl. These dependencies are invisible until build time.

These challenges result in manual dependency tracking and versioning difficulties, creating security concerns when dependencies are overlooked or forgotten. If your SBOM generator can’t capture these dependencies, you lose visibility into their codebase composition, making it nearly impossible to accurately identify and address security vulnerabilities.

RunSafe Dependency Methods

Examples from the Wild West

Git Submodules: Popular, but Poorly Versioned

Despite being widely disliked, Git Submodules are one of the most popular dependency management approaches in C/C++. They provide a way to embed external repositories while maintaining some level of version control.

The challenge with submodules is that they frequently reference Git commit hashes rather than semantic versions. Instead of depending on “library v1.2.0,” you’re depending on commit hash ‘a1b2c3d4’ versus the traditional semantic versioning approach. This makes it difficult to map dependencies to Common Platform Enumeration (CPE) identifiers or vulnerability databases that expect product versions, not version control hashes.

Despite this limitation, Git Submodules are probably one of the easiest dependency approaches to work with from an SBOM perspective, which says something about how challenging the alternatives can be.

SBOM Challenge: Git commit hashes don’t map to CVE databases.
Result: Even if a tool includes the submodule in your SBOM, it won’t be able to determine if it’s vulnerable.

OpenCV: The Third-Party Directory Approach

OpenCV, one of the most popular computer vision libraries, exemplifies the challenges of C/C++ dependency management. OpenCV’s build tree includes a “3rdparty/” directory full of other open source libraries. These are copied into the codebase at a specific moment in time, without any version tracking or external linkage. 

Here’s an actual SBOM entry for a file embedded via OpenCV: 

 {
      "name": "predictor_enc.c",
      "authors": [
        {
          "name": "Google Inc"
        }
      ],
      <... cut by me for space in message ...>
      "copyright": "Copyright 2016 Google Inc",
      "properties": [
        {
          "name": "filePath",
          "value": "/plugfest/opencv/3rdparty/libwebp/src/enc/predictor_enc.c"
        }
      ]
    },

Upon investigating the repository, you can confirm the “3rdparty/” dependencies are manually included instead of through git submodules or cmake’s built-in `FetchContent` behavior.. As a result, this open source software would be missed and not reported on as a dependency.

SBOM Challenge: No traceable source, version, or update mechanism.

Result: Traditional SBOM tools miss these entirely because they look for package manifests not files buried in a subdirectory.

Mongoose Web Server: Official Copy-Paste Integration

Mongoose, a popular embedded web server, takes an even more direct approach. Their official documentation instructs users to copy exactly two files—mongoose.c and mongoose.h—anywhere in their codebase.

This approach creates several challenges:

  • Invisible dependencies: The files might be placed in any directory, making them nearly impossible to detect without examining the actual build process
  • Security risk: Updates require manual file replacement, and there’s no automatic way to know when updates are available
  • No provenance: Once copied, these files become indistinguishable from the rest of your codebase

SBOM Challenge: Completely invisible unless you trace every file.
Result: These dependencies blend in with the rest of your source tree. Unless your SBOM tool analyzes each compiled file and scans license headers, you’ll miss them completely.

SQLite: Build-Time Fetching from the Web

SQLite demonstrates the most invisible form of dependency management. Some build systems will fetch SQLite source code directly from the web during compilation using commands like wget or curl. This dependency exists nowhere in your source code as it only appears during the build process.

SBOM Challenge: Only exists at compile time.
Result: A static SBOM generator has no way to know the file was downloaded unless it observes the build process in real time.

Why Build-Time SBOMs Are Essential

These real-world examples illustrate why traditional package-based SBOM generation fails for C/C++. When dependencies can be copied directly into source trees, embedded as Git Submodules, fetched dynamically during builds, or integrated through copy-paste instructions, they are all too easy to miss.

To overcome these challenges, SBOM tools must watch the build process itself and not just analyze the source code or look for packages. A file-based, build-time SBOM generator tracks every file that is compiled, linked, or fetched, and extracts metadata like:

  • Provenance information from license headers and copyright notices in source files. This is information that most open source projects include and that meets minimum SBOM requirements.
  • All included dependencies regardless of how they were integrated into the project.
  • Build-specific dependencies that vary based on compile-time configuration. It will show only what’s actually used rather than what’s available.
  • Dynamically fetched components that don’t exist in source code but are pulled during the build process.

Every file used in a build gets recorded, providing visibility into the actual composition of software. That visibility leads to better vulnerability identification, less software supply chain risk, and compliance with SBOM regulations.

Working With C/C++, Not Against It

Having built a C/C++-specific SBOM generator at RunSafe, I’ve learned that you can’t force C/C++ dependency management into the expected package manager approach. And you shouldn’t try.

C/C++ development is complex and full of legacy habits that defy modern package management. What we need are tools that support the embedded C/C++ world. When we try to force this rich, complex, legacy ecosystem into modern packaging paradigms, we lose a lot of critical information. RunSafe’s build-time, file-based approach aims to capture that missing information.

The Wild West of C/C++ development isn’t going away. But with the right tools and approaches, we can bring order to the chaos without losing the flexibility that makes C/C++ so powerful in the first place.

What unconventional build systems have you encountered in your C/C++ projects? Share your stories. The more we understand the chaos, the better we can support accurate, secure, and compliant SBOM generation.

Guide to Creating and Utilizing SBOMs

Latest Blog Posts

What Is a SBOM? Binary vs Build-Time vs Source Code

What Is a SBOM? Binary vs Build-Time vs Source Code

Get the key takeaways—listen to the audio overview.   Software Bills of Materials (SBOMs) are a detailed inventory of all the components—open source, proprietary, and third-party—used within a software application. SBOMs play a key role in ensuring software...

read more