Escaping a transitive dependency nightmare with some help from Gradle
Tags: gradle, java
Published: 2021-09-06 16:23:01

---
title: Escaping a transitive dependency nightmare with some help from Gradle
published: true
description:
tags: gradle, java
//cover_image: https://direct_url_to_image.jpg
---

Every so often at work, our CI pipeline's dependency check job spits out another vulnerable dependency detected in our application. Not entirely surprising considering the rate at which new CVEs are published these days.

This isn't much of a problem for direct (or first-level) dependencies, as long as there's a patch for it out already. All you do is upgrade your dependency to the latest patched version, ensure the update doesn't actually break your application, and you're good to go.

If the vulnerability lies in a transitive dependency (a dependency of *another* dependency, typically one that you don't directly declare), it starts to get a little messy. A couple of issues can arise here:
1. You don't know where this dependency even came from.
2. The parent dependency hasn't updated to the latest patched version yet.

Luckily, modern build tools help make this chore (an important chore nonetheless) a little less painful. In our case, this was with some help from Gradle.

### Tracking down transitive dependencies

Identifying the top-level dependency where a vulnerable dependency is used is usually the first step to patching your application.

Gradle's build scan can probably provide the most information about your project in one central place, but this involves publishing build details to Gradle's servers, something you might want to avoid if you don't want sensitive details related to your project out in public.

Instead, a fast and easy way to do this locally on the command-line is with the `dependencyInsight` task provided by Gradle. This visualizes the origin of a transitive dependency and, in the case of multiple conflicting versions, also shows why a particular version was chosen. 

The latter is what separates it from another similar task called `dependencies` which only provides a raw visualization of every dependency in your project.

You can run this task from the command-line as follows:
```
$ gradle -q dependencyInsight --dependency <dependency-name>
```

You can also provide an option `--configuration` to specify the gradle configuration to scan. If nothing is provided, it scans `compileClasspath` by default.

In my case, I wanted to track down `commons-io` (version 2.6 had the vulnerability CVE-2021-29425) in my project. Running the above gave the following output:

```
commons-io:commons-io:2.6 (selected by rule)
   variant "compile" [
      org.gradle.status              = release (not requested)
      org.gradle.usage               = java-api
      org.gradle.libraryelements     = jar (compatible with: classes+resources)
      org.gradle.category            = library

      Requested attributes not found in the selected variant:
         org.gradle.dependency.bundling = external
         org.gradle.jvm.version         = 11
   ]

commons-io:commons-io:2.6 
+--- <internal-library>:0.1.372
     +--- compileClasspath
```
(*Simplified output only for representation, most large projects have much more complex dependency trees*)

This tells me that `commons-io v2.6` is a dependency of an internal library that we used between teams. Now I could have waited for the team responsible for maintaining this internal library to patch it themselves, but that would mean blocking my CI pipeline from further deployments for an unknown period of time. Instead, I need a way to force-upgrade this dependency to a patched version until the maintainers patch their library as well.

This is where another feature of Gradle comes into play.

### Applying constraints on transitive dependencies

Dependency constraints is the easiest (and the recommended) way to force resolve a particular version of a transitive dependency. Constraints apply on all direct and transitive dependencies defined in a project. 

A constraint on a dependency can be applied in your `build.gradle` script like this:

```
dependencies {
    implementation 'org.apache.httpcomponents:httpclient'
    constraints {
        implementation('org.apache.httpcomponents:httpclient:4.5.3') {
            because 'previous versions have a bug impacting this application'
        }
        implementation('commons-codec:commons-codec:1.11') {
            because 'version 1.9 pulled from httpclient has bugs affecting this application'
        }
    }
}
```

The above constraints force Gradle to always use `httpclient v4.5.3` and forces resolution of the `commons-codec` library to v1.11 instead of v1.9 specified in `httpclient`.

Therefore, to force upgrade to a patched version of `commons-io`, I had to add the following constraint in my `build.gradle` script:

```
constraints {
        implementation('commons-io:commons-io:2.10.0') {
            because('Versions < 2.7 allows limited path traversal with FileNameUtils.normalize method - CVE-2021-29425')
        }
    }
```
The `because` closure allows you to explain why this version should be chosen and it will appear in the `dependencyInsight` output.

Now my `dependencyInsight` output looks like this:
```
commons-io:commons-io:2.10.0
   variant "compile" [
      org.gradle.status              = release (not requested)
      org.gradle.usage               = java-api
      org.gradle.libraryelements     = jar (compatible with: classes+resources)
      org.gradle.category            = library

      Requested attributes not found in the selected variant:
         org.gradle.dependency.bundling = external
         org.gradle.jvm.version         = 11
   ]
   Selection reasons:
      - By constraint : Versions < 2.7 allows limited path traversal with FileNameUtils.normalize method - CVE-2021-29425


commons-io:commons-io:2.6 -> 2.10.0
+--- <internal-library>:0.1.372
     +--- compileClasspath
```

Now I can rest assured that the vulnerable dependency always resolves to the patched version instead of the one declared inside the internal library.