Connection leaks aren't just bugs—they are production-killing events. Here is the post-mortem of an outage we survived, and the automated static analysis standard we built to make it biologically impossible to repeat.
It was 3 AM. PagerDuty woke me up. Our API was returning 500 errors.
The database was fine. CPU was fine. Memory was fine. But every query was timing out.
The Problem
FATAL: too many connections for role "app_user"
We had exhausted our 100-connection limit. But our traffic was normal. Where were all the connections going?
The Leak
After hours of debugging, we found it:
// ❌ The connection leak hiding in our codebase
async function getUserOrders(userId) {
const client = await pool.connect();
const orders = await client.query('SELECT * FROM orders WHERE user_id = $1', [
userId,
]);
return orders.rows;
// Where's client.release()? 🤔
}
Every call leaked a connection. With 50 requests/minute, we exhausted the pool in 2 minutes.
Why This Happens
| Scenario | Result |
|---|---|
Forgot release() entirely |
Connection never returned |
Early return before release()
|
Connection leaked |
| Exception thrown |
finally block missing |
| Async error | Unhandled rejection, no cleanup |
The Correct Pattern
// ✅ Always release in finally block
async function getUserOrders(userId) {
const client = await pool.connect();
try {
const orders = await client.query(
'SELECT * FROM orders WHERE user_id = $1',
[userId],
);
return orders.rows;
} finally {
client.release(); // Always executes
}
}
Or even better—don't use connect() at all for simple queries:
// ✅ Best pattern: use pool.query() directly
async function getUserOrders(userId) {
const orders = await pool.query('SELECT * FROM orders WHERE user_id = $1', [
userId,
]);
return orders.rows;
}
Let ESLint Catch This
npm install --save-dev eslint-plugin-pg
import pg from 'eslint-plugin-pg';
export default [pg.configs.recommended];
Now every missing release is caught:
src/orders.ts
3:17 error 🔒 CWE-772 | Missing client.release() detected
Fix: Add client.release() in finally block or use pool.query() for simple queries
The Rule: no-missing-client-release
This rule tracks:
- Every
pool.connect()call - Every code path through the function
- Whether
client.release()is called on all paths - Whether it's in a
finallyblock (recommended)
Production Impact
After deploying this rule:
- 0 connection leaks in 6 months
- No more 3 AM pages for connection exhaustion
- CI catches issues before they reach staging
Quick Install
npm install --save-dev eslint-plugin-pg
import pg from 'eslint-plugin-pg';
export default [pg.configs.recommended];
Don't wait for the 3 AM wake-up call.
📦 npm: eslint-plugin-pg
📖 Rule docs: no-missing-client-release
The Interlace ESLint Ecosystem
Interlace is a high-fidelity suite of static code analyzers designed to automate security, performance, and reliability for the modern Node.js stack. With over 330 rules across 18 specialized plugins, it provides 100% coverage for OWASP Top 10, LLM Security, and Database Hardening.
Explore the full Documentation
© 2026 Ofri Peretz. All rights reserved.
Build Securely.
I'm Ofri Peretz, a Security Engineering Leader and the architect of the Interlace Ecosystem. I build static analysis standards that automate security and performance for Node.js fleets at scale.
Top comments (0)