by the Open Source Security Foundation (OpenSSF) Best Practices Working Group, 2025-04-24
This document guides component creators and component users to simplify updates and help avoid backward incompatibility problems when updating. A key technique is for component developers to avoid creating backward incompatibilities wherever practical. Backward-incompatible changes to an application programmer interface (API) often lead to unaddressed security vulnerabilities.
Modern software systems are mostly reused software. Studies show that, for example, the average percentage of open source software (OSS) in software applications is somewhere between 70% [Black Duck 2025] and 90% [Sonatype 2024]. These numbers don’t even include reused closed source software. These reused components often reuse other components and can be many layers deep. Manually handling this scale can lead to many failures, so it’s important to automate dependency management (e.g., through package managers). In theory, if a reused component has a vulnerability, users should simply update to a newer version that fixes that vulnerability once the newer version is available.
Unfortunately, sometimes newer versions of components fail to support older interfaces, or change their functionality in backward-incompatible ways. This leads to many or most of their users being stuck on older versions of the software for long periods or perhaps forever. One study found that 90% of analyzed codebases contain OSS components that are more than 10 versions behind [Black Duck 2025]. When a vulnerability is found—and eventually one is—the vulnerability is fixed in the “current” component version, but many users of that software will remain vulnerable because it’s impractical for them to update to the current version. Some Linux distributions backport security fixes to older versions, making updates easier to handle, but this also keeps users in old versions no longer supported by the original project. Eventually this support ends and the update effort is typically enormous. In addition, system failures that are caused by updates train component users that component developers cannot be trusted to develop backward-compatible updates. These terrible experiences will lead component users to believe that upgrades are dangerous, so they won’t update when they should update.
In short: backward-incompatible changes often lead to unaddressed security vulnerabilities.
Historically fewer software components were reused, and there were fewer layers of indirect use. When only one component is being reused by a program, handling a small backward-incompatible change is relatively simple. However, at scale, such changes become a massive problem. Modern software development involves much larger scales. For example, if you start creating an application using React (a widely used system), you begin with over two thousand packages before adding any functionality. [Singh2022]
Backward-incompatible changes are also increasingly problematic because most software components are used indirectly. When there are many layers of dependencies, it takes time for each layer’s updates to trickle up, introducing a sort of “speed of light” rate limit for updating software. Any delay in updating any intermediate layer impedes updates of all transitive users. For example, [Wetter2021] found in response to the Log4Shell vulnerability that “most artifacts that depend on log4j do so indirectly. The deeper the vulnerability is in a dependency chain, the more steps are required for it to be fixed. […] For greater than 80% of the packages, the vulnerability is more than one level deep, with a majority affected five levels down (and some as many as nine levels down).”
Backward-incompatible changes are even harder to deal with today because of the larger scale of software today. Custom software is often larger, and often depends on many other components. If a new interface must eventually be used, it may be possible to slowly change over time different files and components through a series of releases, though it can be costly and time-consuming. Demanding that “everything change at once” is far more difficult. For example, Python 3.0 was released on 2008-12-03. This was a backwards-incompatible release; transitioning from Python2 to Python3 required all code and libraries to simultaneously change. This transition was notoriously difficult and slow, with even its creator finding it difficult in his organization. Python2 support was sunset on 2020-01-01, yet the Python Developers Survey 2022 found that 14 years after the Python3 release, 7% of Python users overall still used the older Python2, with notable uses in data analysis (29%), web development (19%), and DevOps (23%). As of 2025-05-07, 17 years later, 19.1% of websites using Python use Python2. Backward-incompatible changes are difficult to manage.
Developers have created mechanisms to deal with backward incompatibility, but these often create larger problems later. A developer may clone some code; in cloning, code is copied into the project. Unfortunately, these copies may include vulnerabilities, and since their origin is no longer automatically tracked, those vulnerabilities are hidden by the development process and are no longer automatically updated. An alternative is shading, a “variant of cloning where entire packages are cloned and renamed.” This may be done at build time (aka “b-shading”) and some ecosystems have tools specifically to support b-shading (e.g., the Maven shade plugin). While b-shading solves an immediate problem and is trackable by tools, the approach also introduces longer-term risks as it tends to endlessly defer necessary updates. Other kinds of shading are used as well [Dietrich2023]. In all cases, alternatives create risks when compared to simply updating a given component to its current version.
In extreme cases, such as the Log4Shell vulnerability, specialized programs were created to directly hotpatch programs to perform updates [Nalley2021]. This extreme approach is not reasonable to apply in “normal” circumstances and risks causing many additional problems.
Some component developers claim keeping older interfaces working “can’t” be done. Yet it’s usually quite possible. Even large projects have managed to maintain backward-compatible interfaces for decades, even within current branches of a codebase. An example is the Linux kernel, whose developers sometimes call this “don’t break userspace”. As Linus Torvalds (leader of the Linux kernel project) noted in 2005, “We care about user-space [external] interfaces to an insane degree. We go to extreme lengths to maintain even badly designed or unintentional interfaces. Breaking user programs simply isn’t acceptable… We know that people use old binaries for years and years and that making a new release doesn’t mean that you can just throw that out. You can trust us. … I’m not talking about never obsoleting bad interfaces at all. I’m talking about the unnecessary breakage that comes from changes that simply aren’t needed, and that isn’t given proper heads-up for.” This simple policy has enabled the Linux kernel developers to build trust with their users.
Even in rare cases where a backward-incompatible change must be done, the damage caused by backward-incompatible interfaces can usually be limited.
Consider the following whenever making changes that might change the component’s external interfaces:
Consider the following when developing and maintaining software with dependencies:
We encourage you to check out these related materials:
[BlackDuck2025] Black Duck, 2025, Open Source Security & Risk Analysis Report, https://www.blackduck.com/resources/analyst-reports/open-source-security-risk-analysis.html
[Delorie2019] Delorie, DJ, 2019-08-01, “How the GNU C Library handles backward compatibility”, https://developers.redhat.com/blog/2019/08/01/how-the-gnu-c-library-handles-backward-compatibility
[Dietrich2023] Dietrich, Jens, Shawn Rasheed, Alexander Jordan, and Tim White, 2023, “On the Security Blind Spots of Software Composition Analysis”, https://arxiv.org/abs/2306.05534
[Nalley2021] Nalley, David and Volker Simonis, 2021-12-12, “Hotpatch for Apache Log4j”, https://aws.amazon.com/blogs/opensource/hotpatch-for-apache-log4j/
[Singh2022] Singh, Rajdeep, 2022, Find How Many Packages We Need to Run a React’ Hello World’ App, https://medium.com/frontendweb/find-how-many-packages-we-need-to-run-a-react-hello-world-app-695fbb755af7
[Sonatype2024] Sonatype, 2024, https://www.sonatype.com/state-of-the-software-supply-chain/introduction
[Wetter2021] Wetter, James, and Nicky Ringland, 2021-12-17, Open Source Insights Team “Understanding the Impact of Apache Log4j Vulnerability”, https://security.googleblog.com/2021/12/understanding-impact-of-apache-log4j.html
This document was developed by the OpenSSF Best Practices Working Group (WG). The initial draft was developed by David A. Wheeler. The need for it was identified during the OpenSSF Policy Summit DC 2025 of 2025-03-04 at the OSS Best Practices Breakout Group. We give grateful thanks to the contributions (sorted alphabetically) of Avishay Balter, Chris de Almeida, David A. Wheeler, Georg Kunz, Matt Wilson, and Salve J. Nilsen.