Dependencies During Development

Barely Managing

This was originally published in the Kult of the Cyber Witch Zine, issue 01:

Dependencies During Development: Barely Managing

Software relies on dependencies, but managing them is a hard problem. They quickly become numerous and interwoven, stacking up technical debt and hiding serious vulnerabilities under mountains of abstraction. But disaster can be avoided by taking a regimented approach to inclusion, identification, and remediation.

So, why is dependency management so hard? Colloquially, infosec and developers don’t share a unified definition for “dependency management”. Effective communication is already undermined, so let’s start at first principles and try to understand one another.

Establishing a Rapport

We’ll start by defining dependency as required code that’s referenced in a project’s source control, but only included at build time. Each dependency likely has dependencies.

From a development perspective “Dependency Management” is about ensuring the software’s building blocks continue to be fit for purpose. It’s about consistency. If it works, leave it; if it already does it, don’t redo the work. Updating a dependency ties up developer time without changing how the product functions for end users.

From a security perspective, “Dependency Management” is tracking and updating dependencies, their dependencies, and remediating known vulnerabilities. It’s an exercise in logistics and managing risk. Compensating for old, known-dangerous dependencies consumes blue team time without changing the overall security posture.

Managing these competing interests is an organisational and costing issue, and a totally different topic. What matters now is recognising the potential for miscommunication.

Defining the problem(s??)

Often dependencies are installed and tracked using a package manager (composer, RubyGems, npm, etc). It’s simple and helps both developers and security, right? We’ll get to that in a moment.

In infosec, we love assigning blame: either the thing is flawed or it was misused somehow. The real problem with dependency management is that package managers are used exactly as intended. Sure they can be misused, but let’s assume perfect use for now. All of the package managers listed above are intentionally capable of using managed dependency sources, arbitrary git repos, directories outside the project directory, and more.

Take, for example, a large PHP web app at a company with a sizable codebase. At a minimum there are dependencies in .gitmodules, composer.json, and in multiple packages.json files. These are valid and logical places to declare a dependency.

Without a single source of truth, answering “Do you use <blank> library?” requires manual investigation. Probably involving multiple repos and packages.

Developers on large projects can’t track quickly changing dependencies; security is the team really worried about them anyway.

Security teams don’t have time to spelunk through repos; developers look at the repos a lot anyway.

Friction between teams is unavoidable: differing definitions, upset by the extra workload, and each with valid reasons why the other team should be responsible for it. “Process and compliance” solutions drive the wedge deeper, and lag behind “nightly-build” workflow.

A Unified Front

The solution, then, is to remove the responsibility of tracking dependencies from both teams. Luckily, such a solution exists.

CI/CD pipelines minimise similar friction in the testing/QA process. In this workflow, test-engineers and developers work collaboratively. Broken code is (hopefully) caught before leaving development. Fortunately for us, the build process collects the dependencies by necessity: so let’s log them. We’ll make dependency management a code quality gate and fail builds on matches with threat intel.

So, how do we implement this?

Prioritise collaboration over compliance. Both teams’ll have some work to do. Security needs to create a vulnerable-library register of dependencies, versions, and criticality. Dev/Ops needs to modify the build to log dependency collection and compare them with the register.

Then there’ll be the growing pain. We’ve put in place a solution to identify technical debt. That means prioritised, deliberate cleanup work for everyone. Dev has to devote a bit more time to these security tickets. Security has to spend more time compensating for the uncovered vulnerabilities until they can be removed. Project leads must agree a threshold at which a build fails, and accept the risk below that.

But once the backlog is cleared, we’re left with a system where

  1. Libraries with known critical vulnerabilities won’t make it into production ✨
  2. Security has an always up-to-date register of dependencies in use 🤘
  3. Developers can build, assured that external components are fit for purpose 👏
  4. The Dev/Security relationship shifts towards harmony and collaboration 💖

Although our conceptions differ slightly, nobody wants to create a broken product. And we can do that within our already established workflows!