You can fork a package, but can you own it?
On manage software dependencies in a sane way
Fork your dependencies, trim them to only your use case, never update unless it breaks for your users. I’ve been vocal about this for 10+ years. I’ve always said that updating is way riskier than latent bugs (which can be tracked and CVEs monitored).
If you are updating a dependency, it’s on you to analyze every single commit in the full transitive set of dependencies. If you dont see anything compelling, dont update!
I remember at HashiCorp once in awhile an engineer would try to update a dep or replace a DIY lib with an external one and id always ask “show me the commit we need.” Dont update for the sake of it.
Feeling pretty swell about this mentality with all the supply chain attacks happening.
That's from Mitchell Hashimoto. A friend sent it to me, and the first word he reached for was bold. Mine too. I mostly agree with him, honestly. But the second I read it, my head jumped to a caveat, and the caveat turned out to be the thing I actually wanted to write about.
You can fork a small library and trim it to what you use. I’ve done it, it’s fine. But could you fork React and maintain it? I couldn’t. I don’t think most teams could either. So “fork your dependencies” is wonderful advice right up to the point where the dependency is too big to hold in your hands, and then it quietly stops being advice at all.
And that caveat says something about the advice itself. I think that it was never really about forking. It’s about knowing exactly what you’ve taken on and being willing to own it, and that’s the bit most of us skip. We don’t decide to take a dependency. We install one. And almost everything that goes wrong with dependencies:
the supply chain attacks,
the licence dramas,
the half-finished SBOMs,
the things you find out about only after they break
comes back to that one move: we took dependence on someone else's product without ever deciding to. Everything else here is a variation on it.
To fork or not?
I was reminded of this not long ago. I was doing a live Q&A about my TypeScript port of some code, and a whole chunk of it was one question asked five different ways:
Why did you write your own
deepEqualsinstead of just pulling a package?
I tried to explain that it wasn't laziness, and it wasn't not-invented-here pride either. I explained that it’s the code I write once and forget. Of course, it’ll take some time, but not that long as you might think. The reception was, let's say, mixed. I get it, it’s much easier to just install a package and outsource maintenance. Especially when you're in a hurry, you don't decide anything. You reach. And this reach can be a decent interim solution, but then you should reflect on yourself on the next step.
Why do I reach for my own code on the small stuff? Look at which packages actually get hit in all these supply chain attacks we keep reading about. It’s almost always the little ones. The one-liners. Remember the Left-pad Incident? It’s usually some tiny or low-level helper nobody thinks about. They’re everywhere, they’re trivial, and precisely because they’re trivial, nobody is watching them. Which is also exactly why they’re so easy to write yourself. So for me, handwriting a small helper isn’t paranoia. It’s the calmer option, and it’s one of the few cases where I know exactly what’s in my own system.
That's also why, in Emmett, my Event Sourcing library, I'm almost stubborn about keeping the core package free of dependencies and limiting whatever I inject elsewhere. Probably too stubborn, if I'm honest. But I'd rather err that way, because when it goes wrong on the user’s side, it goes wrong far worse than a bit of code I maintain myself. It’s about responsibility for the outcome of your actions.
If I had to compress what I believe into one line, it wouldn’t be “fork everything”, and it wouldn’t be “write everything yourself”. It would be something duller than both:
Be precise about which dependencies you take on, look at how many dependencies your dependencies pull in, and treat that as part of the decision.
The number itself isn’t the point. It just tells you how much you’re agreeing to own without ever seeing it.
Because your direct dependencies were never the worst part. You chose those. Most of the time, you can read them, and you can follow what they’re doing. The scary part is the dependencies of your dependencies, and theirs, all the way down, the part you didn’t choose, can’t see, and have no say over.
And I want to be careful here, because it’s easy to let this slide into another round of JavaScript-bashing, and that’s not what I mean. Every ecosystem has this. JS and TypeScript just sit at one far end of it, where there’s a package for absolutely everything. Which is good, in general, you’re not rebuilding the wheel every other week, the way you are in some other places. But it’s also how you end up with a node_modules you couldn’t fully explain if someone put a gun to your head.
At the other extreme, there’s Microsoft and .NET, where the instinct runs so hard the opposite way that it tips into Not Invented Here Syndrome. Neither end is the “right” one. They’re both defaults people drift into without ever making a call.
So for me, it’s not about reaching zero dependencies. But having dependencies that we cautiously agreed upon.
Which takes me to the part that, in my experience, almost nobody does. You can’t make a call on what you can’t see, and if you don’t even have the basic knowledge (e.g. some list) of what you depend on, then every conversation about supply chain risk is a bit of theatre.
Dependency Inventory
In most of the environments, there are tools to generate the Software Bill of Materials - the inventory of your dependency tree. In some, they’re even built in. It’s easy to dunk on NPM, but it’d be better to do due diligence before doing so. Not many people seem to know this, but recent versions of npm ship an npm sbom. So the tooling exists, even in NPM. That isn’t the problem.
The problem is that most organisations have never generated one in their life. No SBOM, no inventory, nothing written down anywhere. So the day the next Log4Shell lands, and there will be a next one, they can’t answer the very first question anyone will ask them: do we run this, and if so, where?
On the other hand, tools often don't help here, even those built to do so. NPM audit mostly does the opposite. I honestly can’t remember the last time I installed something, and the audit didn’t immediately tell me to bump a stack of packages. Most of it is false positives, with no real attempt to say how dangerous any of it is. And that lands you in the oldest trap going: if it’s always red, you stop looking at red. So the one signal that was supposed to make you stop and decide ends up training everyone to decide nothing.
Bus factor and rug pulls
There’s a related thing I can’t quite leave alone, so let me wander into it. I think a lot of teams spend their energy on the symptom and never once look at the source.
Watch what happens in the .NET world whenever a popular package changes the deal. Fluent Assertions went commercial. Moq shipped a thing that quietly hashed your git email and phoned it home. MassTransit and AutoMapper announced commercial licenses within the same stretch. And nearly every time, the reaction across .NET shops is identical. It’s a mixture of
let’s rip it out and write their own,
search for a free alternative
Cry to Microsoft to buy the lib or provide a replacement,
Essentially: a throw-the-baby-out-with-the-bathwater strategy.
And for me, that’s solving the wrong thing entirely. The source isn’t that the package started charging money, or pulled a rug. The source is that we took on a critical dependency without ever admitting to ourselves that it was critical, and never once thought about what we’d do if the terms changed. We didn’t consider the bus factor, and we didn’t do due diligence to ensure the work on it was sustainable and could continue. Pulling it out and hand-rolling a replacement fixes none of that. It just resets the same trap, this time with only the code we maintain.
The IdentityServer episode was the clearest version of it I’ve seen. People were upset that they had to pay suddenly. Then, in the next sentence, calling it a critical security component. Then, in the sentence after that, asking what the free alternatives were. A critical security component that you want for free and are ready to swap out overnight is, to my mind, asking for a security incident.
And there’s a bit of maths that quietly settles most of these arguments, if anyone bothers to do it. Take what the licence costs you per year. Then take into account what it would cost to have an engineer build and maintain your own version. Put the two side by side.
Almost every time, paying the maintainer comes out cheaper, and on top of that, you’ve lowered the bus factor on something you already lean on, which is its own kind of supply chain security. “We’d write it ourselves, but then we’d have to maintain it” is true. I just read it as the argument for paying the person who already does, not against it. If you depend on something, its survival is your problem too. That’s part of owning the decision.
LLM as a fork
Getting back to Mitchell’s thought. The part I find most interesting is because of the moment we’re in. I keep hearing that LLMs change all of this, that writing your own small things is suddenly trivial, so the whole dependency question softens. I don’t buy it. It’s never that easy. Writing the small thing was never the hard part anyway. Owning it, understanding it, maintaining it, being the one on the hook when it breaks at 2 am, that’s the hard part, and no model takes that off your plate.
I don’t see how LLMs can change the cost of owning code. They can (maybe) change the cost of producing it. That doesn’t fix the “install without deciding”. The old move was install and move on. The new move is “vibe it” and move on. Same missing decision, new flavour. The same lack of responsibility and ownership.
This trend isn’t new. It’s a classic Shadow IT. If you haven’t been around long enough to run into the term, Shadow IT refers to the tools and systems people build or adopt within a company without going through whoever is officially meant to approve them. The spreadsheet that quietly runs a whole department. The little script someone wrote on a Friday that half the team now depends on. The integration nobody in the platform group has ever heard of. It has always existed because people route around slow governance to get their job done, and most of the time, nobody notices until it breaks.
With LLMs, it’s more tempting than it has ever been. Someone in sales promises a customer a feature the team supposedly needs. The team has no time, so they cobble a tool together from the API and ship it. It doesn’t work. The customer says they’re not paying for this. It escalates. The thing was unowned from the moment it was conceived; nobody decided to take it on, it just appeared, and the blame game is starting.
And here’s where I think it all settles, because the corporate steamroller flattens everything in the end. Companies will dictate the allowed list, the way they always have. The cautious majority will stick to what’s known and popular: React, TypeScript, Python, Spring Boot. That’s what they did last time, and the time before. And the people who want to move faster will do it off the books, with an LLM, as Shadow IT. The declarative, standards-based frameworks that hide their complexity will do well in that world, because that style suits how these tools work, but it’s the same ceiling as before. You bet on React. You don’t own it. The small stuff you can hold; the big stuff stays as bet.
What to do then?
We cannot fix the entire software industry, but we can fix how our own engineering teams operate. Instead of waiting for automated audits to scream at us, or ripping out packages in an emotional panic, I suggest a simple, regular exercise for your organisation.
Sit down and explicitly define your dependency posture:
Inventory: List the dependencies you use (even without peer dependencies). Use tools
npm-sbomto actually see what you are pulling in.Criticality: Identify which of these packages are absolutely critical to your system.
Lifecycle: Define a clear strategy for upgrading and versioning them. Are you updating just for the sake of it, or are you looking for specific commits like Mitchell suggests?
The Bus Factor: Ask yourself: what happens if the author of a critical package gets hit by a bus, burns out, or the tool becomes paid?
Mitigation: Decide on a concrete backup plan for that exact scenario. Do you fork it? Do you pay the license fee? Maybe pay earlier for support or help in another way to maintain it.
Response Time: Estimate how quickly you can upgrade and deploy the application if a major security breach occurs in a dependency. Also, if the strategy is to use replacement, then how fast will you be able to replace this dependency?
Building reliable software requires intent. We don't have to write everything from scratch, but we must be precise about what we bring into our software. Architecture is not just about writing code; it is about choosing which liabilities you are willing to own.
Cheers,
Oskar
p.s.2. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help. You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to Red Cross, Ukraine humanitarian organisation or donate Ambulances for Ukraine.


