tailwind Animate.css npm plugin cluster lures developers through fake job interviews
GitHub ingested 26 npm advisories (MAL-2026-18065 through MAL-2026-18090) on 2026-10-20 attributing a fake job interview supply-chain campaign tracked as MAL-2026-17665 / MAL-2026-17666 / MAL-2026-17667. The packages are Tailwind CSS plugins that reuse an identical Animate.css integration template and hide their payload inside a private scoped dependency (@aaron205whitmore/postcss-animate-utils or @jasperquinn/postcss-motion-helpers) that resolves only when a candidate clones the lure repo whose web/.npmrc carries an npm token.
Published 20 Oct 2026
Every version named by the advisory is in the scanner, so a lockfile check finds it directly.
- Severity
- High
- Type
- Malicious package
- Published
- 20 Oct 2026
- Ecosystems
- npm
- Detected by
- OpenSSF malicious-packages, GitHub Advisory Database
- Also known as
- Tailwind Animate.css fake interview npm campaign, MAL-2026-17665 campaign, MAL-2026-17666 campaign
- Source
- github.com
Malicious package. A package that was malicious from its first publish under its own name - a fake tool, a lure used in a social-engineering campaign, or a trojanised utility - rather than a copy of an existing name.
The full picture
On 2026-10-20 the GitHub Advisory Database ingested 26 npm malware advisories (MAL-2026-18065 through MAL-2026-18090) attributing a coordinated supply-chain campaign the OpenSSF record calls a fake job interview campaign. The 26 packages are Tailwind CSS plugins that reuse an identical Animate.css integration template, README and description. The GHSA bundle cross-references three campaign-level MAL IDs (MAL-2026-17665, MAL-2026-17666, MAL-2026-17667). Public reporting on MAL-2026-17667 (tailwind-animatecss-uniform@2.0.7) ties the campaign to a shell company called Nodveta advertising a "backend engineer assessment" on the Torre.ai hiring platform.
Attack mechanics
Every one of the 26 published tarballs is intentionally clean on npm install: no install scripts, no network code, no obfuscated loader. The payload is NOT in the tarball. Each tarballs src/motion.js line 5 contains a single require() of a private scoped package - either @aaron205whitmore/postcss-animate-utils or @jasperquinn/postcss-motion-helpers. Those scopes are not public: a normal npm install cannot resolve them and will error out. The lure that makes resolution succeed is the committed web/.npmrc inside the lure repository given to candidates during the fake interview - that .npmrc carries an npm authentication token with read access to the private scope. When the candidate runs npm install inside the lure repo, the private dependency resolves and the payload lands on their machine. Execution happens the moment the candidate starts the dev server (npm run dev, pnpm dev, Vite / Next.js / Nuxt / Astro / Remix / Qwik / plain Tailwind) because tailwind.config.js loads the plugin at startup, which triggers the src/motion.js:5 require and the private payload runs inside the dev servers Node process. --ignore-scripts is irrelevant because there are no scripts to ignore.
The content of the private payload is not available for public analysis, because the private scope is still gated. Based on operator shape and parallel campaigns (DPRK Contagious Interview, which uses a very similar committed-token lure vector), the plausible payload surface is credential harvesting from the dev process (browser-password-store files via Chromium debug protocol, Claude / Cursor / Windsurf API tokens from local config, SSH keys, cloud CLI credentials, every .env the dev process can read, every crypto-wallet seed phrase on the machine) with Telegram or webhook exfil. This is pattern-matched inference, not confirmed.
Operator shape
The 26 packages were published between 2026-07-28 and 2026-10-09 under ~14 distinct maintainer accounts. The private-dependency split is bimodal: 13 of 26 require @aaron205whitmore/postcss-animate-utils, 12 require @jasperquinn/postcss-motion-helpers, 1 (tailwind-animatecss-uniform) uses MAL-2026-17667 as campaign root. Multiple maintainer accounts publish two packages each with the same private-scope choice (enterprise0908, keinsell-jh, scott-rogers, doublekk61, hackson306623, johnsmith0821, cypherbyte, hannahgibbs, jamesvectorpack, adriannamrr, killerdev1127 all run paired slugs), suggesting a small operator team rather than 17 unrelated actors. The GHSA bundle was ingested in a single OpenSSF batch (consecutive MAL IDs 18065-18090) on 2026-10-20, which is where this record anchors.
Classification
malicious-package. Severity high: confirmed malicious payload mechanism (private-dependency require from Tailwind plugin entry point) with explicit social-engineering delivery (fake backend-engineer assessment from Nodveta on Torre.ai), but limited reach - no legitimate upstream was hijacked, no worm propagation, and the private-scope resolution gating means only developers who clone a lure repo AND run its dev server inside it get the payload. Not critical because none of the 26 slugs is a package anyone would depend on in normal operation.
Discovery
The OpenSSF malicious-packages project (credit field on every GHSA record). Public summary via OSV (MAL-2026-17667) links the campaign to the Nodveta / Torre.ai fake backend-engineer-assessment lure.
28 packages 41 versions
- npmanimate-tailwind-kit3.0.13.0.4→
- npmanimate-twcss-bridge2.0.6→
- npmanimate-twcss-pack2.0.6→
- npmanimatecss-tailwind-binary2.0.6→
- npmanimatecss-tailwind-bridge*→
- npmanimatecss-tailwind-graphic2.0.6→
- npmanimatecss-tailwind-helper2.0.6→
- npmanimatecss-tailwind-luminary2.0.62.0.7→
- npmanimatecss-tailwind-plugin2.0.6→
- npmanimatecss-tailwind-presets1.0.02.0.3→
- npmanimatecss-tailwind-vectrix2.0.62.0.7→
- npmsignalledger-bridge2.0.6→
- npmsignalledger-diagnostics2.0.6→
- npmtailwind-animaflow-kit2.0.6→
- npmtailwind-animate-kit2.0.62.0.72.0.8→
- npmtailwind-animatecss-kit2.0.6→
- npmtailwind-animatecss-uniform2.0.7→
- npmtailwind-animationlab2.0.6→
- npmtailwind-css-keyframes1.0.7→
- npmtailwind-fabric3.0.13.0.4→
- npmtailwind-keyframes2.0.6→
- npmtailwind-kinetic2.0.62.0.8→
- npmtailwindcss-animate-catalog2.0.62.0.7→
- npmtailwindcss-animatemotion1.0.7→
- npmtailwindcss-effect-lab0.0.0-stage1.6.7→
- npmtailwindcss-keyframe-kit3.0.03.0.13.0.2→
- npmtailwindcss-motionanimate1.0.1→
- npmtailwindcss-visual-kit0.0.0-stage1.3.6→
These are usually pulled in as transitive dependencies rather than installed directly. Check your whole tree at once. It runs in your browser and nothing is uploaded.
What it does if you installed it
- Published packages (26 npm packages, 14+ maintainer accounts, published 2026-07-28 through 2026-10-09, GHSA batch ingested 2026-10-20):
animate-tailwind-kit3.0.1 and 3.0.4 (publisherjohnxia1997, MAL-2026-18065, GHSA-q29m-3m5r-fmjj);animate-twcss-bridge2.0.6 (enterprise0908, MAL-2026-18066, GHSA-v2vq-rvhc-qqr9);animate-twcss-pack2.0.6 (enterprise0908, MAL-2026-18067, GHSA-jr8w-9rvh-vv8p);animatecss-tailwind-binary2.0.6 (jamesvectorpack, MAL-2026-18068, GHSA-5fhm-3gq2-6gj8);animatecss-tailwind-bridgeall versions (MAL via GHSA-3h7p-p7hr-fqf7);animatecss-tailwind-graphic2.0.6 (jamesvectorpack, MAL-2026-18069, GHSA-mp8w-g7p9-w5v3);animatecss-tailwind-helper2.0.6 (hannahgibbs, MAL-2026-18070, GHSA-8rrv-xcpx-rv3r);animatecss-tailwind-luminary2.0.6 and 2.0.7 (cypherbyte, MAL-2026-18071, GHSA-vj87-h2x3-2985);animatecss-tailwind-plugin2.0.6 (hannahgibbs, MAL-2026-18072, GHSA-gqf2-w3vm-g87w);animatecss-tailwind-presets1.0.0 and 2.0.3 (elony16, MAL-2026-18073, GHSA-gh2v-353h-8jhq);animatecss-tailwind-vectrix2.0.6 and 2.0.7 (cypherbyte, MAL-2026-18074, GHSA-hcfv-m7w3-vcch);signalledger-bridge2.0.6 (scott-rogers, MAL-2026-18075, GHSA-h368-qgvj-jhgw);signalledger-diagnostics2.0.6 (scott-rogers, MAL-2026-18076, GHSA-q9pp-3583-37vv);tailwind-animaflow-kit2.0.6 (killerdev1127, MAL-2026-18077, GHSA-2hh6-9wwc-7j9w);tailwind-animate-kit2.0.6, 2.0.7 and 2.0.8 (keinsell-jh, MAL-2026-18078, GHSA-pvxf-hc22-hp2x);tailwind-animatecss-kit2.0.6 (killerdev1127, MAL-2026-18079, GHSA-q56v-wqww-c9pm);tailwind-animatecss-uniform2.0.7 (MAL-2026-17667);tailwind-animationlab2.0.6 (adriannamrr, MAL-2026-18080, GHSA-wrp2-f9jg-g5qx);tailwind-css-keyframes1.0.7 (kinsley_badu, MAL-2026-18081, GHSA-qpfh-v8qh-gq5m);tailwind-fabric3.0.1 and 3.0.4 (johnxia1997, MAL-2026-18082, GHSA-jm3w-rxjc-9rp6);tailwind-keyframes2.0.6 (adriannamrr, MAL-2026-18083, GHSA-94m4-x888-pgf4);tailwind-kinetic2.0.6 and 2.0.8 (keinsell-jh, MAL-2026-18084, GHSA-h6pg-m44p-2x74);tailwindcss-animate-catalog2.0.6 and 2.0.7 (hackson306623, MAL-2026-18085, GHSA-6hg9-v89p-9w82);tailwindcss-animatemotion1.0.7 (doublekk61, MAL-2026-18086, GHSA-jfww-6c2q-w9rr);tailwindcss-effect-lab0.0.0-stage and 1.6.7 (johnsmith0821, MAL-2026-18087, GHSA-259h-r4vh-ph5j);tailwindcss-keyframe-kit3.0.0, 3.0.1 and 3.0.2 (hackson306623, MAL-2026-18088, GHSA-8vv2-m423-rcx4);tailwindcss-motionanimate1.0.1 (doublekk61, MAL-2026-18089, GHSA-jqm8-4v5f-g79x);tailwindcss-visual-kit0.0.0-stage and 1.3.6 (johnsmith0821, MAL-2026-18090, GHSA-m8c8-f5hx-664g). Each GHSA record cross-references the campaign bundle at MAL-2026-17665, MAL-2026-17666 and MAL-2026-17667. - Attack mechanics (identical across all 26 packages per GHSA batch): the published tarball has no install scripts and no network code, so
npm install --ignore-scriptsand standard install-time scanners see a clean package. Each tarballssrc/motion.jsline 5 requires a private scoped dependency - either@aaron205whitmore/postcss-animate-utils(seen in theanimate-twcss-*,animatecss-tailwind-bridge/graphic/helper/plugin/vectrix,signalledger-diagnostics,tailwind-animaflow-kit,tailwind-fabric,tailwind-keyframes,tailwind-kinetic,tailwindcss-animatemotion,tailwindcss-animate-catalogandtailwindcss-keyframe-kitsubgroup) or@jasperquinn/postcss-motion-helpers(seen in theanimate-tailwind-kit,animatecss-tailwind-luminary/presets,signalledger-bridge,tailwind-animate-kit,tailwind-animatecss-kit,tailwind-animationlab,tailwind-css-keyframes,tailwindcss-visual-kitandtailwindcss-motionanimatesubgroup). Those scopes are not readable from the public registry: resolution only succeeds when the candidate has cloned the lure repository and runsnpm installinside it, because the lure repositorysweb/.npmrccarries an npm token with read access to the private scope. Execution happens whentailwind.config.jsloads the plugin and the dev server (npm run dev,pnpm dev, Next.js / Vite / Nuxt / Astro / Remix / Qwik / etc.) starts, which is the first thing any candidate will do during the fake interview assignment. The published tarball`s payload (post-config-load) was not retrieved during OSSF analysis because the private dependency is still gated. - Lure delivery (fake job interview / "Contagious Interview" clone): public reporting on MAL-2026-17667 ties the campaign to a fake "backend engineer assessment" from a shell company calling itself Nodveta, advertised on the Torre.ai hiring platform. The template handed to candidates is a repository containing a
web/folder (a Next.js / Tailwind CSS app with an Animate.css integration challenge) and a committedweb/.npmrcwhose//registry.npmjs.org/:_authToken=line gives read access to the private@aaron205whitmoreor@jasperquinnscope. The shape, cadence (fake recruiters contacting backend / front-end engineers with a short take-home assignment that must be run locally) and the private-registry-token delivery mechanism closely parallel the DPRK "Contagious Interview" / Famous Chollima operation documented across 2025 and early 2026 (see our stegabin incident and the cross-ecosystem April 2026 wave), but no advisory has formally attributed this batch to that operator and the "Nodveta" / Torre.ai front is new - Reach: narrower than the 26-package count suggests. These are first-publish lures under one-off maintainer accounts; nobody who did not clone the lure repo and run its
web/project is affected, because the payload is in a private scope that will not resolve outside the lure repo. Weekly download counts for every slug are in the single digits. The risk sits on candidates - frontend / backend engineers specifically being recruited through Torre.ai / fake backend-engineer-assessment postings - rather than on the general npm population. Treat as a targeted-social-engineering campaign, not a mass-exposure incident - Operator continuity signals: multiple shared publisher-account signals suggest a small, coordinated operator rather than 17 unrelated actors - the subgroup
@aaron205whitmorevs@jasperquinnprivate-scope split correlates with maintainer accounts (johnxia1997consistently points to@aaron205whitmoreacrosstailwind-fabric;enterprise0908runs both halves ofanimate-twcss-bridgeandanimate-twcss-pack;keinsell-jhruns bothtailwind-animate-kitandtailwind-kinetic;scott-rogersruns bothsignalledger-bridgeandsignalledger-diagnostics;doublekk61runs bothtailwindcss-animatemotionandtailwindcss-motionanimate;hackson306623runs bothtailwindcss-animate-catalogandtailwindcss-keyframe-kit;johnsmith0821runs bothtailwindcss-visual-kitandtailwindcss-effect-lab;cypherbyteruns bothanimatecss-tailwind-luminaryandanimatecss-tailwind-vectrix;hannahgibbsruns bothanimatecss-tailwind-helperandanimatecss-tailwind-plugin;jamesvectorpackruns bothanimatecss-tailwind-binaryandanimatecss-tailwind-graphic;adriannamrrruns bothtailwind-animationlabandtailwind-keyframes;killerdev1127runs bothtailwind-animaflow-kitandtailwind-animatecss-kit). The private-scope split (@aaron205whitmorevs@jasperquinn) suggests two parallel payload branches - either two operators sharing the Animate.css lure template, or one operator rotating the private-scope token to split blast radius across two scopes
Clean-up steps
- Lockfile grep (all 26 slugs): grep every
package.json,package-lock.json,pnpm-lock.yaml,yarn.lock,bun.lock,node_modules/.package-lock.json, and internal registry mirrors for the full set:animate-tailwind-kit,animate-twcss-bridge,animate-twcss-pack,animatecss-tailwind-binary,animatecss-tailwind-bridge,animatecss-tailwind-graphic,animatecss-tailwind-helper,animatecss-tailwind-luminary,animatecss-tailwind-plugin,animatecss-tailwind-presets,animatecss-tailwind-vectrix,signalledger-bridge,signalledger-diagnostics,tailwind-animaflow-kit,tailwind-animate-kit,tailwind-animatecss-kit,tailwind-animatecss-uniform,tailwind-animationlab,tailwind-css-keyframes,tailwind-fabric,tailwind-keyframes,tailwind-kinetic,tailwindcss-animate-catalog,tailwindcss-animatemotion,tailwindcss-effect-lab,tailwindcss-keyframe-kit,tailwindcss-motionanimate,tailwindcss-visual-kit. Also grep for the private scopes@aaron205whitmore/postcss-animate-utilsand@jasperquinn/postcss-motion-helpers - On any host that ever ran
npm run dev/pnpm dev/vite/next dev/tailwindcss buildon a candidate assessment repo containing aweb/project and any of the slugs above: treat the host as atreat as fully compromisedevent per the standard CWE-506 boilerplate. The payload is in the private scope and was not retrieved for public analysis, so the attack surface is unknown - rotate every credential reachable from the host (SSH keys, cloud CLI credentials, browser-saved passwords, Claude / Cursor / Windsurf API tokens, every git credential helper entry, every cloud-provider credential, every crypto-wallet seed phrase on the machine). Image the host for forensic review before clean-rebuild. The payload loads inside the dev server`s Node process, so anything the dev process can read is in scope - Developer workstation hunting: audit
~/.npmrcand any repo-local.npmrcfor tokens pointing to@aaron205whitmoreor@jasperquinnscopes, and search for cloned repositories containing aweb/folder with.npmrccommitted to source control. The committed-npm-token pattern is a reliable artefact: a legitimate project should never commit a readable//registry.npmjs.org/:_authToken=line. Scan every candidate-assessment repo received through Torre.ai, LinkedIn, Upwork, Dice, or cold-email channels for the same pattern - fake interview packages routinely rotate across private scopes - For engineering teams who received take-home assessments through recruiter channels (Torre.ai, LinkedIn, Upwork, direct email) especially from a company called Nodveta or presenting a backend/frontend engineer assessment that bundles a
web/folder with a private npm scope dependency: do NOT run the assessment on a daily-driver machine. Use a disposable VM, container, or dedicated throwaway workstation that holds no credentials. Review the published package for asrc/motion.js:5 require()of a private scoped package - that single line is the signature. Report the recruiter / assessment to the platform where you received it - Operator-continuity watch: expect wave 2 under new private scopes (anything beyond
@aaron205whitmoreand@jasperquinn) and new maintainer accounts riding the same Animate.css / Tailwind plugin template, likely with new fake-company brands (not just Nodveta). The 2026-07-28 to 2026-10-09 publication window with GHSA ingestion only on 2026-10-20 suggests the operator ran the campaign unobserved for ~10 weeks before OpenSSF caught it - adjacent packages predating 2026-07-28 may yet surface in backfill. Also watch for the same lure template delivered as a VS Code extension pack, Nuxt / Astro / Remix / Qwik plugin, or Vite-plugin variant that moves the private-dependency require out ofsrc/motion.jsinto a different plugin entry point
Sources
- GHSA-jfww-6c2q-w9rr - npm tailwindcss-animatemotion@1.0.7 (publisher doublekk61, MAL-2026-18086, campaign root MAL-2026-17665/17666/17667, private dep @aaron205whitmore/postcss-animate-utils)github.com
- GHSA-8vv2-m423-rcx4 - npm tailwindcss-keyframe-kit@3.0.0,3.0.1,3.0.2 (publisher hackson306623, MAL-2026-18088, private dep @aaron205whitmore)github.com
- GHSA-h368-qgvj-jhgw - npm signalledger-bridge@2.0.6 (publisher scott-rogers, MAL-2026-18075, private dep @jasperquinn/postcss-motion-helpers)github.com
- GHSA-q9pp-3583-37vv - npm signalledger-diagnostics@2.0.6 (publisher scott-rogers, MAL-2026-18076)github.com
- GHSA-h6pg-m44p-2x74 - npm tailwind-kinetic@2.0.6,2.0.8 (publisher keinsell-jh, MAL-2026-18084, private dep @aaron205whitmore)github.com
- GHSA-pvxf-hc22-hp2x - npm tailwind-animate-kit@2.0.6,2.0.7,2.0.8 (publisher keinsell-jh, MAL-2026-18078, private dep @jasperquinn)github.com
- GHSA-q29m-3m5r-fmjj - npm animate-tailwind-kit@3.0.1,3.0.4 (publisher johnxia1997, MAL-2026-18065, private dep @jasperquinn)github.com
- GHSA-jm3w-rxjc-9rp6 - npm tailwind-fabric@3.0.1,3.0.4 (publisher johnxia1997, MAL-2026-18082, private dep @aaron205whitmore)github.com
- GHSA-6hg9-v89p-9w82 - npm tailwindcss-animate-catalog@2.0.6,2.0.7 (publisher hackson306623, MAL-2026-18085)github.com
- GHSA-m8c8-f5hx-664g - npm tailwindcss-visual-kit@0.0.0-stage,1.3.6 (publisher johnsmith0821, MAL-2026-18090, private dep @jasperquinn)github.com
- GHSA-259h-r4vh-ph5j - npm tailwindcss-effect-lab@0.0.0-stage,1.6.7 (publisher johnsmith0821, MAL-2026-18087)github.com
- GHSA-jqm8-4v5f-g79x - npm tailwindcss-motionanimate@1.0.1 (publisher doublekk61, MAL-2026-18089, private dep @jasperquinn)github.com
- GHSA-94m4-x888-pgf4 - npm tailwind-keyframes@2.0.6 (publisher adriannamrr, MAL-2026-18083, private dep @aaron205whitmore)github.com
- GHSA-wrp2-f9jg-g5qx - npm tailwind-animationlab@2.0.6 (publisher adriannamrr, MAL-2026-18080, private dep @jasperquinn)github.com
- GHSA-qpfh-v8qh-gq5m - npm tailwind-css-keyframes@1.0.7 (publisher kinsley_badu, MAL-2026-18081, private dep @jasperquinn)github.com
- GHSA-2hh6-9wwc-7j9w - npm tailwind-animaflow-kit@2.0.6 (publisher killerdev1127, MAL-2026-18077, private dep @aaron205whitmore)github.com
- GHSA-q56v-wqww-c9pm - npm tailwind-animatecss-kit@2.0.6 (publisher killerdev1127, MAL-2026-18079, private dep @jasperquinn)github.com
- GHSA-8rrv-xcpx-rv3r - npm animatecss-tailwind-helper@2.0.6 (publisher hannahgibbs, MAL-2026-18070, private dep @jasperquinn)github.com
- GHSA-gqf2-w3vm-g87w - npm animatecss-tailwind-plugin@2.0.6 (publisher hannahgibbs, MAL-2026-18072, private dep @aaron205whitmore)github.com
- GHSA-vj87-h2x3-2985 - npm animatecss-tailwind-luminary@2.0.6,2.0.7 (publisher cypherbyte, MAL-2026-18071, private dep @jasperquinn)github.com
- GHSA-hcfv-m7w3-vcch - npm animatecss-tailwind-vectrix@2.0.6,2.0.7 (publisher cypherbyte, MAL-2026-18074, private dep @aaron205whitmore)github.com
- GHSA-gh2v-353h-8jhq - npm animatecss-tailwind-presets@1.0.0,2.0.3 (publisher elony16, MAL-2026-18073, private dep @jasperquinn)github.com
- GHSA-mp8w-g7p9-w5v3 - npm animatecss-tailwind-graphic@2.0.6 (publisher jamesvectorpack, MAL-2026-18069, private dep @aaron205whitmore)github.com
- GHSA-5fhm-3gq2-6gj8 - npm animatecss-tailwind-binary@2.0.6 (publisher jamesvectorpack, MAL-2026-18068, private dep @jasperquinn)github.com
- GHSA-v2vq-rvhc-qqr9 - npm animate-twcss-bridge@2.0.6 (publisher enterprise0908, MAL-2026-18066, private dep @aaron205whitmore)github.com
- GHSA-jr8w-9rvh-vv8p - npm animate-twcss-pack@2.0.6 (publisher enterprise0908, MAL-2026-18067, private dep @jasperquinn)github.com
- GHSA-3h7p-p7hr-fqf7 - npm animatecss-tailwind-bridge all versions (part of this campaign, same MAL-2026-17665/17666 group)github.com
- DependencyWatch: npm-2026-02-27-stegabin-contagious-interview (prior Contagious Interview DPRK operator write-up, same social-engineering shape - fake coding assignment and committed credential in a candidate repository)dependencywatch.io
- DependencyWatch: multi-2026-04-07-contagious-interview-5-ecosystems (five-ecosystem Contagious Interview wave reference, same lure-repository delivery vector)dependencywatch.io
Check your lockfile against this and every other incident.
Paste a lockfile and it is matched on your own device against every compromised version we track. Nothing is uploaded and there is no account.