36 Malicious npm Packages Disguised as Strapi Plugins: Red Team Reconstruction of the Guardarian Supply Chain Attack
Preface: Why Read From the Attacker's Perspective
This article is written in a different format than usual. Instead of describing an incident from the outside, we reconstruct the operational logic from the inside: each phase is narrated from the perspective of the person who designed the attack. This is not glorification. It is reverse engineering of adversarial thinking, the same method professional red teams use to anticipate attacker moves. If you understand how the attacker thinks, you understand where the defensive chain breaks. Each "attacker" section is followed by a defensive box that brings the perspective back to the blue side.
The case is real: between April 14 and 15, 2026, 36 malicious npm packages disguised as Strapi CMS plugins were published to the npm registry. The ultimate target was Guardarian, a cryptocurrency exchange platform. The operation went through eight malware variants in thirteen hours, starting from generic credential harvesting and arriving at fileless execution with crontab persistence. For web agencies and startups using Strapi daily, this case is not academic: it is a reminder that the npm supply chain is an active battlefield.
Phase 1 — Reconnaissance: Mapping the Target Before Writing a Single Line of Code
*I'm sitting at my terminal and my objective is clear: Guardarian. A crypto platform with Strapi-based infrastructure. How do I know? Their LinkedIn job postings mention Strapi and Node.js. Their GitHub repositories have forks of Strapi plugins. DNS responses and TLS certificates reveal hostnames like "prod-strapi". Their job postings seek PostgreSQL and Redis experience. Every piece of public information is a tile in the infrastructure puzzle. I don't need a zero-day exploit. I need to understand how their deployment pipeline works.*
*I search through GitHub issue comments, Stack Overflow discussions, npm profiles of their developers. I find references to Jenkins CI in branch naming conventions. I find paths like /opt/secrets/ and /var/www/nowguardarian-strapi/ in publicly shared configuration snippets. I find database names: guardarian, guardarian_payments, exchange, custody. I haven't written a single line of malicious code yet, but I already know my target's architecture better than many of their junior developers.*
Defense — Reconnaissance Phase: Every organization should conduct periodic OSINT exposure audits. Search your company name combined with technology names (Strapi, PostgreSQL, Redis, Jenkins) on GitHub, Stack Overflow, LinkedIn. Remove or anonymize references to internal paths, database names, production hostnames. Train developers not to share configuration snippets with real data.
Phase 2 — Arsenal Preparation: Creating Identities and Packages
*The npm registry does not verify identities. I create four accounts in minutes: umarbek1233, kekylf12, tikeqemif26, umar_bektembiev1. Four, not one, because distribution across multiple accounts makes simultaneous takedown harder and simulates an organic ecosystem of independent contributors. If npm suspends one account, the other three continue serving packages.*
*The naming convention is the heart of the deception. All packages are called strapi-plugin-something: strapi-plugin-cron, strapi-plugin-events, strapi-plugin-seed, strapi-plugin-api, strapi-plugin-database, strapi-plugin-server, and thirty others. Plausible, generic names that a developer might search for when they need to extend Strapi. The starting version is 3.6.8 — a number that sounds mature, stable, not a 0.1.0 that would scream "new project". I add credible descriptions, correct keywords, a minimal README. Anyone searching "strapi plugin cron" on npm will find my package mixed in with legitimate results.*
*But the primary vector is not passive typosquatting. My plan is more targeted: I know Guardarian developers install Strapi plugins. If I can make my packages appear in the dependencies of an internal project — through a pull request, a recommendation in a chat, or simply hoping someone finds them while searching — the damage is done before anyone reads a line of code.*
Defense — Preparation Phase: Implement an npm package allowlist policy. Tools like npm audit signatures, Socket.dev, and Snyk can detect new packages with suspicious naming. Configure your private registry (Verdaccio, Artifactory, GitHub Packages) as a proxy with a whitelist: only explicitly approved packages can be installed. For every new Strapi package, verify: publication date, download count, author, source repository.
Phase 3 — The Vector: postinstall as a Zero-Click Weapon
*Here is the detail that makes npm such an elegant vector: the scripts.postinstall field in package.json. Any command placed in that field is executed automatically when someone types npm install. There is no prompt, no confirmation, no sandbox. The command runs with the privileges of the user who launched npm, which in most development environments and in too many CI/CD environments is root or has access to sensitive environment variables. It is a zero-click execution: the very act of installing the package is the act of executing my code.*
*In my package.json, the postinstall points to a script that looks innocuous at a superficial glance. But the real payload is obfuscated, base64-encoded, concatenated from multiple strings, and reconstructed at runtime. A distracted reviewer sees a setup line. An attentive reviewer sees a bomb.*
The npm postinstall hook is the single most abused vector in JavaScript supply chain attacks over the past three years. Every organization using npm should: (1) set --ignore-scripts as a global default and enable scripts only for verified packages; (2) run npm audit before every installation; (3) manually verify the package.json of every new dependency before CI/CD installation; (4) consider adopting Deno or Bun for new projects, which do not execute postinstall scripts by default.
Phase 4 — Rapid Evolution: Eight Variants in Thirteen Hours
*The operation is not static. Over thirteen hours I publish eight distinct malware variants, each more refined than the last. It is agile development applied to malicious code: each variant responds to feedback — real or anticipated — on the detectability of the previous one.*
Variants 1-5: Credential Harvesting and Environment Profiling
*The first five variants are explorations. They collect environment variables — DATABASE_URL, REDIS_URL, AWS_SECRET_ACCESS_KEY, everything the Node.js process can see — and send them to my C2 server at 144.31.107.231 via plaintext HTTP. Yes, plaintext. This is not laziness: it is pragmatism. HTTPS requires certificates, and a TLS certificate leaves traces in Certificate Transparency logs that might attract researcher attention. Plain HTTP on a high port is background noise in a development server's traffic. Nobody monitors outbound HTTP traffic from an npm build container.*
*Each variant adds a profiling layer: hostname, current user, filesystem paths, running processes, contents of /etc/hostname. I am building a map of the target environment before delivering the surgical strike.*
Variant 6 (strapi-plugin-seed): The Surgical Database Strike
*With variant 6 I change strategy. I no longer collect generic credentials: I embed PostgreSQL credentials directly in the code, obtained from previous variants or from reconnaissance. The malware attempts direct connections to databases with specific names: guardarian, guardarian_payments, exchange, custody. These are not invented names — they are the actual production database names of Guardarian. If the connection succeeds, the malware runs enumeration queries: table listings, schemas, first rows of each table. The dump is sent to the C2.*
*This variant is the proof that the operation is not opportunistic. It is targeted. An opportunistic attack collects everything and filters later. I already know what I am looking for and where to find it. The database names are hardcoded in my code because I discovered them before I started.*
Defense — Database Access Phase: Production database credentials must never be accessible from development or CI/CD environments. Use dedicated vaults (HashiCorp Vault, AWS Secrets Manager) with granular per-environment access. Monitor production database connections: every connection from an unexpected IP or hostname must generate an alert. If an npm process attempts a PostgreSQL connection, it is a certain compromise indicator.
Variant 7 (strapi-plugin-api v3.6.8): Persistence and Hostname Verification
*Variant 7 is where the operation becomes sophisticated. The malware now checks the system hostname: if it contains the string "prod-strapi", it activates the full payload. On any other hostname, it behaves like an innocuous plugin. This is surgical targeting: the code is designed to activate only on Guardarian's production infrastructure and sleep everywhere else. If a security researcher installs it on their laptop for analysis, they will see nothing malicious.*
*When the hostname matches, the malware writes a file called .node_gc.js to the /tmp/ directory. The name is deliberately chosen to look like a Node.js garbage collector artifact — a file a sysadmin would see and ignore. Then it adds an entry to the current user's crontab: execution every minute. Persistence is guaranteed: even if the original process dies, the crontab will relaunch it within sixty seconds. The .node_gc.js file establishes a reverse shell to the C2 at 144.31.107.231.*
Defense — Persistence Phase: Monitor crontab modifications with auditd or equivalent. Any crontab modification by a Node.js process is a critical compromise indicator. Monitor file creation in /tmp/ with names mimicking system processes (.node_gc.js, .npm_cache.js, .v8_log.js). Implement read-only filesystem for /tmp/ in production containers, or use tmpfs with noexec. The hostname check is a reminder: your production hostnames are sensitive information. Use naming conventions that do not reveal server function to an external observer.
Variant 8 (v3.6.9): Fileless Execution and Secret Hunting
*The final variant is the cleanest. I abandon writing files to disk — too detectable, too persistent as a forensic artifact. I switch to fileless execution: the payload is launched directly with node -e, injecting JavaScript code as a command-line argument. It does not touch the filesystem, leaves no files to analyze. It exists only in memory.*
*The target is surgical: the malware searches for /opt/secrets/strapi-green.env and the contents of the /var/www/nowguardarian-strapi/ directory. It looks for .env files, private keys, Redis dumps, Docker secrets, Kubernetes service account tokens. Everything is sent to the C2 in plaintext. One file at a time. If the file does not exist, it moves to the next without error. If it exists, it reads and sends it. Silent, fast, single-use.*
*The paths are not random. /opt/secrets/strapi-green.env is a path specific to Guardarian's infrastructure — probably discovered from a comment in a Dockerfile, an environment variable in a CI log, or from previous malware variants. /var/www/nowguardarian-strapi/ contains the production Strapi deployment. I am looking for the keys to the kingdom in places I already know.*
Defense — Fileless Execution Phase: Fileless execution via node -e is difficult to detect with traditional signature-based antivirus. Effective defenses include: (1) process monitoring for suspicious command-line arguments — any node -e with base64 or obfuscated payload is an alert; (2) file access monitoring with inotify/auditd; (3) least privilege principle: the Strapi process should not have read access to /opt/secrets/ or Docker sockets or K8s tokens; (4) network segmentation: outbound HTTP traffic from a Strapi container to unknown IPs should be blocked by default.
Phase 5 — Exfiltration: Simplicity as Operational Virtue
*Exfiltration is deliberately primitive. Plaintext HTTP to 144.31.107.231. No custom protocol, no DNS tunneling, no steganography. Why? Because simplicity is an operational virtue. A custom protocol is a unique fingerprint that an IDS can learn to recognize. Plaintext HTTP on port 80 is indistinguishable from legitimate traffic of a web application calling an external API. No SOC in the world generates alerts for an outbound HTTP GET request from a Node.js server.*
*The exfiltrated content includes: complete .env files with database credentials, API keys, wallet private keys; partial Redis dumps with sessions and tokens; Docker secrets mounted in the filesystem; Kubernetes service account tokens that would allow lateral movement across the entire cluster. It is the complete kit for a second stage of the attack: with these credentials, I could return at any time through the front door, without needing npm or supply chains. The npm malware is the lockpick for the back door; the exfiltrated credentials are the copy of the house key.*
Defense — Exfiltration Phase: Outbound HTTP traffic from production containers must pass through a proxy with a destination allowlist. Any request to an unlisted IP must be blocked and logged. Implement Data Loss Prevention (DLP) at the network level: pattern matching for strings resembling credentials, private keys, JWT tokens. Rotate all credentials immediately if you suspect a compromise — not after the forensic analysis, before. The cost of a preventive rotation is negligible compared to the cost of exfiltrated and used credentials.
The Timeline: Thirteen Hours From the Attacker's Workbench
The temporal compression of the operation deserves visualization. In thirteen hours, the attacker iterated eight times on their malware, publishing it across 36 packages through 4 accounts. The speed suggests meticulous preparation: the code for successive variants was probably already written and waiting for deployment, not developed in real time.
- 1.Hour 0: Creation of 4 npm accounts and initial publication of 36 packages with variants 1-2 (generic credential harvesting)
- 2.Hours 2-4: Variants 3-5 published as updates — environment profiling, hostname collection, process enumeration
- 3.Hour 6: Variant 6 (strapi-plugin-seed) — hardcoded PostgreSQL credentials, direct probing of Guardarian databases
- 4.Hours 8-9: Variant 7 (strapi-plugin-api v3.6.8) — hostname check for "prod-strapi", .node_gc.js creation, crontab persistence
- 5.Hours 11-13: Variant 8 (v3.6.9) — fileless execution via node -e, targeting /opt/secrets/strapi-green.env and /var/www/nowguardarian-strapi/
- 6.Post-hour 13: Packages identified and removed from npm registry after security community reporting
Indicators of Compromise (IOC)
For SOC teams and security managers responsible for Strapi infrastructure, these are the indicators to search for immediately.
- C2 IP: 144.31.107.231 — check network logs for any outbound connection to this address
- File on disk: /tmp/.node_gc.js — persistence file disguised as Node.js garbage collector artifact
- File on disk: /tmp/vps_shell.sh — shell script for reverse shell connection
- Crontab: entries executing .node_gc.js or vps_shell.sh every minute — verify with crontab -l for all system users
- npm accounts: umarbek1233, kekylf12, tikeqemif26, umar_bektembiev1 — verify no packages from these authors are present in your dependencies
- Packages: any strapi-plugin-* package with version 3.6.8 or 3.6.9 not published by the official @strapi organization
- Processes: node -e executions with base64 or obfuscated payload in active processes
- File access: anomalous reads of /opt/secrets/*.env, /var/www/*/.env, Docker secrets, K8s tokens
Defensive Lessons: What Stops This Type of Attack
Stepping out of the attacker's perspective and returning to the defender's, this case illustrates five principles that every organization depending on npm should adopt.
- 1.Private registry with allowlist: do not allow direct installation from npmjs.com in CI/CD and production environments. Use a proxy (Verdaccio, Artifactory, GitHub Packages) that admits only explicitly approved packages.
- 2.Disable postinstall scripts: configure npm with --ignore-scripts by default. Enable scripts only for verified and necessary packages. This single control would have neutralized all eight variants.
- 3.Outbound traffic monitoring: build and production containers should not be able to contact arbitrary IPs. An outbound proxy with an allowlist is the most effective defense against exfiltration.
- 4.Credential segregation by environment: production credentials must not be accessible from development, build, or test environments. Ever. If your production .env file is readable by an npm install process, your security model is broken.
- 5.Crontab and anomalous process monitoring: any crontab modification by an unauthorized process is a compromise indicator. Any node -e process with an obfuscated payload is a compromise indicator. These signals must generate immediate alerts, not end up in a log nobody reads.
None of these measures are new. None require expensive technology or rare expertise. They are all operational hygiene measures that most organizations know but do not implement because they "slow down development". The attacker in this case was counting on exactly that.
Methodological Note and Sources
Sources: Socket.dev technical analysis (April 2026) on the 36 malicious npm packages; Sonatype report on the Guardarian campaign; npm security community analysis; OSINT data on the involved npm accounts. IOCs have been verified across multiple sources. The attacker perspective reconstruction is an analytical exercise based on documented technical artifacts, not on direct information from the attack authors. This article contains no executable malicious code.