A download endpoint that opens a file handle, streams it. No try/finally. If the stream errors, the handle never closes. After enough errors, the process hits the file-descriptor limit.
The vulnerable diff
// api/download.ts
downloadRouter.get("/file/:id", async (req, res) => {
const path = await getFilePath(req.params.id);
// BUG: no try/finally — if pipeline throws, fh.close never runs
const fh = await fs.promises.open(path, "r");
await pipeline(fh.createReadStream(), res);
await fh.close();
});What is wrong
Resource lifecycle (file descriptors, sockets, database handles, etc.) is the same pattern across every kind. Acquire, use, release. The release must happen on every exit path — success, error, and cancellation. Try/finally is the canonical structure. CWE-772 is the broader class: failure to release a resource on the error path. TC39's explicit-resource-management proposal (using keyword) gives a cleaner syntax for the same pattern.
The attack
Symptom: process gets EMFILE: too many open files after a flaky-network spell.
# Logs:
Error: EMFILE: too many open files, uv_resident_set_memory
at FSWatcher.start ...
# `lsof -p $PID | wc -l` shows thousands of open files for a single client connection.Restart fixes it temporarily; the bug recurs under load.
Mesrai's review comment
mesraipilot · Bot · reviewed 1 min ago
[mesrai] [code-review] [Performance] [CWE-772] [high]
`fh.close()` not on every exit path. If `pipeline` throws, close
never runs and the file descriptor leaks.
Wrap in try/finally:
const fh = await fs.promises.open(path, "r");
try {
await pipeline(fh.createReadStream(), res);
} finally {
await fh.close();
}
Or use TC39 explicit-resource-management (Node 24+):
await using fh = await fs.promises.open(path, "r");
await pipeline(fh.createReadStream(), res);
// fh closed at end-of-scope, even on throw
Same pattern for any resource that has an explicit close/release.The fix
// api/download.ts — fixed
downloadRouter.get("/file/:id", async (req, res) => {
const path = await getFilePath(req.params.id);
const fh = await fs.promises.open(path, "r");
try {
await pipeline(fh.createReadStream(), res);
} finally {
await fh.close();
}
});Try/finally guarantees close. The error still propagates to the framework error handler; the handle is released regardless.
Why human review missed it
FD leaks surface only under sustained error rate — dev never hits the limit. Mesrai catches every `open` / acquire-style call without a matching `close` in finally.
Related rules + further reading
Mesrai rule pack: performance/resource-close-in-finally — flags open/acquire without close in finally.
TC39: Explicit Resource Management (using keyword).
CWE-772 — Missing Release of Resource after Effective Lifetime.
Takeaway
Open in try, close in finally. Or `using` if you're on Node 24+. Mesrai catches every open without paired close.