The Week a Supply-Chain Worm Actually Showed Up in One of My Dependency Trees

I’ve written before about the checklist I run whenever a new npm supply-chain worm hits the news cycle. This month, for the first time since I started actually taking it seriously, one of the affected packages showed up as a transitive dependency in a client project I maintain. Not a headline I read and felt relieved wasn’t about me — an actual advisory, with a package name that matched something in one of my lockfiles. Here’s what that week looked like, and which parts of my own checklist held up.
Day one, late afternoon
A GitHub security advisory notification for a package I didn’t recognize by name. My first reaction was exactly the one I’d written about wanting to avoid — a small spike of adrenaline, the urge to start pulling things apart immediately. I made myself do the boring step first: read the actual advisory, not a summary, and check exactly which versions were affected before touching anything. The package turned out to be three levels deep in the dependency tree of a logging utility I use in a legacy Node backend for one client — not something I’d ever installed directly.
Running npm ls for that package across everything I maintain took longer than I expected. This is where the checklist first missed. I have more active projects than I usually keep front of mind, and checking each one — current lockfile plus recent git history, in case a bad version had been installed at some point — ate most of an evening. I’d budgeted “twenty to thirty minutes” in my head based on past incidents. This one took closer to two hours just for detection.
Rotate first, like I promised myself
Once one client project was confirmed exposed, credential rotation came first, exactly like the checklist says — and this time I actually needed it. The affected version had been in that project’s lockfile for about three weeks, so every secret used in that pipeline got treated as potentially compromised: the deploy key, the npm token, a couple of third-party API keys used in the build. Rotating all of it took under an hour — almost anticlimactic next to the two hours I’d just spent finding the exposure.
The part I hadn’t fully planned for: figuring out whether anything had actually happened during the exposure window, not just whether I was theoretically exposed. Confirming a bad version was installed doesn’t tell you whether the payload actually fired. I went through CI logs from that window, checked for unexpected outbound calls, diffed recent commits against what I remembered writing — one known behavior of this attack class is quietly exfiltrating credentials rather than doing anything visibly destructive. I found no evidence anything had fired. But “I looked and found nothing” is a different, more honest claim than “I’m sure nothing happened,” and I made sure I could say the first one.
Telling the client stayed uncomfortable
I already knew this mattered when I wrote the checklist. It was still uncomfortable in the moment. Explaining that a dependency three steps removed from anything I’d chosen directly had a documented compromise, and that I couldn’t offer a hundred percent guarantee — only that I’d checked thoroughly and found no evidence of impact. I sent a written summary rather than trying to explain it live: what happened, what I checked, what I rotated, what I’d be watching for. The reaction was calmer than I expected, mostly because I led with what I’d already done instead of the scary part.
The lesson that actually changed my behavior: I don’t refresh lockfiles on legacy client projects often enough, and that’s what turned a three-week window into what it was. A project I touch daily would pick up a patched version quickly through routine updates. A legacy project I maintain but rarely develop just sits on a vulnerable transitive dependency for weeks, because nothing prompts a look. There’s now a scheduled monthly npm audit and lockfile refresh specifically for the projects I maintain but don’t actively build on.
My checklist held up where it mattered most — read the actual advisory first, rotate before waiting for certainty, don’t trust npm audit alone. But the time estimate was way off, and the “check whether anything actually happened” step needed to be far more explicit than I’d written it. I’ve updated it since. The honest summary of that week: having a checklist rehearsed on paper made the response faster and calmer than improvising from scratch would have. But writing a checklist and living through the real version of it are still two different levels of ready, and I only found the gaps by hitting them.