A Fiverr buyer messaged me just after midnight. He needed "a senior developer with AI and web experience" to fix the invoice generation in an AI invoice app that another freelancer had built.

The project looked normal. But one line in its Tailwind config would have run an attacker's hidden code on my computer the moment I typed npm run dev.

TL;DR: The Tailwind config loaded an npm plugin. The plugin pulled in a private package that nobody could see, using an npm token shipped inside the project. I reported it, and npm took the packages down about 8 hours later.

The red flags I almost missed

The chat was friendly, but a few things did not add up:

  • No errors to show. He said the app gave "unknown errors", but he had no screenshot and no error log. He wanted me to run it myself.
  • A business overview before the code. The first file was a polished project overview "prepared for internal and investor review", not code.
  • Code by Google Drive, not GitHub. The project came as a zip in a Drive folder named "project2-invoice ai", owned by a Google account with a random-looking name. "project2" made me wonder who got "project1".
  • Docs that didn't match the code. The README described a React + Vite app with Clerk login. The code was Next.js 16 with its own JWT login. The setup guide mentioned start.bat and QUICK_START.md, which were not in the zip.
  • The push to run it. Every guide in the project said the same thing: run npm run install:all, then npm run dev.

How the trap worked

The trap was a chain of four small pieces. Everything else in the project was clean: an Express API, MongoDB models, Gemini calls and React components. That is on purpose. If you only review the app code, you find nothing.

1. A dev dependency. frontend/package.json lists tailwind-animationlab. This plugin was published on npm on September 28, the same day the project's Tailwind config was last edited.

2. A plugin nobody uses. frontend/tailwind.config.js is full of friendly comments. At the very end, it loads the plugin:

plugins: [ require('tailwind-animationlab')({ duration: '500ms', delay: '1s', iterationCount: '2' }) ], 

Not one component uses a class from this plugin. It is there only to be loaded.

3. A package nobody can see. The plugin looks like a normal Animate.css helper. But one of its files loads another package:

// tailwind-animationlab/src/motion.js const postcssAnimateUtils = require('@jasperquinn/postcss-motion-helpers'); 

That package was not on the public npm registry. A normal install of it gave a 404.

4. The key, shipped inside the project. frontend/.npmrc held an npm access token (hidden here):

//registry.npmjs.org/:_authToken=npm_**************************** 

What one npm run dev would have done: npm install uses that token to download the hidden package. Then Next.js starts, Tailwind loads its config, the config loads the plugin, and the plugin loads the hidden package. From that moment, the attacker's code runs with the same access to your files that you have.

I never saw the hidden code, because I did not use the attacker's token. In similar fake-job campaigns, this kind of code stole saved browser passwords, API tokens and crypto wallets, and installed a backdoor.

How I checked it without running anything

I never downloaded the zip to my own computer. I asked Claude, an AI assistant, to pull it from Google Drive into an isolated cloud sandbox. It read every file as plain text and never ran npm install or any script.

It listed the archive, read each package.json and config file, and scanned the code for obfuscation, odd URLs and hidden lines. The backend and components came back clean. The token in .npmrc was the first clue. The full trap only showed up when it downloaded the plugin's code from npm and read it.

The lesson: the danger is not always in the code you can read. Here it was in a package the project downloads.

Reporting it, and what npm did

I reported tailwind-animationlab through npm's support form, with the full chain and the token. npm took it down about 8 hours later. The attackers needed only about 4.5 hours to publish a replacement.

When (Sri Lanka time, UTC+5:30) What happened
Oct 29, around noon I report tailwind-animationlab to npm
Oct 29, 20:24 npm replaces tailwind-animationlab and tailwind-keyframes (same author) with security placeholder packages
Oct 29, 20:45 npm removes the hidden @jasperquinn/postcss-motion-helpers
Oct 30, 01:08 A new hidden helper, @nexlayerlabs/postcss-keyframe-utils, is published
Oct 30, 18:08 npm removes that one too
Oct 30, 18:18 GitHub Trust & Safety emails me: "one or more violations of npm's Open-source Terms have occurred" and action was taken

It's not just one package

After the takedown, I searched npm for the plugin's exact description: "A configurable, namespaced Animate.css integration for Tailwind CSS". I found at least 19 more packages from 12 other npm accounts, built from the same code. 14 of them were first published on September 28. The rest appeared on September 29, October 23 and October 26.

Each one depends on a hidden helper package:

Hidden helper Look-alike packages Status on Oct 30
@jasperquinn/postcss-motion-helpers 11 Removed by npm on Oct 29
@aaron205whitmore/postcss-animate-utils 7 Not public; reported to npm
@nexlayerlabs/postcss-keyframe-utils 1 Removed by npm on Oct 30

This fits a bigger pattern. In October 2025, researchers linked 338 malicious npm packages to the "Contagious Interview" campaign, which uses fake recruiters and coding tasks. I can't say who is behind this one, but the method is the same.

How to protect yourself

If a client or recruiter sends you code to run, check these first:

  • Don't run it on your main computer. If you must run it, use a throwaway VM or cloud sandbox with no saved passwords, keys or wallets.
  • Read before you install. Check package.json and every config file that runs during dev: Tailwind, PostCSS, Next.js, Vite and ESLint.
  • Treat an .npmrc with a token as a red flag. A normal project has no reason to ship someone's npm access token.
  • Look up every unfamiliar dependency. On npmjs.com, check its age, weekly downloads, version history and its own dependencies. A dependency you can't see publicly is a red flag.
  • Watch for plugins that do nothing. A plugin that is configured but never used is there for a reason.
  • Ask for a paid order and real error logs first. A real client has both.
  • Report it. Use npm's support form (report malware), the freelance platform's report button and Google Drive's report option.

Indicators

For security teams, here are the npm names I found. The first two were already removed when I checked on October 30.

Package npm account Hidden helper
tailwind-animationlab (removed) adriannamrr @jasperquinn/postcss-motion-helpers
tailwind-keyframes (removed) adriannamrr removed before I could check
animatecss-tailwind-presets elony16 @jasperquinn/postcss-motion-helpers
motion-helpers-compat elony16 @jasperquinn/postcss-motion-helpers
animate-twcss-pack enterprise0908 @jasperquinn/postcss-motion-helpers
animatecss-tailwind-luminary cypherbyte @jasperquinn/postcss-motion-helpers
tailwind-kinetix ronald9373 @jasperquinn/postcss-motion-helpers
tailwindcss-motionanimate doublekk61 @jasperquinn/postcss-motion-helpers
animatecss-tailwind-helper hannahgibbs @jasperquinn/postcss-motion-helpers
tailwind-css-keyframes kinsley_badu @jasperquinn/postcss-motion-helpers
tailwind-animatecss-kit killerdev1127 @jasperquinn/postcss-motion-helpers
animatecss-tailwind-binary jamesvectorpack @jasperquinn/postcss-motion-helpers
animate-tailwind-kit johnxia1997 @jasperquinn/postcss-motion-helpers
animatecss-tailwind-keyframes elony16 @aaron205whitmore/postcss-animate-utils
animatecss-tailwind-vectrix cypherbyte @aaron205whitmore/postcss-animate-utils
tailwind-kinetics isaac9089 @aaron205whitmore/postcss-animate-utils
signalledger-diagnostics scott-rogers @aaron205whitmore/postcss-animate-utils
animate-twcss-bridge enterprise0908 @aaron205whitmore/postcss-animate-utils
animatecss-tailwind-plugin hannahgibbs @aaron205whitmore/postcss-animate-utils
tailwindcss-animatemotion doublekk61 @aaron205whitmore/postcss-animate-utils
signalledger-bridge scott-rogers @nexlayerlabs/postcss-keyframe-utils

Update, November 5

The campaign is still running. On November 2 I found a newer version of tailwind-kinetics (2.0.10) that depends on a different hidden helper, @unipswap/postcss-animate-units. I also found two more hidden helpers, @scott-rogers/postcss-signal-bridge and @doublekk61_pro/postcss-motion-animation. None of the three can be viewed on the public registry. The pattern is the same every time: a look-alike Animate.css plugin, published recently, that depends on a package you can't see.

I wrote this post with help from Claude, an AI assistant, which also did the read-only analysis described above. I checked the findings myself. I'm also working on a free, open-source checker for this kind of trap, and I'll share it here when it's ready.

Sources: tailwind-animationlab on npm · @jasperquinn/postcss-motion-helpers on npm · 338 Malicious npm Packages Linked to North Korean Hackers