You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If it's good advice, the guide should say so directly. The reader will
choose what advice they accept and don't, so there's no need to soften
the language.
Signed-off-by: Ben Cotton <[email protected]>
Copy file name to clipboardExpand all lines: docs/Simplifying-Software-Component-Updates.md
+9-9Lines changed: 9 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -29,21 +29,21 @@ Even in rare cases where a backward-incompatible change **must** be done, the da
29
29
Consider the following whenever making changes that might change the component’s external interfaces:
30
30
31
31
1.**Ensure users have an automated update mechanism.** For example, ensure that the component is in at least one repository and can be downloaded via a package manager. Note that the [Cyber Resilience Act (CRA) Annex I(I)(2)(c)](https://eur-lex.europa.eu/eli/reg/2024/2847/oj#anx_I) requires that products “ensure that vulnerabilities can be addressed through security updates, including, where applicable, through automatic security updates that are installed within an appropriate timeframe enabled as a default setting, with a clear and easy-to-use opt-out mechanism, through the notification of available updates to users, and the option to temporarily postpone them”.
32
-
2.**Consider maintaining a stable release branch that only receives security updates and no new features**. Such branches are often called long-term support (LTS) branches and include only backported vulnerability fixes. LTS branches enable users to rapidly update to fix vulnerabilities. Approach the users of your project to support such LTS branches. It’s still important to support older APIs where practical on newer branches, so that users can more easily move to a current branch. If there aren’t enough resources to maintain stable branches, it’s more important to have a single branch where later versions continue to support earlier APIs.
32
+
2.**Maintain a stable release branch that only receives security updates and no new features**. Such branches are often called long-term support (LTS) branches and include only backported vulnerability fixes. LTS branches enable users to rapidly update to fix vulnerabilities. Approach the users of your project to support such LTS branches. It’s still important to support older APIs where practical on newer branches, so that users can more easily move to a current branch. If there aren’t enough resources to maintain stable branches, it’s more important to have a single branch where later versions continue to support earlier APIs.
33
33
3.**Avoid making backward-incompatible changes to existing external interfaces to the extent feasible**. You can create **new** interfaces, but do your best to keep old ones working. E.g., if you use semantic versioning (SemVer), do your best to **never** change the major version number once it becomes “1”.
34
34
4.**Mark an interface “deprecated” if you don’t want more users of it, but where practical, don’t remove that interface.** In general, do not **remove** a deprecated interface unless using it is by **definition** a security vulnerability. The term “deprecated” should **not** be interpreted as “will be removed in the future” but as “we recommend using something else instead”. We encourage projects to **allow the continued use of** deprecated interfaces where practical.
35
35
5.**Don’t change an API in ways that *encourage* errors**. For example, don’t keep an API name but swap the order of parameters. Instead, create a new name with the order swapped while leaving the old interface in place.
36
-
6.**If you want to create a higher-level/more abstract API that is easier to use, consider keeping the older API as the “lower-level” API**. Consider implementing the “higher-level” API in terms of the “lower-level API”.
37
-
7.**If you have a new API approach at the same level of abstraction, consider creating the new API, then implement the old API using the new API**. This provides the old functionality, and it also helps verify that the new API didn’t lose functionality. If you decide to later remove the old API, perhaps many years later, it will be easier to do so.
36
+
6.**If you want to create a higher-level/more abstract API that is easier to use, keep the older API as the “lower-level” API**. Consider implementing the “higher-level” API in terms of the “lower-level API”.
37
+
7.**If you have a new API approach at the same level of abstraction, create the new API, then implement the old API using the new API**. This provides the old functionality, and it also helps verify that the new API didn’t lose functionality. If you decide to later remove the old API, perhaps many years later, it will be easier to do so.
38
38
8.**If you found a better name, create the new name, but ensure the old name will continue to work**. You might choose to implement the old name by calling the new name.
39
39
9.**Where practical, change an API in backward-compatible ways**. In many ecosystems you can add **optional** parameters without changing an interface, as long as the new parameters’ defaults cause the original behavior. This makes it easier to add functionality without breaking an existing API.
40
40
10.**Where practical, make it easy to gradually transition to the new API**. Instead of requiring all code to switch to the new API, make it possible to gradually update different files over time.
41
-
11.**Consider using a versioned API, e.g., “v1” and “v2”**. This is common in REST interfaces. You can then create a new version of the API, while continuing to support the older API, because it’s easy to see which interface was intended.
42
-
12.**In system packages and libraries, consider using “compat symbols” which lets the linker select from one of many implementations of a function**. These can be used to provide multiple versions of an interface, including backward-compatible ones. [[Delorie2019](https://developers.redhat.com/blog/2019/08/01/how-the-gnu-c-library-handles-backward-compatibility)]
41
+
11.**Use a versioned API, e.g., “v1” and “v2”**. This is common in REST interfaces. You can then create a new version of the API, while continuing to support the older API, because it’s easy to see which interface was intended.
42
+
12.**In system packages and libraries, use “compat symbols” which lets the linker select from one of many implementations of a function**. These can be used to provide multiple versions of an interface, including backward-compatible ones. [[Delorie2019](https://developers.redhat.com/blog/2019/08/01/how-the-gnu-c-library-handles-backward-compatibility)]
43
43
13.**If you have internal interfaces you do *not* intend to keep stable, *clearly* document this where potential users can see it**. An example is the Linux kernel, which has a stable external interface to userspace applications but clearly documents that its internal kernel module interfaces are **not** stable.
44
44
14.**Ensure you have an automated test suite that tests older and newer APIs**.
45
45
15.**Attempt to anticipate likely future changes to an API, so that they will have fewer impacts on users**. For example, you might add an extra parameter like “flags” or “configuration” that enables adding capabilities later without changing the API for existing users.
46
-
16.**Consider supporting subsetted functionality**. Code that can’t be executed can’t be exploited and functional changes won’t impact users. Therefore, consider making it easy to enable only a subset of the component’s functionality. Mechanisms for doing this include splitting functions into components (so users can choose a subset of them), providing a plug-in architecture (so users can choose their plug-ins), or a configuration system that allows users to enable or disable specific functions.
46
+
16.**Support subsetted functionality**. Code that can’t be executed can’t be exploited and functional changes won’t impact users. Therefore, consider making it easy to enable only a subset of the component’s functionality. Mechanisms for doing this include splitting functions into components (so users can choose a subset of them), providing a plug-in architecture (so users can choose their plug-ins), or a configuration system that allows users to enable or disable specific functions.
47
47
48
48
## Component Users
49
49
@@ -53,9 +53,9 @@ Consider the following whenever making changes that might change the component
53
53
4.**If your ecosystem allows it, applications (but not libraries) should use lockfiles where available**. A lockfile is a text file identifying the specific version of the dependencies used by a project so they can be precisely reproduced. Ideally a lockfile includes cryptographic hashes (so later tampering can be detected and prevented). Be as specific and complete as the ecosystem supports.
54
54
5.**Enable tools to automatically detect when a vulnerable component is being used**. E.g., in GitHub, enable Dependabot.
55
55
6.**Implement automated tests and run them every time dependencies change**. These automated tests should check both functionality and security. Make sure the tests pass in all situations you care about (e.g., different platforms or different customer configurations). Running tests after updating dependencies provides evidence that the updates won’t break functionality or security.
56
-
7.**For system applications and libraries, consider building on the oldest platform you want to support**. For more information see [[Delorie2019](https://developers.redhat.com/blog/2019/08/01/how-the-gnu-c-library-handles-backward-compatibility)].
57
-
8.**If upgrading to a newer version of a component is impractical, consider backporting vulnerability fixes to older versions**. Such backports can be done downstream or in a stable (LTS) branch maintained by the project. If the project does not maintain a stable release branch, but that component is so critically important to a user to backport vulnerability fixes downstream, consider contributing those backports upstream and/or offering support in maintaining such a stable release branch.
58
-
9.**Consider sourcing open source components from (distribution) providers which maintain stable releases of older components**.
56
+
7.**For system applications and libraries, build on the oldest platform you want to support**. For more information see [[Delorie2019](https://developers.redhat.com/blog/2019/08/01/how-the-gnu-c-library-handles-backward-compatibility)].
57
+
8.**If upgrading to a newer version of a component is impractical, backport vulnerability fixes to older versions**. Such backports can be done downstream or in a stable (LTS) branch maintained by the project. If the project does not maintain a stable release branch, but that component is so critically important to a user to backport vulnerability fixes downstream, consider contributing those backports upstream and/or offering support in maintaining such a stable release branch.
58
+
9.**Favor using open source components from (distribution) providers which maintain stable releases of older components**.
59
59
10.**Strive to avoid maintaining downstream modifications of open source code**. Downstream modifications may be pursued by users for various different reasons (e.g. business or use case specific features). However, maintaining such downstream modifications create a serious risk of accumulating changes over time which significantly hinder uplifting in a timely and seamless manner, due to the need to adapt those changes to every new upstream release. Instead, if changes are needed, e.g., new features, consider contributing or jointly developing those with the upstream project.
0 commit comments