Overview
Unrestricted Resource Consumption in Node.js (Express) can allow attackers to exhaust memory or CPU by sending large or crafted requests, leading to degraded service or outages. In multi-tenant or shared environments, even modest abuse can escalate into widespread impact across nodes or clusters if not mitigated. Note: no CVEs are referenced in this general guide.
In Node.js, this vulnerability often arises when an Express app buffers request bodies fully or performs CPU-intensive work in the main event loop. Absence of input size limits, synchronous loops, or unbounded streaming can cause the event loop to stall, raising latency for all clients until the process or host becomes unresponsive.
Mitigations include size limits on body parsing (e.g., express.json({ limit: '100kb' })), rate limiting, and offloading heavy work to worker threads or external services. Also implement timeouts, streaming instead of buffering, input validation, and resource monitoring to detect early signals of abuse.
Code Fix Example
Node.js (Express) API Security Remediation
// VULNERABLE
const express = require('express');
const app = express();
app.post('/process', (req, res) => {
let body = '';
req.on('data', (chunk) => { body += chunk; });
req.on('end', () => {
// CPU-intensive, unbounded work on input
let sum = 0;
for (let i = 0; i < body.length * 1000000; i++) { sum += i; }
res.json({ status: 'done', length: body.length, sum });
});
});
app.listen(3000, () => console.log('Server listening'));
// FIX
const express2 = require('express');
const { Worker } = require('worker_threads');
const app2 = express2();
app2.post('/process', (req, res) => {
// Enforce payload size limit and offload CPU work
let body = '';
req.on('data', chunk => {
body += chunk;
if (body.length > 100 * 1024) { // 100 KB limit
req.connection.destroy();
}
});
req.on('end', () => {
const worker = new Worker(`const { parentPort } = require('worker_threads');
const input = ${JSON.stringify(body)};
let sum = 0;
for (let i = 0; i < input.length * 100000; i++) { sum += i; }
parentPort.postMessage({ sum, len: input.length });`, { eval: true });
worker.on('message', msg => res.json({ status: 'done', length: msg.len, sum: msg.sum }));
worker.on('error', () => res.status(500).send('Processing failed'));
});
});
app2.listen(3001, () => console.log('Server listening on 3001'));