npm Blog (Archive)

The npm blog has been discontinued.

Updates from the npm team are now published on the GitHub Blog and the GitHub Changelog.

Incident report: npm, Inc. operations incident of January 6, 2018

On Saturday, January 6, 2018, we incorrectly removed the user floatdrop and blocked the discovery and download of all 102 of their packages on the public npm Registry. Some of those packages were highly depended on, such as require-from-string, and removal disrupted many users’ installations.

On Sunday, we published an initial blog post to clarify that this issue was an internal operations issue and not a security issue, but at the time of that post we lacked many details because we had not yet conducted npm’s post-incident retrospective process. This disclosure follows our retrospective and goes into detail about how this mistake happened and what actions we’ve already taken and will take to prevent similar incidents.

A full list of the affected packages is at the end of this post.

Root cause

npm’s automated spam analysis process examines every package publication for signals that a package may be spam. These signals include data about the publisher as well as the package’s README.

On this date, a package was published that contained spam content plus the README for floatdrop’s legitimate package timed-out. Because of the matching READMEs, our spam system flagged floatdrop as associated with the spammer. In the course of reviewing and acting on spam reports, an npm staffer acted on this flag without further investigating the user and removed the user and all of their packages from the registry.

Within 60 seconds, it became clear that floatdrop was not a spammer—and that their packages were in heavy use in the npm ecosystem. The staffer notified colleagues and we re-activated the user and began restoring the packages to circulation immediately.

Most of the packages were restored quickly, because the restoration was a matter of unsetting the deleted tombstones in our database, while also restoring package data tarballs and package metadata documents. However, during the time between discovery and restoration, other npm users published a number of new packages that used the names of deleted packages. We locked this down once we discovered it, but cleaning up the overpublished packages and inspecting their contents took additional time.

Background

When are packages and accounts removed?

As a general rule, the npm Registry is and ought to be immutable, just like other package registries such as RubyGems and crates.io. The basis of open source software development requires that developers are able to depend on the code they build into their projects. In addition, a large global network of mirrors and caches means that removing a package from npm’s Registry doesn’t really make it “go away,” anyway.

However, there are legitimate cases for removing a package once it has been published.

In a typical week, most of the npm support team’s work is devoted to handling user requests for package deletion, which is more common than you might expect. Many people publish test packages then ask to have them deprecated or deleted. There also is a steady flow of requests to remove packages that contain contain private code that users have published inadvertently or inappropriately.

As of 2016, users are unable to delete packages more than 24 hours after they are published. npm staff must evaluate these requests on a case-by-case basis to assess the risk of negative effects on other developers’ projects if a dependency is removed. In many of these cases, deprecating the package instead of deleting it solves the user’s problem, but for some, removing the package from the registry is the best solution.

The second broad category of deletions is when npm removes a package from the registry because it contains problematic content. Malware or spam are examples of content we will delete. We are obligated to remove packages that would harm others or violate the law, and we also believe in keeping the registry free from packages that serve no valid purpose.

Spam—packages that are either blank or populated with someone else’s code, with READMEs that attempt to direct traffic to another website—has become a far larger problem in the npm Registry in the last year. This is an unwelcome side effect of our community’s popularity, because packages’ pages on the npmjs.com site have become highly ranked in search engines.

Fortunately, our understanding of the problem has also increased as our tooling gets better at surfacing it to us. Working with Smyte, we have developed systems to analyze package contents as they are published, as well as flag users with problematic posting habits or associations with previously detected spam. These flags are posted in a Slack channel for review by npm support staff. We then take a closer look at the details of why the user or package was flagged, and, when we feel it is appropriate, remove it from the registry.

To support these common workflows, we have internal tools for removing packages and user accounts in one action, steps we have taken many thousands of times in the last few months. Deleting a user’s account is the most common action we take in response to spam and it was this tool that was implicated in Saturday’s incident. Our systems incorrectly flagged floatdrop, and npm personnel mistakenly removed their account.

When are package names reused?

Another general principle, and a corollary to our prohibition against removing packages 24 hours after they’re published, is that a package name and version should not be reused on the registry. If I publish foo@1.2.3 and other developers depend on this package in their projects, it is bad for me to remove this package and break their dependencies—but it’s even worse to publish a new foo@1.2.3 that does something else. Everyone whose projects depend on this package would pull in the new code automatically, leading to potentially disastrous results.

In cases where the npm staff accepts a user’s request to delete a package, we publish a replacement package by the same name—a security placeholder. This both alerts those who had depended on it that the original package is no longer available and prevents others from publishing new code using that package name. At the time of Saturday’s incident, however, we did not have a policy to publish placeholders for packages that were deleted if they were spam. This made it possible for other users to publish new versions of eleven of the removed packages.

After a thorough examination of the replacement packages’ contents, we have confirmed that none was malicious or harmful. Ten were exact replacements of the code that had just been removed, while the eleventh contained strings of text from the Bible—and its publisher immediately contacted npm to advise us of its publication. As many in the npm community have pointed out, however, this oversight could have enabled malicious code to be published and downloaded by users with dependencies on the original packages. We consider this an unacceptable security risk.

Timeline

All times here are in UTC to place them in context with our status incident.

18:36 — floatdrop user deleted

18:43 — floatdrop notified by email

18:58 — first report of require-by-string failing installations

19:17 — user restored; package restoration commences

19:43 — status incident posted to status.npmjs.org

20:12 — all but the 11 over-published packages are restored

21:58 — overpublished packages are restored, review in progress

22:35 — status incident closed

Steps we’re taking in response

An apology

Our systems and processes balance the need to eliminate spam with the need to reduce false positives. However, we failed to address the need to recover swiftly and cleanly from human error. We will continue to incorporate this understanding in the design and implementation of systems in the future.

We apologize for this mistake. We further apologize to everyone who experienced broken installations during this incident. We know you rely on us to be an invisible and reliable part of your JavaScript development infrastructure, and on Saturday we were not.

Affected packages

These 11 packages were republished by other users:

create-error-class
duplexer3
gulp-plumber
infinity-agent
is-retry-allowed
pinkie
pinkie-promise
read-all-stream
require-from-string
vinyl-git

This is the full list of all 102 affected packages:

@floatdrop/duplexer2
@floatdrop/express-co
after-event
bem-deps
bem-object
bem-pack
bemjson-to-html
bh-property-helpers
cacha
capture-stack-trace
cctz
chnpm
co-with-promise
configs-overload
connect-once
create-error-class
dag
debug-http
dependencies-diff
deps-graph
deps-normalize
dns-graceful-stack-switch
dns-gracefull-stack-switch
duplexer3
each-done
enb-browserify
express-cocaine-service
express-dinja
express-error-with-sources
express-generators
express-mongo-db
express-mongoose-db
express-public-ip
express-real-ip
express-render-jsx
express-request-id
express-stackman
flatit
funsert
get-iterable
glue-streams
got-promise
gulp-batch
gulp-bem
gulp-bem-debug
gulp-bem-js-pack
gulp-bem-pack
gulp-bh
gulp-ext
gulp-grep-stream
gulp-plumber
gulp-reload
gulp-start
gulp-start-process
gulp-watch
hashdir
httpinkie
ignore-middleware-error
infinity-agent
is-retry-allowed
jsot
jsot-bh
memorize-middleware
memorize-promise
migratio
missing-middleware-error
nested-prop
npmup
object-match-statement
p-batch
parse-bem-identifier
parsetrace
path2glob
pff
pg-parade
pinkie
pinkie-promise
plugin-jsx
protoscop
proxy-support
qwish
react-styled
read-all-stream
read-streams
reque
require-from-string
require-or-die
save-stream
sent
snap-context
stream-assert
stream-dirs
tech-deps.js
timed-out
tobe
update-my-deps
vinyl-git
wrap-middleware
y-header
y-tabs
yajob
yandex-photos