Skip to main content
interlace
Plugin: node-securityRules

require-stream-error-handler

Require an error listener on streams passed to pipe, which does not forward errors

Detects .pipe() on a stream that has no 'error' listener. This rule is part of eslint-plugin-node-security and provides LLM-optimized error messages with fix suggestions.

🚨 Security rule | 💡 Provides suggestions | ⚠️ Set to error in recommended

Quick Summary

AspectDetails
CWE ReferenceCWE-248 (Uncaught Exception)
SeverityHigh (remote denial of service)
Auto-Fix💡 Suggests a listener or pipeline()
CategorySecurity
ESLint MCP✅ Optimized for ESLint MCP integration
Best ForHTTP servers that stream files, uploads, or compressed bodies

Vulnerability and Risk

Vulnerability: .pipe() forwards data and nothing else. It does not forward errors, and it does not destroy the source when the destination fails. A stream that emits 'error' with no listener re-throws inside the EventEmitter, and in Node an unhandled 'error' event is an uncaught exception — the process exits.

Risk: One request for a missing, unreadable, or permission-denied file is enough to stop the server. That makes it a remote denial of service costing the attacker a single request, with no authentication and no payload.

Rule Details

The rule reports a .pipe() whose source or destination is a stream it can prove has no handler:

  1. Constructed inlinefs.createReadStream(p).pipe(res). The value has no name, so no 'error' listener can ever have been attached to it. This is a property of the expression, not a heuristic.
  2. Named but never handled — a name bound to a stream constructor that never appears with .on('error'), .once('error'), or .addListener('error') anywhere in the file.

The whole file is judged at Program:exit, so a listener registered after the .pipe() still counts. Statement order is not the criterion.

pipeline() is never reported. It destroys every stream and surfaces the failure through its callback or rejected promise, which is exactly the fix this rule recommends — reporting it would be reporting the mitigation.

Examples

❌ Incorrect

import fs from 'fs';

// Constructed inline — nothing can have listened to it
function download(req, res) {
  fs.createReadStream(`./uploads/${req.params.id}`).pipe(res);
}

// The DESTINATION is constructed inline; a disk error is equally fatal
busboy.on('file', (name, file) => {
  file.pipe(fs.createWriteStream(`./tmp/${name}`));
});

// Named, resolvable, never handled
const stream = fs.createReadStream('/etc/hosts');
stream.pipe(res);

✅ Correct

import fs from 'fs';
import { pipeline } from 'stream/promises';

// Name it, handle 'error', then pipe
function download(req, res) {
  const stream = fs.createReadStream(`./uploads/${req.params.id}`);
  stream.on('error', () => {
    if (!res.headersSent) res.status(404).end();
  });
  stream.pipe(res);
}

// Or use pipeline(), which destroys every stream and reports the failure
async function downloadSafely(req, res) {
  try {
    await pipeline(fs.createReadStream(`./uploads/${req.params.id}`), res);
  } catch {
    if (!res.headersSent) res.status(404).end();
  }
}

Configuration

OptionTypeDefaultDescription
allowInTestsbooleantrueAllow unhandled stream errors in test files
{
  rules: {
    'node-security/require-stream-error-handler': ['error', {
      allowInTests: true
    }]
  }
}

Security Impact

VulnerabilityCWEOWASPCVSSImpact
Uncaught Exception248A04:20217.5 HighProcess exit — remote denial of service
Improper Error Handling391A04:20215.3 MedPartial writes, truncated responses

Known False Negatives

Handlers attached in another module

Why: A bare identifier with no visible binding may well be handled by the code that created it. "I could not prove this is handled" is not a finding.

// ❌ NOT DETECTED — provenance is outside this file
export function forward(incoming, outgoing) {
  incoming.pipe(outgoing);
}

Mitigation: Attach the listener where the stream is created, or use pipeline() at the boundary.

Handlers registered through a helper

Why: The rule looks for a literal 'error' event name on the stream's own name.

// ❌ NOT DETECTED
const s = fs.createReadStream(p);
attachStandardHandlers(s);
s.pipe(res);

Mitigation: Prefer pipeline(), which needs no listener bookkeeping at all.

Further Reading

⚙️ Options

OptionTypeDefaultDescription
allowInTestsbooleantrueAllow unhandled stream errors in test files

Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.