Part 12 of 12 6 min read

Don't reinvent the wheel

Don't Depend on It

Every dependency was a trade: someone else's code for a little risk. AI made the risk much bigger and the code nearly free. Rerun the trade.

I taught beginners to reach for a gem before writing a line. Don't reinvent the wheel. Someone smarter already solved this, it's tested, it's maintained, add it to the Gemfile and move on. Rails apps I've shipped had a couple hundred gems in the lockfile and nobody thought that was weird. It was the responsible thing to do.

The trade underneath that advice was simple. A dependency gives you a solved problem for free, and in exchange you accept a small amount of risk that someone else's code, or someone else's account, turns on you. For twenty years the free part was enormous and the risk part was small. AI changed both numbers at once.

The risk got bigger

Two things happened to the risk side.

First, the attacks are relentless now, and they're aimed at the registries, not at your app. The big ones from the last year alone: in September 2025 someone phished one maintainer and shipped malicious versions of chalk, debug, and sixteen other packages that together see over two billion downloads a week. Two months later the Shai-Hulud worm compromised nearly 800 packages by stealing tokens from each machine it landed on and publishing itself to everything those tokens could reach. In March 2026 one hijacked npm account turned axios, 100 million downloads a week, into a trojan dropper. The malicious change was one line in package.json: a phantom dependency with a postinstall hook. In June a state-sponsored crew poisoned 140 Mastra packages in 19 minutes. Sonatype counted over 450,000 new malicious open source packages in 2025, up 75% in a year.

Second, the window between a vulnerability and an exploit has basically closed. The Cloud Security Alliance puts the average time from disclosure to a working exploit at under 12 hours now, down from months. An agent can read a CVE and produce exploit code in the time it takes you to read the advisory. And the same agents are scanning the internet for every unpatched instance.

Ruby isn't exempt. This July, CVE-2026-66066 made nearly every Rails app that processes uploaded images, which is nearly every Rails app, readable by an unauthenticated attacker. Not a bug in Rails, exactly. A crafted file that lied to libvips about what it was, three layers down in a library most Rails developers have never typed the name of. Active Storage trusted it. I had six apps to patch that morning.

The old model was that an unpatched dependency was a slow leak. You'd get to it. Now it's a race you're in whether you know it or not, and you're in it once for every package in the lockfile.

The free part isn't free anymore either

Here's the other half, and it's the part I actually find interesting.

The reason you took on a color picker dependency was that building one is genuinely hard. Keyboard accessibility, touch, hex and RGB and HSL, a11y labels, dark mode. A weekend of work to get right, and it's not your product. Of course you installed the package.

Last week I needed a color picker. I pointed Claude Code at the package I would have installed and said:

Read through this color picker library. Build our own version as a single
Stimulus controller plus a CSS file in our design system. Keep the keyboard
handling and the hex/RGB inputs, drop the gradient editor and the plugin
system, no external dependencies. Then write a system spec that picks a
color with the mouse and one that does it with only the keyboard.

Forty minutes later I had a color picker that's about a tenth the size of the package, uses our tokens, and is in my repo where I can read it, grep it, and change it. It's not in my supply chain. Nobody can phish a maintainer and push a new version of it. There's no postinstall. It doesn't pull in eleven transitive packages I've never heard of.

There's a quieter benefit too. If my color picker has a bug, nobody knows but me. A vulnerability in a public package comes with the source, a version string, and a list of every site that ships it, and the agents on the other side read all of that as fast as the ones on mine. A private implementation gives them nothing to read and nothing to search for. That's not a defense against someone who's after you specifically, but most attacks aren't aimed at anyone. They're aimed at a version number.

That's the shift. A dependency used to buy you expertise you didn't have time to acquire. The agent has the expertise and the time. What's left is the risk, and I'm paying it for nothing.

Not every dependency, just most of them

I'm not writing my own ORM or crypto. Here's how I think about it now.

Keep the dependencyBuild or vendor it
The problemDeep and dangerous to get wrong: crypto, auth, databases, parsing untrusted input, image decodingShallow and specific to your app: UI widgets, formatting, HTTP wrappers, "utilities"
The maintainerA foundation, a company, or a well-funded project with release signing and a security processOne person, a tired volunteer, or you can't tell who it is
The transitive treeSmall and stableDozens of packages, several of which exist to pad a string
Install scriptsNoneAny postinstall at all
If it's compromisedYou'd find out from the news within the hourYou'd find out from your users

The right column is where most of a typical app's lockfile lives. The left column is maybe twenty packages, and they get pinned, watched, and updated the day an advisory drops.

The ecosystem matters

This is harsher for JavaScript than for Ruby, and it's worth being honest about why. Ruby has a real standard library. Rails ships most of what an app needs. A Gemfile with forty entries is a big app.

JavaScript has no standard library to speak of, so every project assembles one out of npm. That's how the world ended up where unpublishing eleven lines called left-pad broke React, Babel, and thousands of builds a second in 2016. That wasn't even an attack. It was a maintainer having a bad day, and it showed exactly how deep the tree went.

Your package manager is part of your attack surface too. The axios payload was a postinstall script. npm ran it. Bun doesn't run lifecycle scripts from your dependencies unless you put the package in trustedDependencies, and it ships a scanner hook that can refuse an install outright. That one default is the difference between being affected by a whole class of attacks and reading about them. If you're still on npm because it's what you've always used, that's nostalgia, and it's the expensive kind. npm is finally blocking install scripts by default in v12, which tells you how obvious the fix was.

What I do now

When I'm about to add something, I ask the agent to look at it first. How big is the transitive tree, who maintains it, does it have install scripts, when was the last release. Half the time the answer is "this is 300 lines, want me to just write it." Yes.

For what's already in the lockfile, I had an agent go through each app and sort every dependency into the table above. Then it rewrote the right column, one at a time, with tests. Each app lost somewhere between a third and half its dependencies and nothing changed for the user. Audits got shorter. The next advisory morning will be shorter too.

The old advice assumed the code was the expensive part. It isn't anymore. The expensive part is trust, and I'd rather extend it to twenty things I chose on purpose than two hundred I never looked at.

This is part of Rethink Everything, where I own the servers and duplicate code for the same reason: the agent made owning things cheap, and owning things is how you know what's in them.

The whole series

  1. 1Code is written for humans to readStop Writing and Reading Code
  2. 2Never rewrite from scratchRewrites Might Be Better Than Massive Refactors
  3. 3Use the language your team already knowsLanguage Strengths Over Language Familiarity
  4. 4Build it once for the web and wrap itGo Native Everywhere
  5. 5Keep the monolith majesticI Hated Microservices. Agents Love Them.
  6. 6Pay someone else to run your serversOwn Your Infrastructure
  7. 7Don't repeat yourselfDuplicate Code So Your Agents Stop Colliding
  8. 8Types are ceremonyTypeScript, I Don't Hate You Anymore
  9. 9Respect the testing pyramidMore E2E Tests, Fewer Unit Tests
  10. 10Refactor for readabilityStop Refactoring for Humans. Refactor for the Agent.
  11. 11Get fast on the keyboardTalk to Your Agents
  12. 12Don't reinvent the wheelDon't Depend on It