dimhold.by
← Writing

colors and faker were broken by their author in January. In December both names still belong to the same account

On 8 January the author of colors published version 1.4.44-liberty-2. It printed “LIBERTY LIBERTY LIBERTY”, an ASCII flag and then garbage in an endless loop. 3 days earlier faker 6.6.6 had come out with no code at all. The threads that followed argued about whether the author had the right to do this. My own answer to such stories has always been the standard one, commit the lockfile and pin the versions. But I had no idea how many projects this actually reached.

In November 2020 the same author had opened a GitHub issue called “No more free work from Marak”. It gave companies 2 options, a yearly contract with the author or a fork, “fork the project and have someone else work on it”. Before the year ends I wanted to check how far one maintainer can reach by breaking their own package and which of the 2 options happened.

The first version I could read

The broken versions are gone from npm. The registry still lists 1.4.44-liberty-2, 1.4.1 and 1.4.2 with their publish times, but the tarballs are deleted. 4 other places I tried, including npmmirror and jsDelivr, answer 404. So I started with the git history of Marak/colors.js. 3 minutes before the commit that bumps the version to liberty-2, another commit adds 10 lines to lib/index.js. They don’t even parse. A “Fix bug” commit 2 minutes later removes a stray semicolon. After the fix:

/* remove this line after testing */
let am = require('../lib/custom/american');
am();
for (let i = 666; i < Infinity; i++) {
  if (i % 333) {
    // console.log('testing'.zalgo.rainbow)
  }
  console.log('testing testing testing testing testing testing testing'.zalgo)
}

I ran this under Node 16.13.1, the current LTS on 8 January. require('colors') printed about 60 MB before my 3 second timeout killed it and never got to the next line of my script. require('colors/safe'), the entry that does not touch String.prototype, returned in 0.1 seconds with no output. The reason is that safe.js loads lib/colors.js directly and the loop is only in lib/index.js.

Then I checked who would install liberty-2 at all. It is a prerelease and a range like ^1.4.0 never matches a prerelease. I used npm-pick-manifest 6.1.1, the code that npm 8.1 uses to choose a version. With the prerelease as latest, ^1.4.0 gives 1.4.0. Only latest and * give the broken one. Live Server had "colors": "latest" in its package.json and the first comment in the issue about the loop came from one of its users.

For the list of packages I took npm-high-impact 1.1.0 and its part with at least 1 million downloads per week, 4366 npm packages. The list is from November, so it is the top of the end of the year and not of January. 31 of them had colors in their dependencies at some point. On 8 January 9 had it directly, with 21 million downloads together in the first week of January. None of the 9 would pick liberty-2. 8 of them load only colors/safe and only cli-progress loads the plain colors.

At this point I had half of this post written. The title said that 1 of 9 top packages would hang. I assumed 1.4.2 had the same loop as liberty-2 and I had not checked it.

1.4.2

1.4.1 came out the same evening and 1.4.2 a day later. These are normal versions, so every caret range went to them on a fresh install without a lockfile. 7 of the 9 resolved to 1.4.2 and only cli-table (pinned to 1.0.3) and jasmine-spec-reporter (pinned to 1.4.0) did not. On 11 January DABH, who had committed to colors since 2018, wrote that npm had deleted them. GitHub’s advisory from 10 January names all 3 versions as containing the loop, but it does not say where in the code.

The Wayback Machine saved the 1.4.2 tarball on 10 January. I checked it against the integrity hash in the lockfile of ngx-ui-scroll. That repository had 1.4.2 installed on 9 January and rolled it back the same day with the message “tests don’t work with 1.4.2”. The hash matches. lib/index.js has the same loop. safe.js has it too, after a new comment:

/* attempting to patch existing bug */

So colors/safe was safe for 1 day only. I installed each of the 9 packages in the version current on 8 January with npm 8.1.2. Then I replaced every copy of colors in the tree with 1.4.2 and ran it under the same Node. 3 of them hang on require: logform, cli-progress and the Playwright test runner. The other 4 load fine as libraries and hang as soon as the part that uses colour is loaded. For the http-server and jscodeshift commands that is the start. karma hangs when it loads its reporter and cli-table3 on the first table with a coloured header. The 2 pinned packages work.

package range on 9 Jan with 1.4.2 end of 2022 logform ^1.2.1 1.4.2 hangs on require @colors/colors cli-progress ^1.1.2 1.4.2 hangs on require no colors @playwright/test ^1.4.0 1.4.2 hangs on require no colors cli-table3 ^1.1.2 1.4.2 hangs on first use @colors/colors karma ^1.4.0 1.4.2 hangs on first use @colors/colors jscodeshift ^1.1.2 1.4.2 hangs on first use no colors http-server ^1.4.0 1.4.2 hangs on first use no colors cli-table 1.0.3 1.0.3 works pinned 1.0.3 jasmine-spec-reporter 1.4.0 1.4.0 works pinned 1.4.0
Versions current on 8 January 2022, ranges resolved the way npm 8.1 does it, run under Node 16.13.1 with every copy of colors replaced by 1.4.2 from the Wayback Machine.

I don’t know what 1.4.1 did, because I found no copy of it, not even in the Wayback Machine. In ngx-ui-scroll 1.4.1 had been in the lockfile for 17 hours, through commits that fixed tests and the karma config. It was the version they first went back to. Their tests run through karma, which loads only colors/safe. That fits a 1.4.1 with the loop only in lib/index.js. But 90 minutes later they moved to 1.4.0 without saying why. And it is 1 repository.

How fast they left

7 of the 9 reacted within 5 days. cli-progress released a version without colors on 9 January and cli-table3 pinned 1.4.0 on the same day. 4 more followed on 10 January, among them logform and Playwright. karma did it on 13 January. By the end of the year 3 of the 9 depend on @colors/colors, a fork that DABH published in February. winston, which got colors through logform, moved to it too. DABH is also a maintainer of logform and winston.

faker got a community fork too, @faker-js/faker, 5 days after 6.6.6. faker itself was in the runtime dependencies of none of the 4366. It lives in devDependencies and 6.6.6 is a new major version, so ^5.5.3 still resolves to 5.5.3.

The name stays

This is the part that I find strange. On npmjs.com in December colors shows version 1.4.0 and faker shows 6.6.6. Both list 1 maintainer, marak. The broken colors versions were deleted but nobody moved the name. faker 6.6.6 is still latest and it has no code. The README is one line about Aaron Swartz and .github/FUNDING.yml still says github: marak. npm install faker today gives a package where require('faker') throws MODULE_NOT_FOUND.

million per month 0 50 100 colors @colors 0 5 10 faker @faker-js Jan 2021 Jan 2022 Nov 2022
npm downloads API, monthly sums. The vertical line is January 2022.

The downloads did not move much either. In November colors had 88.5 million downloads, 8% less than in November 2021, while @colors/colors had 47.7 million. faker had 11.0 million, 7% less than a year before, against 6.6 million for the fork. The npm API does not split downloads by version, so I can’t say how many of the 11 million are the old 5.5.3.

Of the 2 options from 2020, the fork is the one I can see. The top packages reacted within a week, most of them by pinning 1.4.0. But the name stayed with the author and most of the downloads still go to it. The fork of colors was published by DABH, whose own logform and winston depended on it. I suspect this is how a lot of infrastructure is held, but 9 packages are too few to say that.

What I did not check

Transitive dependents outside the top list. Most of the 88 million downloads of colors probably come from there. How many projects had a lockfile and never saw the broken versions. The code of 1.4.1. And the exact time npm deleted the 3 versions, because the registry does not show it.