Interlace ESLint
ESLint Interlace
PostgreSQLRules

no-transaction-on-pool

Prevents running transaction commands directly on pool (must use dedicated client).

Keywords: transactions, race condition, CWE-362, pg, node-postgres, pool

Prevents running transaction commands directly on pool (must use dedicated client).

⚠️ This rule errors by default in the recommended config.

Quick Summary

AspectDetails
CWE ReferenceCWE-362 (Race Condition)
SeverityHigh (CVSS: 7.5)
CategoryCorrectness

Rule Details

pool.query() acquires a different client for each call. Transaction commands (BEGIN, COMMIT, ROLLBACK) on pool may execute on different connections, breaking transaction semantics.

❌ Incorrect

// Each query might use a different connection!
await pool.query('BEGIN');
await pool.query('INSERT INTO users VALUES ($1)', [user]);
await pool.query('COMMIT'); // May commit nothing

✅ Correct

const client = await pool.connect();
try {
  await client.query('BEGIN');
  await client.query('INSERT INTO users VALUES ($1)', [user]);
  await client.query('COMMIT');
} catch (e) {
  await client.query('ROLLBACK');
  throw e;
} finally {
  client.release();
}

Error Message Format

⚠️ CWE-362 | Transaction command on pool causes race condition | HIGH
   Fix: Use pool.connect() and run transaction commands on the returned client

When Not To Use It

  • Never disable this rule - transactions on pool are always incorrect

Known False Negatives

The following patterns are not detected due to static analysis limitations:

Aliased Pool Reference

Why: The rule tracks pool.query() patterns; renamed references are missed.

// ❌ NOT DETECTED - Aliased pool
const db = pool;
await db.query('BEGIN'); // 'db' not recognized as pool

Mitigation: Use consistent naming for database pools. Add custom pool identifiers via configuration.

Dynamic Query String

Why: Transaction commands in variables are not detected.

// ❌ NOT DETECTED - Query from variable
const cmd = 'BEGIN';
await pool.query(cmd);

Mitigation: Use literal query strings for transaction commands.

Wrapped Pool Methods

Why: Wrapper functions hide pool access.

// ❌ NOT DETECTED - Transaction in wrapper
async function runQuery(sql: string) {
  return pool.query(sql);
}
await runQuery('BEGIN'); // Wrapper not traced

Mitigation: Apply rule to all modules. Use dedicated transaction wrappers that enforce client pattern.

Transaction Commands in Template Literals

Why: Complex template literals may hide transaction keywords.

// ❌ NOT DETECTED - Hidden in template
const action = 'BEGIN';
await pool.query(`${action}`);

Mitigation: Use explicit transaction management patterns. Create utility functions that enforce correct usage.

On this page