Skip to main content

Dependencies in your code

Dependencies provided by external sources are a normal part of software development. We use many open source libraries written by 3rd parties to provide functionality in our apps. In PHP we use composer to install these external libraries and we have similar tools for node, python and golang.

The last few years have seen an uptick in security attacks using trusted dependencies and weak security in the management of package ecosystems to compromise applications. Node, PHP and Python have all had high profile compromises, we’ve been very fortuante to not be using the packages affected.

Security is not the only issue though, some languages lean more into the working practice of grabbing any dependency rather than fully assessing if you need it. This can lead to long term maintenence churn on packages providing very simple functionality or bringing in a massive node package download to the frontend to achieve something simple provided by in-browser functionality. Both of which can create work we don’t need.

This guidance is to formalise the rules of thumb used to choose externally provided libraries to ensure we reduce the risk of a compromised package and minimise our maintenence burden.

New Libraries and Dependencies Guidance

1. Question if you need an external dependency

We need to balance not-reinventing the wheel with taking on a support and security burden. “Would it take longer than a day to write the thing yourself?” is a good question to ask. It’s worth taking the time to research how difficult that would be. You have the chance to do some learning and development in an area, which you can share and builds the whole team’s experience. Maybe that’s better than outsourcing the knowledge and approach to a 3rd party?

Equally, it could be an area that’s complex and requires detailed specialist knowledge to work in, such as security, interoperability with closed source code or detailed understanding of standards. In these cases letting somebody else do the work is advisable as time, effort and specialist knowledge take time to acquire.

Another question to ask before writing your own code is “will it take longer than a day to maintain?” Just because you understand it now doesn’t mean you’ll understand it in a year. If you write it yourself who handles ongoing support and who else understands it? Consider if it’s not application code, but really a shared library within OPG for a specific need we all share so we don’t end up copy-pasting code about.

The tradeoff here is essentially how much time does a 3rd party dependency save you now, versus how much time will it cost you later.

This article written following the incident with the colors.js library is a good explanation on reuse vs build.

2. Always check the GitHub repo for recent activity

You want a library that is getting regular patches and security updates.

You also want a library that doesn’t change its external API every 6 months, forcing you to rework large portions of your app to accomodate it. Reading around about the library can assist you in getting a feel for this as can looking at which major version it is, be suspicious of anything lower than 1.0.0 or with flags for “not yet production ready” or “proof of concept”

You want a library that is compatible with the main framework you are using and any coding standards your team have in place. You also need to consider if it’s maintaining compatibility with upcoming language and framework versions. We’ve seen this with Laminas and PHP 8.1 compatibilities, for example, where there was a lag in language compatibility updates.

3. Check that the dependency is managed by a community

Single contributor libraries have more chance of being compromised and nobody noticing. More eyes on changes means more chance of vandalism or malicious activity being caught earlier.

Communities also enforce standards and tend to mean APIs are more stable as others need to use them too. Communities supported by charitable trust (JS foundation) or big organisations (Mozilla) tend to help with ensuring maintenence, stability and backward compatibility.

Libraries with single maintainers, even well known ones, can be subject to issues not found in larger communities, like the creator rage quitting (see node left pad) or just vanishing and the package becoming abandoned.

The number of open issues and how they are responded to can also be an indicator of community health. It’s worth being wary of anything with a high number of “won’t fix” type labels.

4. Check the license on the dependency

Not all software licenses are compatible. We use an MIT license for our open source code, so anything using that is 100% compatible.

Some licenses may be incompatible with private storage of source code, for example due to forcing publication, which might not be appropriate to a service that requires some parts of its codebase to be private for operational reasons.

5. Check how the library responded to any previous security incidents

Transparency and quick reaction to any previous security issues are a good sign of how a library’s maintainers will react next time. Particularly if they have defined incident processes, public lessons learned and risk mitigations already in place.

6. See how easy contributing is

We like to give back if we use things, hopefully that’s not a painful process. How easy is it for a new contributor to get a PR merged on the library? This will help if we do need to patch something upstream to gain compatibility with recent language versions.

This can be a downside too: what checks do you need to pass to get code added? Is it too easy? Could somebody manipulate that process?

7. Do you need the whole thing?

Smaller libraries are prefered to larger frameworks to avoid bringing in large amounts of code you don’t need for one class or method you do. The attack surface of a bigger project means there’s more potential for you to get an exploit in unrelated code you end up shipping anyway.

This is particularly an issue with frontend dependencies, don’t add 1 library with 40 sub dependencies just to do something browsers can do anyway with CSS or native APIs. The performance and usability impact of 25mb of JS just to add a hover effect should not be underestimated. Check out tools like tree-shaking in builds that can reduce this by not deploying unused codepaths.

8. Check what the library’s own dependencies look like

Sometimes it’s not the direct dependency itself that’s the risk, but the dependencies of the dependency. It’s worth checking for further downstream projects with the same set of issues.

9. Pin to a specific version

It’s always best to pin a given dependency to a specific version. So using 1.0.4 rather than a less specific ^1.0.

While lock files will pin to known good versions, you might inadvertantly pull in a malicious version 1.0.5 as it becomes available as part of development changes when updating packages if you aren’t pinned.

We have dependabot to pull in point-version updates when they become available. For particularly volatile ecosystems (NPM/Node) it is best to wait a 3 days before merging PRs for non-security related updates to allow time for malicious activity to be detected.

Some teams have experimented with automerging patch updates. If you do this it’s worth automating the 3 day wait, particularly if your app is node based. Critical security updates are worth considering in isolation, as it may be that you are at greater risk leaving it unpatched for 3 days than the patch itself being bad. A check of the upstream commits for tampering can help make decisions on this.

10. Look for opportunities to refactor out dependencies

Not all our code bases are new (Sirius and Make at now 8 years old in places). As such it’s always worth being on the lookout for dependencies that were good at time of implementation, but have since fallen behind or lost support. It’s always good to reassess the value of a given dependency, in some cases the core language or framework will have moved on and now provide functionality natively. A good example of this is jQuery, where a decade ago you needed its selector engine, but now the web platform provides document.querySelectorAll()`. In general do this at least every 6 months.

Pay particular attention for the warnings of “no longer supported” that package managers will provide.

This page was last reviewed on 20 March 2024. It needs to be reviewed again on 20 September 2024 by the page owner #opg-developers .
This page was set to be reviewed before 20 September 2024 by the page owner #opg-developers. This might mean the content is out of date.