Manage your software dependencies or they will manage you
Contents
- What happens when a dependency gets patched.
- How do you remediate vulnerable dependencies quickly and safely?
- A version bump isn’t always enough.
- Dependency upgrades can run across the whole organization.
- Type-aware search shows where a dependency is used in code.
- Coding agents can call the same recipes.
- In the future, your dependencies will update themselves.
Automated dependency management is how large organizations keep thousands of repositories current. Recipes on Moderne find vulnerable or out-of-date dependencies, direct and transitive, and upgrade them along with any code changes the new version needs. Moderne runs those recipes across every repository at once and opens the pull requests.
Open source libraries distill the work of untold legions of software engineers, and you’d be crazy to go without them. But with dependencies come dependency problems. Each library has its own cadence for breaking changes and vulnerability disclosures, and its own thankless lone maintainer who might get hit by a bus. Every dependency you take is an alert waiting to cancel your plans for the day, or the week.
Two libraries with nothing in common can cause the same headaches, whether they handle HTTP or cryptography:
- Breaking code changes
- Security vulnerabilities
- Transitive version conflicts, like the diamond dependency problem
- No updates at all, because the maintainer opened a craft brewery instead of answering issues
- Bad updates, from new owners who get creative about extracting revenue from users they think are locked in
What happens when a dependency gets patched.
When a dependency gets patched, the fix still has to reach each repository that uses it, directly or through another library. A large organization can have tens of thousands of repositories, each with a graph of hundreds of dependencies. Working out your exposure by hand can take weeks, and attackers don’t wait for you to finish.
Here’s how the fix travels from the advisory to your code:
- The fix is published. The maintainer releases a patched version, and the advisory lands in the GitHub Advisory Database, which also imports CVEs from the National Vulnerability Database.
- A recipe checks every dependency. The Find and fix vulnerable dependencies recipe compares each direct and transitive dependency in each repository against that database.
- Patch upgrades come first. By default, the recipe upgrades a dependency only when the fix is a patch version away, because patch releases aren’t supposed to include breaking changes. The maximum upgrade delta option raises the limit to minor or major.
- Transitive dependencies get fixed too. With override transitives on, the recipe also upgrades vulnerable transitive dependencies, which account for most of the vulnerabilities in a typical application.
- The data table shows what’s left. Every vulnerable dependency, fixed or not, is listed with its CVE, severity, depth in the graph, and the version that fixes it.
- The changes ship as pull requests. Moderne commits the results to all the affected repositories at once, as pull requests or to a branch.
The recipe adapts the fix to the build. In a Gradle project that uses the Spring dependency management plugin, for example, it manages transitive versions with a custom resolution strategy, because that plugin requires one.
I ran this recipe on more than 200 repositories, and it found about 7,500 matches against the vulnerability database. Nobody fixes 7,500 of anything by hand. The recipe’s data table lists all of them, and you can download it as CSV, Excel, or JSON to analyze in other tools.
Equivalent recipes exist for JavaScript, Python, and C#, so those repositories get the same check.
How do you remediate vulnerable dependencies quickly and safely?
Run Find and fix vulnerable dependencies as a dry run with the upgrade delta set to patch, and review the diffs before committing anything. The data table flags which remaining issues a version update alone would fix, and the rest need a minor or major upgrade, sometimes with a migration recipe, or have no fix yet.
A version bump isn’t always enough.
In an earlier analysis of dozens of open source Java repositories, 70% of vulnerable dependencies needed a minor or major upgrade or had no fix available (Dependency updates unmasked). My 200-repository run did better. Patch upgrades were the largest group, almost half the total, and the recipe makes those for you.
Minor and major upgrades can break code, so the dependency change can run together with a recipe that fixes the breaking changes, where one exists. A Spring Boot 4 migration works the same way, with build files and code upgraded in one change.
Sometimes the fix is in a major version you can’t take yet. For those cases, a backpatch applies the security fix to the version you’re on, so you can close the vulnerability before you’re ready to upgrade.
Watch 10 Lessons for Enterprise Code Modernization with OpenRewrite & Moderne:
Dependency upgrades can run across the whole organization.
Finding every repository that uses a dependency is the slow part of an upgrade, and Moderne does it in one recipe run. The Dependency insight recipe shows which versions each repository uses. Then Upgrade dependency version rolls the new version out across all of them, or Upgrade transitive dependency version does it for a library you don’t declare directly. In Gradle, the transitive upgrade adds a constraint to the build file with the reason you give, such as the CVE, so reviewers can see why it’s there.
I once maintained a library that other teams used internally. One day a consumer messaged to say our code was throwing errors, and suggested, in that polite “per my last email” way, that we’d broken something. The stack trace did run through our code, but we hadn’t changed a thing. The library was in maintenance mode.
The cause was Jackson, the serialization library. Our library depended on it, and so did an unrelated third-party library in the same dependency graph. That library had moved to a newer Jackson, and Gradle’s “newest wins” conflict resolution put that version on the classpath instead of the one we’d tested with. A classic diamond dependency problem.
If one consumer had hit it, everyone in the company soon would. This was before OpenRewrite and the Dependency insight recipe, so finding every repository that used both Jackson and our library was hard. Changing our code for the new Jackson release was the fastest part. Working out the impact, contacting teams, and rolling out the fix was 90% of the work.
Now Dependency insight finds them all at once, with a violin chart of the versions in use.
Watch Building the Upgrade Muscle with OpenRewrite at Tinder:
Type-aware search shows where a dependency is used in code.
When a dependency problem lives in the code, in a particular method, class, or data access pattern, type-aware search finds each place it shows up. Knowing which version is on the classpath isn’t enough on its own. Fixing the Jackson problem meant changing how we configured Jackson’s ObjectMapper class. Dependency insight showed which repositories used Jackson, but not where in them ObjectMapper appeared.
The Find types recipe searched 321 repositories in under a minute and a half and found ObjectMapper in 19 of them.
You might point out that a text search for “ObjectMapper” is easy. It is, and your hubris will betray you: a text search misses subclasses like JsonMapper and XmlMapper. With “Check for assignability” on, Find types catches them, because it knows the type hierarchy. (Trigrep brings the same type-aware search to coding agents.)
Coding agents can call the same recipes.
Through Moderne, a coding agent such as Claude Code or Cursor can run the vulnerability check and the upgrade recipes instead of editing build files itself. Reading build files one repository at a time doesn’t show an agent the resolved dependency graph. The recipes work from that graph, so each repository gets a fix that fits its build. For more, see AI coding agents need better tools.
In the future, your dependencies will update themselves.
Imagine the next big CVE ships with a recipe that upgrades the dependency and changes your code to match. By the time someone calls you in a panic, the fix is already rolling out. Or a key library ships a breaking change along with an upgrade recipe, and your code keeps working. With OpenRewrite recipes running through Moderne, dependencies can manage themselves.
For how dependency fixes fit into a wider remediation program, read the playbook for secure code at scale. To run the recipe yourself, follow the docs how-to, or book a demo to see it on your own repositories.
Originally published


