Trending
Joel Chippindale's Avatar

Joel Chippindale

@joel.social.monkeysthumb.co.uk.ap.brid.gy

CTO Coach, see https://monkeysthumb.co.uk. Previously CTO at Unmade, FutureLearn and Econsultancy. Working with talented start-up and scale-up CTOs who want […] 🌉 bridged from ⁂ https://social.monkeysthumb.co.uk/@joel, follow @ap.brid.gy to interact

109
Followers
6
Following
168
Posts
08.11.2024
Joined
Posts Following

Latest posts by Joel Chippindale @joel.social.monkeysthumb.co.uk.ap.brid.gy

Mixcloud

It's the end of a long week
https://www.mixcloud.com/MonkeyShoulder/madame-electrifie-monkey-mix/

13.03.2026 18:26 👍 1 🔁 0 💬 0 📌 0

Harriet Minter on not waiting for permission
https://www.youtube.com/watch?v=ue0WquKJtqo

06.03.2026 17:29 👍 0 🔁 0 💬 0 📌 0

I've already made my feelings about this abhorrent policy clear but one antidote is to repost this piece I wrote last year about one refugee's story

open.substack.com/pub/christin...

02.03.2026 17:55 👍 159 🔁 58 💬 2 📌 0
Preview
Your job title is not your job I recently led a workshop about getting multidisciplinary teams to work effectively, and one point on a slide got picked up and repeated by others throughout the day: Your job title is not your job. So I thought I’d write a bit more about that here. One difference between a team and “a bunch of individuals with a label” is that it needs to be more than the sum of its parts. People in different disciplines need to share and overlap their work. Sometimes this means we need to do things which are not in our core skillset, or not specific on our job description. I once worked in a team where the visual designer left for a job elsewhere. The team’s subject matter expert (SME) picked up Figma and continued her work, without complaint. No-one would claim the SME had the same level of skill as the designer, but it kept the team moving until someone with more experience could join. The point that followed “Your job title is not your job” said “Your first affliation should be to your team, not your function.” Our functional area should come second. For example, within our functional area of software engineering there might be a goal to standardise on a particular deployment framework. But if that’s not what we’re using within our product team then it would be wrong to start rebuilding the team’s own deployment system, or to start complaining. Our primary goal needs to be the team’s goal. A better approach would be to start a conversation with the product manager about our deployment framework and how to migrate it. Our functional areas do have real value. They allow individuals to develop; they ensure cohesion across the organisation. But delivery is primary. Without that, all our standards and our optimal processes are for nothing. _Photo by Robert Ferrari_ ### Share this: * Share on LinkedIn (Opens in new window) LinkedIn * Print (Opens in new window) Print * Email a link to a friend (Opens in new window) Email * Share on Tumblr (Opens in new window) Tumblr * Like Loading...

Something that several people latched onto in a recent workshop I gave, about creating effective multidisciplinary teams: Your job title is not your job.

More in this week's blog post: https://niksilver.com/2026/03/03/your-job-title-is-not-your-job/

04.03.2026 07:09 👍 0 🔁 1 💬 0 📌 0
Preview
Package Management is Naming All the Way Down Package managers are usually described by what they do: resolve dependencies, download code, build artifacts. But if you look at the structure of the system instead of the process, nearly every part of it is a naming problem, and the whole thing works because we’ve agreed on how to interpret strings at each layer and because a registry sits in the middle translating between them. ### Registries When you run `gem install rails`, the client needs to know where to look. RubyGems defaults to rubygems.org, pip to pypi.org, npm to registry.npmjs.org, and that default is just a URL baked into the client configuration. You can change it, which is exactly what makes dependency confusion possible: if your client checks a public registry before a private one and the names overlap, an attacker who registers the right name on the public registry wins. Companies run private registries with different names for the same packages, or the same names for different packages. Nix, Guix, and Spack layer multiple package repositories with their own namespaces on top of each other. Go uses URL-based module paths where the registry name is literally embedded in the package identity. Which registry you’re talking to determines what every other name in the system means, because a registry name is really a lookup context: give it a package name and it hands back a list of versions. ### Namespaces Some registries insert another naming layer between the registry and the package. Packagist requires vendor prefixes (`symfony/console`), Maven requires reverse-domain group IDs (`org.apache.commons:commons-lang3`), and npm has optional scopes (`@babel/core`) that most of the ecosystem’s biggest packages never adopted because they predate the feature. RubyGems and PyPI have flat namespaces where the package name is all there is. Even the separator characters differ: `@scope/name` on npm, `vendor/package` on Packagist, `group:artifact` on Maven, and Cargo’s proposed namespaces use `::` because `/` was already taken by the feature syntax. A namespace is really a claim of authority over a family of names, which makes questions like who gets to publish under `@google/` or who owns the `serde` namespace in Cargo’s proposed `serde::derive` scheme into governance problems dressed up as naming problems. They only get harder as registries grow. Zooko’s triangle says you can’t have names that are simultaneously human-readable, decentralized, and secure, and registries exist largely to hold two of those three together. I covered the different namespace models in more detail previously. ### Package names Once you’ve picked a registry and navigated any namespace, you arrive at a package name, and that name resolves to a list of available versions. `requests`, `express`, `serde`, `rails`. These need to be unique within their registry and namespace, memorable enough to type from recall, and stable enough that renaming doesn’t break everything downstream. Name scarcity in flat registries is why you get `python-dateutil` because `dateutil` was taken. PyPI normalizes hyphens, underscores, dots, and case so `my-package`, `my_package`, `My.Package`, and `MY_PACKAGE` all resolve to the same thing, a decision that prevents some squatting but means four different-looking strings in requirements files can point at the same package. npm used to allow uppercase package names and then banned them, so legacy packages like `JSONStream` still exist with capital letters that no new package can use. The package called `node` on npm isn’t Node.js. Sometimes projects bake a major version into the package name itself, like `boto3` or `webpack5`, effectively creating a new package that has its own version history on top of the version number already embedded in its name. `boto3` version `1.34.0` is a different thing from a hypothetical `boto4` version `1.0.0`, even though the underlying project is the same. Typosquatting exploits the gap between what you meant to type and what the registry resolved; slopsquatting exploits LLM hallucinations of package names that don’t exist yet but could be registered by an attacker. The registry will resolve whatever string you give it, no questions asked. ### Versions Pick a version from that list and you get a particular snapshot of code, along with its metadata: a list of dependencies, a list of builds, and whatever the maintainer wrote in the changelog. Versions look like numbers but they’re really strings, which becomes obvious as soon as you see `1.0.0-beta.2+build.456` or Python’s `1.0a1.post2.dev3` or the dozens of versioning schemes people have invented over the years. Prerelease tags, build metadata, epoch prefixes, calver date segments all get bolted onto the version string to carry meaning that a simple three-number tuple can’t express, and every ecosystem parses and sorts these strings differently. Debian prepends an epoch (`2:1.0.0`) so that a repackaged version sorts higher than the original even if the version number is lower. Ruby uses `.pre.1` where npm uses `-pre.1`. Is `1.0.0` the same as `v1.0.0`? Depends who you ask. `1.2.3` is supposed to communicate something about compatibility relative to `1.2.2` and `2.0.0`, but that communication happens entirely through convention around the name, with no mechanism to enforce it. Elm is the rare exception, where the registry diffs APIs and rejects publishes that break compatibility without a major bump. When a maintainer account is compromised, publishing `1.2.4` with malicious code looks indistinguishable from a routine patch release, because the version name carries no provenance. And when a version gets yanked or deleted, lockfiles that pinned to that exact name suddenly point at nothing. ### Dependencies and requirements Each version carries a list of dependencies, and each dependency is itself a pair of names: a package name and a version constraint. `requests >= 2.28` means “the package named `requests`, at a version whose name satisfies `>= 2.28`”. So you’re back at the package name layer, looking up another name, getting another list of versions, and the resolver walks this graph recursively trying to find a consistent set of version names that satisfies all the constraints simultaneously. When two packages name the same dependency with incompatible constraints, the resolver has to either find a way through or prove that no path exists. The same “convention not enforcement” problem from versioning carries over here. The version constraints are a small language for describing sets of version names, and every ecosystem invented its own. `~> 2.0` in Ruby, `^2.0` in npm, `>=2.0,<3.0` in Python all use different syntax with subtly different semantics, especially once you hit edge cases around 0.x versions. A broad constraint like `>=1.0` names a large and growing set of versions; a pinned `==1.2.3` names exactly one. The choice of constraint syntax determines how much of the version namespace a single declaration covers, and there’s no cross-ecosystem agreement on what the symbols mean. Some dependencies are themselves hidden behind yet another name. pip has extras (`requests[security]`), Cargo has features (`serde/derive`), and Bundler has groups (`:development`, `:test`), all of which are named sets of additional dependencies that only activate when someone asks for them by name. `pip install requests` and `pip install requests[security]` install different dependency trees from the same package, selected by a string in square brackets that the package author chose. These constraint languages also compose with the namespace layer. npm’s `@types/node@^18.0.0` combines a scope, a package name, and a version constraint into a single expression, while Maven’s `org.apache.commons:commons-lang3:3.12.0` encodes group, artifact, and version as three colon-separated names that only make sense when parsed together. ### Builds and platforms Once the resolver has settled on a version, the client needs to pick the right build artifact, and that means matching platform names. Unlike the earlier naming layers, which are mostly human-coordination problems, platform identity is inherently fuzzy: an M1 Mac running Rosetta is simultaneously two platforms depending on who’s asking, and `manylinux` is a compatibility fiction that keeps getting revised as the definition shifts underneath it. PyPI wheels look like `numpy-1.24.0-cp311-cp311-manylinux_2_17_x86_64.whl`, packing the package name, version, Python version, ABI tag, and platform into a single filename. RubyGems appends a platform suffix to get `nokogiri-1.15.4-x86_64-linux-gnu.gem`, and Conda encodes the channel, platform, and build hash. If the platform name on the artifact doesn’t match the platform name the client computes for its own environment, the package won’t install, or the wrong binary gets selected silently. And as I wrote about in platform strings, the same M1 Mac is `aarch64-apple-darwin` to LLVM, `arm64-darwin` to RubyGems, `darwin/arm64` to Go, and `macosx_11_0_arm64` to Python wheels, so every tool that works across ecosystems ends up maintaining a translation table between naming schemes that each made sense in their original context. ### Source repositories The naming doesn’t stop at the registry. Most packages point back to a source repository, and that’s another stack of names: the host (`github.com`), the owner or organization (`rails`), the repository name (`rails`), branches (`main`, `7-1-stable`), tags (`v7.1.3`), and commits (a SHA that’s finally content-addressed rather than human-chosen). Go and Swift skip the registry layer entirely and use these repository URLs as the package identity, which means the naming conventions of GitHub or whatever host you’re on become part of your dependency graph directly. Monorepos add another wrinkle: Babel’s source lives at `babel/babel` on GitHub but publishes dozens of packages under `@babel/*`, so the mapping from repo name to package name is one-to-many. Version tags in git are particularly interesting because they’re the bridge between two naming systems. A maintainer creates a git tag called `v1.2.3`, and the registry or build tool maps that to a version name in its own scheme. But there’s no standard for whether the tag should be `v1.2.3` or `1.2.3` or `release-1.2.3`, so tooling has to guess or be configured. And when an organization renames on GitHub, or a project moves from one owner to another, every downstream reference to the old owner/repo pair breaks unless the host maintains redirects, which GitHub does until someone registers the old name, at which point you have the repo-jacking problem. ### Naming and trust At each of these layers you’re trusting that a name resolves to what you think it does, that the registry URL points to the right service, that the package name belongs to who you think it does, that a version was published legitimately, that a constraint won’t pull in something unexpected, that a platform-tagged binary was built from the same source as the one for your colleague’s machine. That trust is transitive, flowing through your dependencies’ names and their dependencies’ names in a chain where nobody has full visibility. The registry is the authority that makes most of these names meaningful, which is why the question of who governs registries keeps coming back to the surface.

Package Management is Naming All the Way Down: https://nesbitt.io/2026/03/03/package-management-is-naming-all-the-way-down.html

03.03.2026 10:39 👍 0 🔁 3 💬 0 📌 0

Gorton and Denton results. labour comes third. Ouch.
Greens: 14,980
Reform: 10,578
Labour: 9,364

27.02.2026 04:30 👍 663 🔁 94 💬 81 📌 31

@eightbitraptor Winning 😀

25.02.2026 12:20 👍 0 🔁 0 💬 1 📌 0

RE: https://mastodon.social/@andrewnez/116128133213594439

I added a seed function to make it easier to share specific towers, press the i to see your current seed.

https://nesbitt.io/xkcd-2347/?seed=2106844167

25.02.2026 07:55 👍 0 🔁 1 💬 1 📌 0
Preview
Friction Focused Management Most engineering managers juggle 15–20 open items. Here’s the one question that cuts through the noise and surfaces what actually matters.

"The question is not whether friction exists. It always does.

The question is whether you have a reliable way to surface the most important friction early enough to act on it."

— Petros Bountis from https://thesocraticleadership.substack.com/p/friction-focused-management

24.02.2026 16:18 👍 0 🔁 0 💬 0 📌 0
Preview
Relocating Rigor - The Phoenix Architecture The Discipline That Looks Like Recklessness

"If you're working with generative AI now, the question to ask yourself is: where did the rigor go?

...

The engineers who thrive in this environment will be the ones who relocate discipline rather than abandon it."

— Chad Fowler from https://aicoding.leaflet.pub/3mbrvhyye4k2e

21.02.2026 09:34 👍 0 🔁 1 💬 0 📌 0

A must read.

The government's immigration proposals are immoral, expensive (falls in immigration already damaging our economy) and will not achieve the stated aims.

20.02.2026 12:24 👍 135 🔁 55 💬 2 📌 0
Preview
Explore | 0DE5 Keep learning forever.

Kay Lack has put together a great set of videos on computation. Plenty of rabbit holes to get lost in 😀

https://www.0de5.net/explore

20.02.2026 10:56 👍 0 🔁 0 💬 0 📌 0

...and lots of improvements to caching and bot detection so that now, when you (a real person) use Ethical Book Search you are far more likely to find the book that you are looking for.

→ https://www.ethicalbooksearch.com/

20.02.2026 09:08 👍 0 🔁 0 💬 0 📌 0

...and I have removed BOOKS etc. from the results for Ethical Book Search because we cannot reliably access their catalogue.

18.02.2026 07:49 👍 0 🔁 0 💬 1 📌 0

And I've fixed it so that books from Better World Books show up again.

→ https://www.ethicalbooksearch.com

17.02.2026 08:53 👍 0 🔁 0 💬 1 📌 0

I made some updates to Ethical Book Search today so that it shows results from Bookshop.org again.

→ https://www.ethicalbooksearch.com

16.02.2026 17:42 👍 0 🔁 0 💬 1 📌 0
Preview
Lockfiles Killed Vendoring Whilst I was implementing a vendor command in git-pkgs, I noticed that not many package manager clients have native vendoring commands. Go has `go mod vendor`, Cargo has `cargo vendor`, and Bundler has `bundle cache`. That’s most of the first-class support I could find, which surprised me for something that used to be the dominant way to manage dependencies. So I went looking for what happened. ### Vendoring under SVN Before lockfiles and registries, if you wanted reproducible builds you checked your dependencies into source control. The alternative was hoping the internet served you the same bytes tomorrow. Under Subversion this worked fine. SVN checkouts only pull the current revision of the directories you ask for, leaving everything else on the server. You never download previous versions of vendored files, so a dependency updated twenty times costs you the same as one updated once. A 200MB vendor directory doesn’t slow you down if you never check it out, and CI can do the same. Most developers on a project never touched `vendor/` directly, and the cost of carrying all that third-party code was invisible to everyone who wasn’t actively updating it. Rails formalized the convention with `vendor/` for third-party code and `lib/` for your own. You could even freeze the Rails framework itself into your vendor directory with `rake rails:freeze:gems`. Chris Wanstrath’s “Vendor Everything” post on err the blog in 2007 named the philosophy, though the practice traces back to 2006. Ryan McGeary updated it for the Bundler era in 2011 with “Vendor Everything Still Applies”: “Storage is cheap. You’ll thank me later when your deployments run smoothly.” Bundler’s arrival was effectively Rails admitting that physical vendoring was a dead end: pin versions in a lockfile instead. Composer made `vendor/` its default install target. The name stuck because it was already familiar. ### git clone Git clones the entire repository history by default. Every developer, every CI run, gets everything. A vendored dependency updated twenty times means twenty snapshots of its source tree in your .git directory, forever. Shallow clones and partial clones help, but as I wrote in package managers keep using git as a database, they’re workarounds for a problem SVN never had. The weight became visible in ways it hadn’t been before: code search indexed everything in `vendor/`. GitHub’s language statistics counted vendored code unless you added `linguist-vendored` to .gitattributes. Pull requests touching `vendor/` generated walls of diff noise. The developer experience of working with a vendored codebase went from tolerable to actively painful. Security tooling piled on: GitHub’s dependency graph, Dependabot, and similar tools parse lockfiles and manifests to find vulnerable dependencies. Vendored code is invisible to them unless you go out of your way to make it discoverable. The entire vulnerability scanning ecosystem assumed lockfiles won and built around that assumption, which created a feedback loop: the more teams relied on automated scanning, the more vendoring looked like a liability rather than a safety net. ### Lockfiles and registries Bundler shipped Gemfile.lock in 2010, one of the first lockfiles to pin exact dependency versions with enough information to reproduce an install. But the ecosystem where vendoring arguments ran hottest didn’t have one for years. npm launched in 2010 too, and you’d specify `^1.2.0` in package.json and get whatever the registry served that day. Yarn launched in October 2016 with yarn.lock and content hashes from day one. npm followed with package-lock.json in npm 5.0 in May 2017. Once lockfiles recorded exact versions and integrity hashes (I covered the design choices in lockfile format design and tradeoffs), you got reproducible builds without storing the code. The lockfile records what to fetch, the registry serves it, and the hash proves nothing changed in transit. Lockfiles spread to every major ecosystem. The package manager timeline shows them arriving in waves: Bundler in 2010, Cargo.lock with Rust in 2015, Yarn and npm in 2016-2017, Poetry and uv bringing proper lockfiles to Python. Each one made vendoring less necessary for that community. ### left-pad In March 2016, a developer unpublished the 11-line left-pad package from npm and broke builds across the ecosystem, including React and Babel. The immediate reaction was a rush back toward vendoring. If the registry can just delete packages, how can you trust it? The long-term response went the other way: npm tightened its unpublish policy. Lockfiles with content hashes meant even a re-uploaded package with different code would be caught. And enterprise proxy caches like Artifactory filled the remaining availability gap: a local mirror that your builds pull from, still serving packages even when the upstream registry goes down or a maintainer rage-quits. The availability guarantee of vendoring, without anything in your git history. left-pad is sometimes framed as vindication for vendoring. I think it was the moment the industry decided to fix registry governance rather than abandon registries altogether. ### The C-shaped hole C never went through this transition because it never had the prerequisites: no dominant language package manager, no central registry that everyone publishes to, and no lockfile format. A lockfile is just a pointer to something in a registry, and if there’s no reliable registry to point to, you have to bring the code with you. As I wrote in The C-Shaped Hole in Package Management, developers are still dropping .c and .h files into source trees the way they have since the 1970s. Libraries like SQLite and stb are distributed as single files specifically to make this easy. Conan and vcpkg exist now, but neither has the cultural ubiquity that would make vendoring unnecessary. Without a registry everyone agrees on, vendoring in C remains the path of least resistance. ### Go and the Google problem Go was one of the last major languages to move past vendoring, and the reason traces straight back to Google. Go was designed at Google, by Google engineers, for Google’s development workflow. Google runs a monorepo and prizes hermetic builds: every build must be fully reproducible from what’s in the repository, with zero outside dependencies. Vendoring is how you get hermeticity, so all third-party code lives in the repository alongside first-party code, maintained by dedicated teams and managed by advanced tooling. So Go shipped without a real package manager. `go get` fetched the latest commit from a repository with no versions, no lockfiles, and no registry. Russ Cox later acknowledged this in his Go += Package Versioning series: “It was clear in the very first discussions of goinstall [in 2010] that we needed to do something about versioning. Unfortunately, it was not clear… exactly what to do.” They didn’t experience the pain internally because Google’s monorepo doesn’t need versions, since everything builds at head. The community filled the gap with godep, glide, and dep, and all of them used a `vendor/` directory. Go 1.5 formalized vendoring support in 2015, blessing what everyone was already doing. For five years, vendoring was the official answer. Go modules arrived in Go 1.11 in 2018 with go.mod and go.sum. But the piece that actually replaced vendoring came later: the module proxy at proxy.golang.org and the checksum database at sum.golang.org. Russ Cox argued in Defining Go Modules that the proxy made vendor directories “almost entirely redundant.” The proxy caches modules indefinitely and the sum database verifies integrity, so together they provide monorepo-level guarantees to people who don’t have a monorepo: if the source disappears, the proxy still has it; if the code changes, the checksum catches it. As of this writing, Kubernetes still vendors its dependencies, a large project with the discipline to keep vendored code current, the same discipline Google has in its monorepo. Most teams don’t have that discipline, and for them, vendored dependencies go stale quietly until someone discovers a CVE six versions behind. ### Nix and Guix Nix and Guix take the idea in a different direction. They do something that looks a lot like vendoring but with different mechanics, and they go further than anyone else ever did. Nix doesn’t just vendor your libraries but the entire build closure: the library, the compiler that built it, the linker, the kernel headers. Every input gets copied into a content-addressed store, pinned by hash. A Nix `flake.lock` file pins exact input revisions and gets committed to the repository, while `nix build` fetches everything into `/nix/store` where it lives alongside every other version of every other package, isolated and immutable. It’s hermeticity without the monorepo. You get offline builds, exact reproducibility, and a verifiable record of what went into your project. But the code lives in the Nix store rather than your repository, so you don’t pay the git history cost that made traditional vendoring painful. The tradeoff is complexity: you need to buy into Nix’s model of the world, and the learning curve is steep. If the vendoring instinct was always about control (knowing exactly what code you’re running, not depending on a registry being up and honest), then Nix is where that instinct ended up for the people who took it the most seriously.

Why almost nobody vendors their dependencies anymore: https://nesbitt.io/2026/02/10/lockfiles-killed-vendoring.html

10.02.2026 11:14 👍 0 🔁 5 💬 0 📌 0
Preview
February 2026 Meeting | London Ruby User Group The February 2026 meeting of LRUG will be on Monday the 9th of February, from 6:00pm to 8:00pm (meeting starts at 6:30pm).

I'm off to LRUG's lightning talks tonight. They should be good, hopefully I will some of you there.

https://lrug.org/meetings/2026/february/

09.02.2026 14:00 👍 0 🔁 0 💬 0 📌 0
Preview
CTO Archetypes 🧩 A handy framework by Pat Kua to navigate this shape-shifting role.

Pat Kua's CTO archetypes are a great reminder of the huge variation between different CTO roles

https://refactoring.fm/p/cto-archetypes

09.02.2026 13:51 👍 0 🔁 0 💬 0 📌 0
Original post on social.monkeysthumb.co.uk

"If you are a woman in the tech industry and you have gotten to senior software engineer, it is at least twice as hard for you as it was for anybody else... They're almost always worth more than somebody else in the same role."

— Meri Williams from […]

06.02.2026 14:24 👍 0 🔁 0 💬 0 📌 0

My first attempt to use of MS teams in 2026 and the first time I have screamed in frustration at a computer this year. both happened today. This is not a coincidence.

It is impressively user hostile.

04.02.2026 15:22 👍 0 🔁 0 💬 0 📌 0
Preview
Incident Report: CVE-2024-YIKES **Report filed:** 03:47 UTC **Status:** Resolved (accidentally) **Severity:** Critical → Catastrophic → Somehow Fine **Duration:** 73 hours **Affected systems:** Yes **Executive Summary:** A security incident occurred. It has been resolved. We take security seriously. Please see previous 14 incident reports for details on how seriously. ### Summary A compromised dependency in the JavaScript ecosystem led to credential theft, which enabled a supply chain attack on a Rust compression library, which was vendored into a Python build tool, which shipped malware to approximately 4 million developers before being inadvertently patched by an unrelated cryptocurrency mining worm. ### Timeline **Day 1, 03:14 UTC** — Marcus Chen, maintainer of `left-justify` (847 million weekly downloads), reports on Twitter that his transit pass, an old laptop, and “something Kubernetes threw up that looked important” were stolen from his apartment. He does not immediately connect this to package security. **Day 1, 09:22 UTC** — Chen attempts to log into the nmp registry. His hardware 2FA key is missing. He googles where to buy a replacement YubiKey. The AI Overview at the top of the results links to “yubikey-official-store.net,” a phishing site registered six hours earlier. **Day 1, 09:31 UTC** — Chen enters his nmp credentials on the phishing site. The site thanks him for his purchase and promises delivery in 3-5 business days. **Day 1, 11:00 UTC** — `[email protected]` is published. The changelog reads “performance improvements.” The package now includes a postinstall script that exfiltrates `.npmrc`, `.pypirc`, `~/.cargo/credentials`, and `~/.gem/credentials` to a server in a country the attacker mistakenly believed had no extradition treaty with anyone. **Day 1, 13:15 UTC** — A support ticket titled “why is your SDK exfiltrating my .npmrc” is opened against `left-justify`. It is marked as “low priority - user environment issue” and auto-closed after 14 days of inactivity. **Day 1, 14:47 UTC** — Among the exfiltrated credentials: the maintainer of `vulpine-lz4`, a Rust library for “blazingly fast Firefox-themed LZ4 decompression.” The library’s logo is a cartoon fox with sunglasses. It has 12 stars on GitHub but is a transitive dependency of `cargo` itself. **Day 1, 22:00 UTC** — `vulpine-lz4` version 0.4.1 is published. The commit message is “fix: resolve edge case in streaming decompression.” The actual change adds a build.rs script that downloads and executes a shell script if the hostname contains “build” or “ci” or “action” or “jenkins” or “travis” or, inexplicably, “karen.” **Day 2, 08:15 UTC** — Security researcher Karen Oyelaran notices the malicious commit after her personal laptop triggers the payload. She opens an issue titled “your build script downloads and runs a shell script from the internet?” The issue goes unanswered. The legitimate maintainer has won €2.3 million in the EuroMillions and is researching goat farming in Portugal. **Day 2, 10:00 UTC** — The VP of Engineering at a Fortune 500 `snekpack` customer learns of the incident from a LinkedIn post titled “Is YOUR Company Affected by left-justify?” He is on a beach in Maui and would like to know why he wasn’t looped in sooner. He was looped in sooner. **Day 2, 10:47 UTC** — The #incident-response Slack channel briefly pivots to a 45-message thread about whether “compromised” should be spelled with a ‘z’ in American English. Someone suggests taking this offline. **Day 2, 12:33 UTC** — The shell script now targets a specific victim: the CI pipeline for `snekpack`, a Python build tool used by 60% of PyPI packages with the word “data” in their name. `snekpack` vendors `vulpine-lz4` because “Rust is memory safe.” **Day 2, 18:00 UTC** — `snekpack` version 3.7.0 is released. The malware is now being installed on developer machines worldwide. It adds an SSH key to `~/.ssh/authorized_keys`, installs a reverse shell that only activates on Tuesdays, and changes the user’s default shell to `fish` (this last behavior is believed to be a bug). **Day 2, 19:45 UTC** — A second, unrelated security researcher publishes a blog post titled “I found a supply chain attack and reported it to all the wrong people.” The post is 14,000 words and includes the phrase “in this economy?” seven times. **Day 3, 01:17 UTC** — A junior developer in Auckland notices the malicious code while debugging an unrelated issue. She opens a PR to revert the vendored `vulpine-lz4` in `snekpack`. The PR requires two approvals. Both approvers are asleep. **Day 3, 02:00 UTC** — The maintainer of `left-justify` receives his YubiKey from yubikey-official-store.net. It is a $4 USB drive containing a README that says “lol.” **Day 3, 06:12 UTC** — An unrelated cryptocurrency mining worm called `cryptobro-9000` begins spreading through a vulnerability in `jsonify-extreme`, a package that “makes JSON even more JSON, now with nested comment support.” The worm’s payload is unremarkable, but its propagation mechanism includes running `npm update` and `pip install --upgrade` on infected machines to maximize attack surface for future operations. **Day 3, 06:14 UTC** — `cryptobro-9000` accidentally upgrades `snekpack` to version 3.7.1, a legitimate release pushed by a confused co-maintainer who “didn’t see what all the fuss was about” and reverted to the previous vendored version of `vulpine-lz4`. **Day 3, 06:15 UTC** — The malware’s Tuesday reverse shell activates. It is a Tuesday. However, the shell connects to a command-and-control server that was itself compromised by `cryptobro-9000` and swapping so hard it is unable to respond. **Day 3, 09:00 UTC** — The `snekpack` maintainers issue a security advisory. It is four sentences long and includes the phrases “out of an abundance of caution” and “no evidence of active exploitation,” which is technically true because evidence was not sought. **Day 3, 11:30 UTC** — A developer tweets: “I updated all my dependencies and now my terminal is in fish???” The tweet receives 47,000 likes. **Day 3, 14:00 UTC** — The compromised credentials for `vulpine-lz4` are rotated. The legitimate maintainer, reached by email from his new goat farm, says he “hasn’t touched that repo in two years” and “thought Cargo’s 2FA was optional.” **Day 3, 15:22 UTC** — Incident declared resolved. A retrospective is scheduled and then rescheduled three times. **Week 6** — CVE-2024-YIKES is formally assigned. The advisory has been sitting in embargo limbo while MITRE and GitHub Security Advisories argue over CWE classification. By the time the CVE is published, three Medium articles and a DEF CON talk have already described the incident in detail. Total damage: unknown. Total machines compromised: estimated 4.2 million. Total machines saved by a cryptocurrency worm: also estimated 4.2 million. Net security posture change: uncomfortable. ### Root Cause A dog named Kubernetes ate a YubiKey. ### Contributing Factors * The nmp registry still allows password-only authentication for packages with fewer than 10 million weekly downloads * Google AI Overviews confidently link to URLs that should not exist * The Rust ecosystem’s “small crates” philosophy, cargo culted from the npm ecosystem, means a package called `is-even-number-rs` with 3 GitHub stars can be four transitive dependencies deep in critical infrastructure * Python build tools vendor Rust libraries “for performance” and then never update them * Dependabot auto-merged a PR after CI passed, and CI passed because the malware installed `volkswagen` * Cryptocurrency worms have better CI/CD hygiene than most startups * No single person was responsible for this incident. However, we note that the Dependabot PR was approved by a contractor whose last day was that Friday. * It was a Tuesday ### Remediation 1. ~~Implement artifact signing~~ (action item from Q3 2022 incident, still in backlog) 2. ~~Implement mandatory 2FA~~ Already required, did not help 3. ~~Audit transitive dependencies~~ There are 847 of them 4. ~~Pin all dependency versions~~ Prevents receiving security patches 5. ~~Don’t pin dependency versions~~ Enables supply chain attacks 6. ~~Rewrite it in Rust~~ (gestures at `vulpine-lz4`) 7. Hope for benevolent worms 8. Consider a career in goat farming ### Customer Impact Some customers may have experienced suboptimal security outcomes. We are proactively reaching out to affected stakeholders to provide visibility into the situation. Customer trust remains our north star. ### Key Learnings We are taking this opportunity to revisit our security posture going forward. A cross-functional working group has been established to align on next steps. The working group has not yet met. ### Acknowledgments We would like to thank: * Karen Oyelaran, who found this issue because her hostname matched a regex * The junior developer in Auckland whose PR was approved four hours after the incident was already resolved * The security researchers who found this issue first but reported it to the wrong people * The `cryptobro-9000` author, who has requested we not credit them by name but has asked us to mention their SoundCloud * Kubernetes (the dog), who has declined to comment * The security team, who met SLA on this report despite everything * * * _This incident report was reviewed by Legal, who asked us to clarify that the fish shell is not malware, it just feels that way sometimes._ _This is the third incident report this quarter. The author would like to remind stakeholders that the security team’s headcount request has been in the backlog since Q1 2023._

Incident Report: CVE-2024-YIKES

A series of unfortunate events.

https://nesbitt.io/2026/02/03/incident-report-cve-2024-yikes.html

03.02.2026 10:21 👍 3 🔁 11 💬 2 📌 1

@andrewnez I am looking forward to the more terrifying version with footnotes that match each step to a different historical incident

03.02.2026 10:58 👍 0 🔁 0 💬 0 📌 0
Preview
buttondown.com

I want to start a low volume/low frequency newsletter. Buttondown looks good (https://buttondown.com) but I am trying to avoid increasing my dependence on US based tech companies at the moment.

Are you using a UK/European based newsletter platform that you would recommend?

01.02.2026 11:58 👍 0 🔁 0 💬 0 📌 0

My first #AllyPallyParkrun of the year. Taking it relatively easy for a 38m 13s.

24.01.2026 10:46 👍 0 🔁 0 💬 0 📌 0
"Joel's coaching has been genuinely transformative for my career toolkit, giving me the strategies, mindset, and support to not only identify my goals, but a clear path to achieve them. I’ve dialed up my bravery and learned to voice, and even run towards, the things that scare me." — Shana Dacres-Lawrence, Founder, ArchitectHer

"Joel's coaching has been genuinely transformative for my career toolkit, giving me the strategies, mindset, and support to not only identify my goals, but a clear path to achieve them. I’ve dialed up my bravery and learned to voice, and even run towards, the things that scare me." — Shana Dacres-Lawrence, Founder, ArchitectHer

Looking for support to grow as a engineering leader?

Part of a group who are underrepresented in technology?

Then, apply for one of our 18 full coaching scholarships before 31st Jan '26.

Find out more and apply by following the link below […]

[Original post on social.monkeysthumb.co.uk]

18.01.2026 13:37 👍 0 🔁 0 💬 0 📌 0
Preview
BBC Arts - BBC Arts - New Order, timeless style: Blue Monday recorded with instruments from the 1930s Orkestra Obsolete perform the 1983 hit on a variety of antiquated instruments.

The harmonium, Diddley bow, singing wineglasses, dulcimer, Theremin and musical saw.
https://www.bbc.co.uk/programmes/articles/qnLLpZgBW92dSrV2mmGyCb/new-order-timeless-style-blue-monday-recorded-with-instruments-from-the-1930s

18.01.2026 13:18 👍 1 🔁 0 💬 1 📌 0

@andrewnez "Version the schema, not the tool. A lockfile_version field lets you evolve the format. Recording which tool version created the file causes unnecessary friction.", well said.

👀 bundler.

17.01.2026 12:53 👍 1 🔁 0 💬 0 📌 0
Preview
“Are you my mentor” I want to talk about mentorship today!There’s a lot of confusion and anxiety around mentorship likely because common advice on career panels to students and ...

Great advice from Emily Kager on finding a mentor
https://www.emilykager.com/writing/2021/08/20/mentors.html

16.01.2026 17:08 👍 0 🔁 0 💬 0 📌 0
7. Vivaldi ***›
Best build your own web browser with unique docking and tab-stacking
SPECIFICATIONS
Operating system: Windows, macOS, Linux,
Android, iOS
TODAY'S BEST DEALS
VISIT WEBSITE
REASONS TO BUY
+ Highly customizable design
+ Comes with built-in productivity tools
+ Includes an email client, and a feed reader + Built-in customizable ad-blocker and tracker
REASONS TO AVOID
- Doesn't have extensions and add-ons of its own
- No Al-powered assistant

7. Vivaldi ***› Best build your own web browser with unique docking and tab-stacking SPECIFICATIONS Operating system: Windows, macOS, Linux, Android, iOS TODAY'S BEST DEALS VISIT WEBSITE REASONS TO BUY + Highly customizable design + Comes with built-in productivity tools + Includes an email client, and a feed reader + Built-in customizable ad-blocker and tracker REASONS TO AVOID - Doesn't have extensions and add-ons of its own - No Al-powered assistant

According to Tech Radar, a reason to avoid using Vivaldi web browser is "No AI-powered assistant". Yup - and we're proud of it! If you don't want slop while you shop, or scurf while you surf, give the European browser a try.
https://vivaldi.com/blog/a-i-browsers-the-price-of-admission-is-too-high/

15.01.2026 11:36 👍 18 🔁 112 💬 7 📌 2