Memory Leak in Express with Hmac Signatures
Memory Leak in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
A memory leak in an Express application that uses Hmac Signatures typically arises when signature verification or key material is handled in a way that retains references to objects across requests. For example, if the application reuses buffers, crypto contexts, or accumulates metadata per request without cleanup, garbage collection cannot reclaim that memory. Over time, as requests arrive, the heap grows, increasing latency and eventually causing process instability or restarts.
Consider an Express route that validates an Hmac Signature on incoming payloads. If the verification logic creates new objects for each signature operation (e.g., Hmac instances, key buffers) and stores them in a module-level cache or attaches them to request/response objects unintentionally, those objects remain referenced. In Node.js, the V8 garbage collector will not collect referenced objects; if references persist due to closures or global registries, memory usage climbs. This is especially relevant when keys are derived per request or when the application caches partial results tied to the signature context without eviction policies.
Real-world patterns that exacerbate this include using streaming verification without properly ending or destroying the stream, or retaining request-specific data in long-lived objects. For instance, attaching the raw signature payload or derived key material to res.locals for later use without clearing can keep large buffers alive. Because Hmac operations involve cryptographic contexts and buffers, failing to release them promptly leads to unbounded memory growth under sustained load.
middleBrick detects scenarios where API behavior suggests memory pressure by correlating runtime telemetry with known unsafe patterns. Although the scanner does not pinpoint the exact line, it flags endpoints with unusual resource characteristics that may indicate leaks, providing remediation guidance to review object retention and lifecycle management.
Example of problematic Express code that can contribute to retention:
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json());
const keyCache = new Map(); // module-level cache
app.post('/verify', (req, res) => {
const { data, signature } = req.body;
const key = crypto.randomBytes(32); // new key each request, stored in cache
const hmac = crypto.createHmac('sha256', key);
const hash = hmac.update(data).digest('hex');
keyCache.set(req.id, key); // key retained indefinitely
if (hash === signature) {
res.json({ valid: true });
} else {
res.status(401).json({ valid: false });
}
});
app.listen(3000);
In this snippet, a new random key per request is stored in keyCache without cleanup, and a Hmac instance is created but not explicitly managed. The reference in the cache prevents garbage collection, causing a memory leak. Using static keys or failing to release cryptographic contexts in a request lifecycle compounds the issue when combined with high request volume.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
To remediate memory leaks with Hmac Signatures in Express, focus on avoiding unnecessary retention of cryptographic material and ensuring objects are released after each request. Use static keys stored outside the request cycle, clean up caches, and avoid attaching large buffers to request or response objects. Below are concrete, safe patterns.
1. Use a static key and avoid caching per-request keys
Load the signing key once at startup and reuse it. Do not generate new keys per request or store them in long-lived maps.
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json());
const KEY = crypto.randomBytes(32); // static key set at startup
app.post('/verify', (req, res) => {
const { data, signature } = req.body;
const hmac = crypto.createHmac('sha256', KEY);
const hash = hmac.update(data).digest('hex');
if (crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(signature))) {
res.json({ valid: true });
} else {
res.status(401).json({ valid: false });
}
});
app.listen(3000);
This approach eliminates per-request key allocations and cache retention, allowing cryptographic objects to be garbage collected after each request.
2. Clean up caches and avoid attaching objects to res.locals
If you must maintain a cache, implement an eviction policy (e.g., TTL or LRU) and ensure you do not store buffers used in Hmac operations. Also, avoid attaching large objects to res.locals that outlive the request.
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json());
const keyCache = new Map();
const MAX_ENTRIES = 1000;
function cleanupCache() {
const entries = Array.from(keyCache.entries());
if (entries.length > MAX_ENTRIES) {
// remove oldest entries
for (let i = 0; i < entries.length - MAX_ENTRIES; i++) {
keyCache.delete(entries[i][0]);
}
}
}
app.post('/verify', (req, res) => {
const { data, signature } = req.body;
const key = crypto.randomBytes(32);
const hmac = crypto.createHmac('sha256', key);
const hash = hmac.update(data).digest('hex');
// store key with cleanup to bound memory
keyCache.set(req.id, key);
cleanupCache();
// Ensure we do not attach key or large buffers to res.locals
if (hash === signature) {
res.json({ valid: true });
} else {
res.status(401).json({ valid: false });
}
// key should not be retained after response; in real usage prefer static keys
});
app.listen(3000);
In this variant, the cache is bounded and cleaned, reducing the risk of unbounded growth. However, the best practice remains using a static key and avoiding caches for cryptographic material altogether.
Additionally, ensure that streams used for signature verification are properly consumed and destroyed, and that no references to request bodies or signature buffers are kept beyond the request lifecycle. These steps mitigate memory leak risks specific to Hmac operations in Express.