Skip to main content
ESLint Interlace
Plugin: operabilityRules

no-verbose-error-messages

Prevent exposing stack traces to users in API responses

Keywords: stack trace, error message, information disclosure, CWE-209, production, API response CWE: CWE-209
OWASP: A01:2021-Broken Access Control

Prevents exposing stack traces and verbose error details to users through API responses. This rule is part of eslint-plugin-operability and provides LLM-optimized error messages.

⚠️ Quality/Security rule | 📋 Set to warn in recommended

Quick Summary

AspectDetails
CWE ReferenceCWE-209 (Info in Errors)
SeverityMedium (information disclosure)
Auto-Fix❌ No auto-fix (requires safe error handling)
CategoryQuality / Operability
Best ForExpress/Fastify/Koa applications with API endpoints

Vulnerability and Risk

Vulnerability: Exposing stack traces in API responses reveals internal application structure, file paths, library versions, and code organization to potential attackers.

Risk: Verbose error messages help attackers:

  • Map internal application structure
  • Identify vulnerable dependencies
  • Craft targeted exploits
  • Understand deployment environment

Rule Details

This rule detects:

  • res.send(error.stack) - directly sending stack traces
  • res.json({ stack: error.stack }) - stack traces in JSON responses
  • Response objects containing stack properties

Why This Matters

RiskImpactSolution
🔍 ReconnaissanceAttackers learn internal detailsReturn generic error messages
📂 Path DisclosureFile paths reveal deployment structureLog details server-side only
📚 Version LeakLibrary versions expose vulnerabilitiesUse error codes, not raw errors

Configuration

This rule has no configuration options.

{
  rules: {
    'operability/no-verbose-error-messages': 'error'
  }
}

Examples

❌ Incorrect

app.use((err, req, res, next) => {
  // Sending stack trace directly
  res.status(500).send(err.stack); // ❌ Stack trace exposed
});

app.get('/api/data', async (req, res) => {
  try {
    const data = await getData();
    res.json(data);
  } catch (error) {
    // Stack trace in JSON response
    res.status(500).json({
      message: error.message,
      stack: error.stack, // ❌ Stack trace in response
    });
  }
});

// Error object with stack property
app.use((err, req, res, next) => {
  res.json({
    error: true,
    stack: err.stack, // ❌ Exposed
    path: __dirname, // ❌ Also exposed
  });
});

✅ Correct

app.use((err, req, res, next) => {
  // Log full error server-side
  console.error('Request failed:', {
    // ✅ Server-side logging
    error: err,
    stack: err.stack,
    requestId: req.id,
  });

  // Return generic message to client
  res.status(500).json({
    error: 'Internal server error', // ✅ Generic message
    requestId: req.id, // ✅ Correlation ID for support
  });
});

// Better: Custom error handler with error codes
class AppError extends Error {
  constructor(
    public code: string,
    public statusCode: number,
    message: string,
  ) {
    super(message);
  }
}

app.use((err, req, res, next) => {
  console.error(err); // ✅ Full error logged server-side

  if (err instanceof AppError) {
    res.status(err.statusCode).json({
      code: err.code, // ✅ User-facing code
      message: err.message, // ✅ Safe message
    });
  } else {
    res.status(500).json({
      code: 'INTERNAL_ERROR',
      message: 'An unexpected error occurred',
    });
  }
});

Error Handling Best Practices

Use Error Codes

const ERROR_CODES = {
  VALIDATION_ERROR: { status: 400, message: 'Invalid input' },
  NOT_FOUND: { status: 404, message: 'Resource not found' },
  UNAUTHORIZED: { status: 401, message: 'Authentication required' },
  INTERNAL: { status: 500, message: 'Internal server error' },
};

function sendError(res, code, requestId) {
  const error = ERROR_CODES[code] || ERROR_CODES.INTERNAL;
  res.status(error.status).json({
    error: code,
    message: error.message,
    requestId,
  });
}

Environment-Aware Handler

app.use((err, req, res, next) => {
  console.error(err);

  const response = {
    error: 'Internal server error',
    requestId: req.id,
  };

  // Only include stack in development
  if (process.env.NODE_ENV === 'development') {
    response.stack = err.stack; // ⚠️ Development only
  }

  res.status(500).json(response);
});

Security Impact

VulnerabilityCWEOWASPCVSSImpact
Info Disclosure209A01:20214.3 MediumReconnaissance aid
Error Handling755A01:20213.7 LowInformation leakage

Known False Negatives

Indirect Stack Access

Why: Indirect property access not tracked.

// ❌ NOT DETECTED - indirect access
const key = 'stack';
res.json({ [key]: error[key] });

Mitigation: Use explicit error sanitization functions.

Further Reading