What 36 Malicious npm Packages Can Do to Your Infrastructure From a postinstall Hook
By Beatriz Costa
Between March 31 and April 4, 2026, thirty-six malicious npm packages disguised as Strapi CMS community plugins were published to the npm registry across four sock-puppet accounts.
If you installed any package matching strapi-plugin-* from the accounts umarbek1233, kekylf12, tikeqemif26, or umar_bektembiev1, remove it immediately, treat the environment as compromised, and follow the remediation steps at the end of this post. If you’re unsure whether your team has installed these packages - Safety Endpoint Security will detect these packages for you, and can be rolled out to your entire team in minutes.
Static analysis of each postinstall.js across all 36 tarballs shows the packages target Guardarian, an EU-licensed cryptocurrency payment gateway. But the techniques in these payloads are not Guardarian-specific. They are generic. If you run containers, databases, and CI/CD pipelines, these payloads demonstrate what an attacker can accomplish from inside npm install — with no user interaction beyond the install itself.
Why This Campaign Is Worth Studying
Most malicious npm packages do one thing: steal environment variables and phone home. These packages do more. Across thirty-one unique payloads (by SHA256 hash), the attacker deployed a catalog of post-exploitation techniques — all through the postinstall script hook that runs automatically on npm install.
The payloads are gated. They check the hostname: hostname === 'prod-strapi', or hostname.includes('prod'). On non-matching machines — developer laptops, unrelated CI runners — they silently exit. This means the code targets a specific environment, but it also means any analysis sandbox with the wrong hostname would never see the malicious behavior execute.
Static analysis of the source code reveals the intended function of the code, allowing us to confirm its designed behavior.
What the Payloads Are Designed to Do
Map internal infrastructure from inside a container
The nordica-recon payload (SFTY-20260403-43222 / MAL-2026-2476) resolves twenty-two internal service names from inside the Strapi container using getent hosts and dig — payment APIs, webhook handlers, admin gateways, customer-facing services.
The nordica-vhost payload (SFTY-20260403-73847 / MAL-2026-2480), analyzed before its removal from npm, enumerates thirty-six staging subdomains via Host header probing — including payment processor integrations, card services, trading APIs, and recurring payment systems.
If someone ran getent hosts against every internal hostname reachable from your CI runner or Docker container, what would they learn?
Escape the container via /proc/1/root
Five payloads attempt to read the host filesystem through /proc/1/root/. If the container runs with sufficient privileges — common in CI/CD environments and some Docker setups — this bypasses container isolation.
The advanced-uuid payload (SFTY-20260403-70957 / MAL-2026-2450) attempts to read:
id_rsa, id_ed25519, authorized_keys) credentials.xml and secret.key from the host filesystem /data1/app/docker/containers/*/config.v2.json .git-credentials, .netrc) /proc/*/cmdlineThe path /data1/app/docker/ is non-standard — it suggests knowledge of the target's specific directory layout, either from prior access or from an earlier reconnaissance payload in this same campaign.
Spray credentials laterally
A hardcoded PostgreSQL credential pair (user_strapi / 1QKtYPp18UsyU2ZwInVM) appears in nine payloads. Whether these credentials are valid, stale, or rotated cannot be determined from static analysis. But the pattern is revealing: the same credential is not only used for database access but also tried for:
172.17.0.1) and two specific Hetzner IPs (SFTY-20260403-09913: guardarian-ext; SFTY-20260403-15905: nordica-deep) 65.21.203.242:9200, alongside target-specific passwords like elastic:guardarian and elastic:Guardarian123 (SFTY-20260403-43561: blurhash) root, ubuntu, deploy, jenkins, node, strapiOne credential, tried against databases, SSH, and Elasticsearch across multiple hosts. How many of your infrastructure credentials are shared across services?
Query cloud metadata
The guardarian-ext payload (SFTY-20260403-09913 / MAL-2026-2463) queries the Hetzner Cloud metadata API — and only Hetzner. Not AWS, not GCP, not Azure:
A separate payload (hextest, SFTY-20260403-12094 / MAL-2026-2466) targets AWS: if AWS_ACCESS_KEY_ID exists in the environment, it exfiltrates the credentials and attempts sts get-caller-identity, s3 ls, and ec2 describe-instances in eu-central-1.
Is your cloud metadata endpoint protected from processes running inside your containers?
Attempt CDN bypass
Two payloads carry a hardcoded API key (a78e8684-1c99-4eb4-b899-16e55d552335) and use it to attempt direct connections to origin servers, bypassing Cloudflare. The nordica-recon payload (SFTY-20260403-43222) and nordica-cms payload (SFTY-20260403-51149) try Hetzner IPs with Host header injection:
Whether this key is valid or rotated cannot be determined from static analysis.
Dump the database
The hextest payload (SFTY-20260403-12094) loads the pg Node.js module — or runs npm install pg --no-save to install it on the fly — and attempts to dump every table in the PostgreSQL database, up to 100 rows each. It also enumerates all databases on the server, all roles and permissions, and server settings.
The seed payload (SFTY-20260403-04066 / MAL-2026-2482) uses the hardcoded credentials to probe six specific database names, including guardarian_payments, exchange, and custody.
Mine git history
The nordica-deep payload (SFTY-20260403-15905 / MAL-2026-2474) runs git log --all -p against the application repository, searching for deleted .env files still present in history, passwords and secrets across all commits, deploy scripts with server IPs, and git stash contents.
If you have ever committed a secret and later removed it, this type of payload finds it.
Establish persistence
Several payloads include persistence mechanisms: a C2 agent written to /tmp/.node_gc.js with crontab-based restart, reverse shells spawned as detached processes, and PHP webshells written to Strapi's public uploads directory. The events payload (SFTY-20260403-29745 / MAL-2026-2460) runs a C2 polling loop that executes arbitrary commands from the server.
The Campaign Timeline
The publication timeline, reconstructed from the time field in each package's npm registry metadata, spans four days:
Days 1–2 (March 31 – April 1): Twenty packages across two accounts (umar_bektembiev1, tikeqemif26). These are reconnaissance payloads — DNS enumeration, network scanning, Elasticsearch probing, host filesystem reads, SSH attempts, Cloudflare bypass tests. No reverse shells. No persistence.
Day 3 (April 2): One package (nordica-deep). Git history mining, SSH brute-force to specific staging servers, and application code extraction.
Day 4 (April 3): Fifteen packages across two new accounts (umarbek1233, kekylf12) using disposable email addresses. Aggressive exploitation — Redis RCE, Docker overlay escape, credential harvesting, direct database exploitation, persistent implants.
Day 5 (April 4): One final package on a different C2 port (8888) with a different exfiltration path (/exfil2/).
Whether the reconnaissance payloads from Days 1–2 collected the intelligence that appears hardcoded in the Day 3–4 payloads, or the attacker possessed that intelligence before the campaign began, cannot be determined from static analysis. Both interpretations are consistent with the evidence.
What is clear: each payload reports to a unique C2 path prefix (av-, nr-, bh-, cr-, sv-, among others), allowing the operator to track which package executed on which host.
An Observation on Tradecraft
There is a tension in this campaign. The targeting is precise: hostname gating, hardcoded credentials, specific internal service names. But the delivery is noisy — 36 packages across 4 throwaway accounts with disposable email addresses, published over 4 days, with no obfuscation. If the attacker already had production credentials, why use a detectable delivery mechanism? If they didn't, the reconnaissance-to-exploitation pipeline they built via npm is operationally interesting and auditable.
This observation does not lessen the significance of the findings; rather, the discrepancy between the targeted precision and the general delivery method is an area worthy of independent analysis.
What This Means For Defenders
The specific techniques in these payloads — cloud metadata access, /proc/1/root/ escape, credential spraying, git history mining, database dumping — are not novel individually. What this campaign demonstrates is the breadth of what a postinstall hook can accomplish when it runs inside an environment with access to infrastructure services.
If your organization:
npm install with network access in CI/CD or production .env files, environment variables, or git history Then the techniques in these payloads apply to you, regardless of whether you use Strapi.
Indicators of Compromise
C2 Infrastructure
144[.]31[.]107[.]231:9999 — primary HTTP C2 144[.]31[.]107[.]231:8888 — secondary C2 144[.]31[.]107[.]231:4444 — reverse shell listener /exfil/, /exfil2/, /c2/, /db/ /dx/, /ir/, /rt/, /gd/, /hw/Credentials Found in Malicious Source Code
The following credentials are embedded in the publicly downloadable source code of the malicious package and have been documented in prior public reporting on this campaign. We include them so defenders can search their own environments.
user_strapi / 1QKtYPp18UsyU2ZwInVM — PostgreSQL credentials (9 payloads) a78e8684-1c99-4eb4-b899-16e55d552335 — API key (2 payloads)We cannot confirm, based on static analysis, whether these credentials are currently valid.
Persistence Artifacts
/tmp/.node_gc.js /tmp/vps_shell.sh, /tmp/redis_exec.sh /app/public/uploads/shell.php, /app/public/uploads/revshell.js /app/node_modules/.hooks.js node_gc or curlnpm Accounts
Affected Packages
Fourteen packages have been removed from npm since this analysis began, marked with †. SHA256 hashes of all tarballs are published for independent verification. OSV advisory IDs are cross-referenced where available.
Remediation
If you installed any of the affected packages:
1. Look for `/tmp/.node_gc.js`. Audit crontab entries for `node_gc` or `curl`. 2. Kill orphaned `node -e` processes connecting to `144.31.107.231`. 3. Check Redis with `CONFIG GET dir`. 4. Inspect `/app/public/uploads/` for `shell.php` or `revshell.js`.
This analysis is based on each `postinstall.js` payload across all 36 tarballs, combined with npm registry metadata for timeline reconstruction. All credentials and infrastructure details referenced in this post are embedded in the malicious package source code that was publicly available on the npm registry.
Safety Firewall blocks malicious packages before they reach your environment. [Get started today.](https://www.getsafety.com/quick-start-guide)