On NPM, left-pad, and Azer Koçulu’s modules

My working day today started with the drama of 273 node modules being removed from a public repository everyone uses, with one module in particular – “left-pad” – breaking a surprisingly large number of other modules. Talk about a great disturbance in the Force, as if millions of voices suddenly cried out in terror, and were suddenly silenced.

The author of the module posted the reason for his actions: https://medium.com/@azerbike/i-ve-just-liberated-my-modules-9045c06be67c#.xp2dkmk69 and while I mostly agree with him, I do wish the impact weren’t quite so large. A list of the modules he removed was also posted: https://gist.githubusercontent.com/azer/db27417ee84b5f34a6ea/raw/50ab7ef26dbde2d4ea52318a3590af78b2a21162/gistfile1.txt

Apart from breaking application deployments and causing inconvenience, there’s also the very real risk of malicious code being pushed up to the NPM repository under the names of these removed modules, so I did some digging…

Several people have already registered some of the modules names on the NPM repository, hopefully to replace the modules with their previous version or prevent people from doing something malicious as mentioned above:

hypnza: 1
ccbikai: 1
westlac: 1
strml: 1
msanford: 1
ehsalazar: 2
hassoncs: 2
iclanzan: 5
backup: 5
kazmer: 8
case: 8
nj48: 238

The list of module names and their new owners is:

abril-fatface: hypnza
ada: ccbikai
after-time: strml
alert: iclanzan
andthen: nj48
anglicize: nj48
ansi-codes: nj48
atbash: nj48
attr: nj48
attrs: nj48
available-slug: nj48
background-image: nj48
ballet: nj48
bind-key: nj48
binding: nj48
blending-modes: nj48
boxcars: nj48
brick: case
brick-browser: nj48
brick-browserify-plugin: nj48
brick-node: nj48
browserify-length: nj48
bud-babelify: nj48
bud-concat: nj48
bud-indexhtml: nj48
bud-live-server: nj48
call-all: nj48
categorize-files: nj48
center-box: nj48
centered: nj48
centered-cover-background: nj48
change-object: nj48
change-object-path: nj48
checkfor: nj48
circle: case
cli-form: nj48
comma-list: nj48
comp: nj48
concat: nj48
config-doc: nj48
core-modules: nj48
cover-background: nj48
create-temp-dir: nj48
debounce-fn: nj48
declarative-js: nj48
default-debug: nj48
delegate-dom: nj48
dom-children: nj48
dom-classes: nj48
dom-event: nj48
dom-select: nj48
dom-style: nj48
dom-tree: nj48
dom-value: nj48
domflow: kazmer
domquery: nj48
door: nj48
duba: nj48
eksi-server: nj48
eksi-sozluk: nj48
english-time: nj48
environ: nj48
every-time: nj48
expand-home-dir: nj48
failing-code: nj48
failing-line: nj48
filename-id: nj48
filter-stack: nj48
findall: nj48
first-val: nj48
flat-glob: nj48
flatten-array: nj48
flickr-client: nj48
flickr-favorites: nj48
flickr-following: nj48
flickr-generate-urls: nj48
flickr-photo-brick: nj48
flickr-photo-info: nj48
flickr-photo-urls: nj48
flickr-recent: nj48
flickr-user: nj48
flickr-user-feed: nj48
fog: case
format-date: nj48
format-text: nj48
fox: case
functools: nj48
genpkg: nj48
get-json: nj48
get-object-path: hassoncs
gezi: nj48
gezi-core: nj48
go: case
go-api-starter: nj48
goodeggs-list: nj48
goodeggs-login: nj48
hide: nj48
html-patcher: nj48
htmlglue: nj48
iframe: nj48
ignore-doc: nj48
img: kazmer
indev: kazmer
indexhtml: nj48
indexhtml-cli: nj48
infinite-scroll: nj48
install-module: nj48
is-node: nj48
iter: nj48
join-params: nj48
jsify: nj48
json-resources: nj48
just-a-browserify-server: nj48
just-a-server: nj48
just-next-tick: nj48
juxt: nj48
key-event: nj48
keyname-of: nj48
keynames: nj48
kik: ehsalazar
kik-starter: ehsalazar
kurdish-time: nj48
left-pad: westlac
less-common-words: nj48
level-client: nj48
level-flush: nj48
level-json: nj48
level-json-cache: nj48
level-json-wrapper: nj48
limited-parallel-loop: nj48
local-debug: nj48
lowkick: nj48
make-editable: nj48
map: iclanzan
matches-dom-selector: nj48
measure-time: nj48
media: nj48
medium-author: nj48
medium-post: nj48
memdiff: nj48
memoize-async: nj48
memoize-sync: nj48
memoize-with-leveldb: nj48
meta-tags: nj48
methodify: nj48
midi-instrument: nj48
midi-sdk: nj48
midibin-api: nj48
mime-of: nj48
mix-objects: nj48
most-common-turkish-words: nj48
most-common-words: nj48
mp3s: nj48
new-chain: nj48
new-command: nj48
new-element: nj48
new-empty-array: nj48
new-error: nj48
new-format: nj48
new-list: nj48
new-object: nj48
new-partial: nj48
new-prop: nj48
new-pubsub: nj48
new-range: nj48
new-reactive: nj48
new-struct: nj48
next-time: case
observer: nj48
on-key-press: nj48
on-off: nj48
one: case
ourtunes: nj48
outer-html: nj48
package-path: nj48
parallel-loop: nj48
parallelly: nj48
parse-path: nj48
pause-function: nj48
personal-api: nj48
play-audio: nj48
play-url: nj48
playfair-display: nj48
post-data: nj48
pref: nj48
prettify-error: nj48
prompt-input: nj48
propertify: nj48
property: nj48
provinces: nj48
pt-mono: nj48
pt-serif: nj48
pubsub: iclanzan
radio-paradise-api: nj48
random-color: nj48
rdio-js-api: nj48
read-cli-input: nj48
read-json: nj48
redux-starter: kazmer
refine-object: nj48
relative-date: nj48
remotely: nj48
require-sdk: nj48
right-pad: nj48
rimraf-glob: kazmer
rm-rf: kazmer
rname: kazmer
rnd: kazmer
route-map: nj48
run-after: nj48
run-paralelly: nj48
run-serially: nj48
sanitize-object: nj48
scrape-eksi: nj48
scrape-pages: nj48
scrape-url: nj48
scraping-eksi: nj48
scroll-bottom: nj48
select-dom: nj48
serial-loop: nj48
serially: nj48
set-content-type: nj48
set-object-path: hassoncs
setup-docker: nj48
shell-jobs: nj48
show-version: nj48
shuffle-array: nj48
simple.io: nj48
simulate-touch: nj48
slug-to-title: nj48
socks-browser: nj48
soundcloud-stream: nj48
stream-format: nj48
strip: nj48
style-dom: nj48
style-format: nj48
styled: iclanzan
subscribe: nj48
subscription: nj48
title-from-url: nj48
to-class-name: nj48
to-slug: nj48
to-title: nj48
toba-batak-dictionary: nj48
toledo-chess: nj48
try-call: nj48
turkish-alphabet: nj48
turkish-synonyms-api: nj48
turkish-time: nj48
unique-now: nj48
uniques: nj48
userbook: iclanzan
uzo: nj48
validate-value: nj48
variable-name: nj48
video-canvas: nj48
video-dimensions: nj48
virtual-glue: nj48
virtual-html: backup
virtualbox: msanford
watch-array: backup
web-assets: backup
with-env: backup
wysiwyg: case
youtube-video: backup

Most of the modules are owned by a user named “nj48”, and when installing the modules we can see how and why – his modules contain a “x.sh” bash script which loops through each modules name, generates a “package.json” file for it, then “npm publish”es it:

A=”$1″

echo ‘{
“name”: “‘”$A”‘”,
“version”: “2.0.0”,
“description”: “”,
“main”: “index.js”,
“scripts”: {
“test”: “echo \”Error: no test specified\” && exit 1″
},
“author”: “”,
“license”: “ISC”
}’ > package.json

npm publish

So far these packages contain just the generated “package.json” file, “x.sh” (the script above) and a file named “x” (which is just a list of the modules) – luckily no malicious code… (yet?)

Only the following 18 modules seem to have Javascript code in them when installed (which seems legitimate unless otherwise stated next to the name) :

ada – does a console.log(‘ada’)
alea
core-util-is
expand-home-dir
get-object-path
img
indev
left-pad
map
read-json
redux-starter
relative-date
rimraf-glob – does an “rm -Rf” (as per module’s purpose)
rm-rf – does an “rm -Rf” (as per module’s purpose)
rnd
set-object-path
try-call
virtualbox – does exec() (as per module’s purpose)

So far nothing too evil looking, and a bunch of broken packages, but definitely worth keeping an eye on https://www.npmjs.com/~nj48 as he controls the majority of these names.

UPDATE: The user who managed to grab most of the package names (“nj48”) has written a post on Medium about it: https://medium.freecodecamp.com/npm-package-hijacking-from-the-hijackers-perspective-af0c48ab9922

Leave a Reply

Your email address will not be published. Required fields are marked *