HIGH data exposureexpress

Data Exposure in Express

How Data Exposure Manifests in Express

Data exposure in Express applications occurs when sensitive information is inadvertently returned to clients through API responses. This manifests in several Express-specific ways:

  • Debug information in error responses - When Express encounters an error, it may include stack traces or internal server details in the response body. The default error handler in development mode sends full stack traces, exposing file paths, line numbers, and potentially database queries.
  • Verbose logging middleware - Middleware like Morgan or custom loggers may capture and expose sensitive request data (headers, bodies, query parameters) in logs that are accessible to unauthorized users.
  • Unintended property exposure - When using res.json() or res.send() with objects, Express serializes all enumerable properties. If objects contain sensitive fields (passwords, API keys, internal IDs), these get exposed without explicit filtering.
  • Header information leakage - Express's default headers (X-Powered-By: Express) and custom error headers can reveal implementation details that aid attackers.
  • Database error propagation - When database operations fail, Express may return raw error objects containing SQL queries, connection strings, or database schema information.

Consider this vulnerable Express pattern:

app.get('/users/:id', async (req, res) => {
  try {
    const user = await User.findById(req.params.id);
    res.json(user); // Exposes password hash, internal IDs
  } catch (err) {
    res.status(500).json({ error: err.message, stack: err.stack }); // Exposes stack trace
  }
});

This code exposes the user's password hash and internal database IDs to any authenticated user who can guess IDs, plus detailed stack traces on errors.

Express-Specific Detection

Detecting data exposure in Express requires both manual code review and automated scanning. Here's how to identify these vulnerabilities:

Manual Code Review Patterns

  • Search for res.json() and res.send() calls that serialize entire objects without property filtering
  • Look for error handling that sends err.message or err.stack directly to clients
  • Check for verbose logging middleware that logs request bodies or headers
  • Review database query error handling for raw error exposure

middleBrick Scanning

middleBrick's Data Exposure check specifically targets Express applications by:

  • Analyzing API responses for sensitive data patterns (passwords, tokens, keys, PII)
  • Testing error endpoints to see if stack traces are exposed
  • Checking response headers for verbose information
  • Scanning OpenAPI specs for endpoints that might return sensitive data

The scanner runs 12 parallel security checks including Data Exposure, testing your unauthenticated attack surface in 5-15 seconds. For Express apps, it specifically looks for:

{
  "data_exposure": {
    "high_risk": ["password", "token", "api_key"],
    "medium_risk": ["email", "phone", "address"],
    "headers_exposed": ["x-powered-by", "server"],
    "error_details": ["stack_traces", "internal_errors"]
  }
}

middleBrick's black-box scanning tests your running Express API without requiring credentials or code access, making it ideal for detecting data exposure in production environments.

Express-Specific Remediation

Fixing data exposure in Express requires both code changes and configuration adjustments. Here are Express-specific solutions:

1. Property Filtering with Select/Projection

Instead of serializing entire objects, explicitly select which properties to return:

app.get('/users/:id', async (req, res) => {
  try {
    const user = await User.findById(req.params.id, 'name email role');
    res.json(user); // Only returns specified fields
  } catch (err) {
    res.status(404).json({ error: 'User not found' });
  }
});

2. Error Handling Middleware

Replace default error handling with Express middleware that sanitizes responses:

// Centralized error handler
app.use((err, req, res, next) => {
  console.error(err); // Log full error internally
  
  const errorResponse = {
    message: process.env.NODE_ENV === 'development' 
      ? err.message 
      : 'Internal server error',
    statusCode: err.statusCode || 500
  };
  
  res.status(errorResponse.statusCode).json(errorResponse);
});

// For async route handlers
const asyncHandler = fn => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

app.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id);
  if (!user) throw new Error('User not found');
  res.json({ name: user.name, email: user.email });
}));

3. Response Sanitization Middleware

Create middleware to sanitize responses before sending:

const sanitizeResponse = (obj, allowedFields) => {
  if (Array.isArray(obj)) {
    return obj.map(item => sanitizeResponse(item, allowedFields));
  }
  
  if (typeof obj === 'object' && obj !== null) {
    return Object.keys(obj)
      .filter(key => allowedFields.includes(key))
      .reduce((acc, key) => {
        acc[key] = obj[key];
        return acc;
      }, {});
  }
  
  return obj;
};

app.use((req, res, next) => {
  const oldSend = res.send;
  res.send = function(data) {
    if (typeof data === 'object') {
      const sanitized = sanitizeResponse(data, ['id', 'name', 'email']);
      oldSend.call(this, sanitized);
    } else {
      oldSend.call(this, data);
    }
  };
  next();
});

4. Security Headers Middleware

Remove verbose headers and add security headers:

const helmet = require('helmet');
app.use(helmet({
  hidePoweredBy: { setTo: 'PHP 4.2.0' }, // Obfuscate technology
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'"],
      imgSrc: ["'self'", 'data:', 'https:']
    }
  }
}));

5. Logging Configuration

Configure logging to avoid exposing sensitive data:

const morgan = require('morgan');
const fs = require('fs');

// Custom token that excludes sensitive headers
morgan.token('body', (req, res) => {
  const { password, token, api_key, ...safeBody } = req.body;
  return JSON.stringify(safeBody);
});

// Log to file without sensitive data
app.use(morgan(':method :url :status :response-time ms - :body', {
  stream: fs.createWriteStream('./access.log', { flags: 'a' })
}));

Related CWEs: dataExposure

CWE IDNameSeverity
CWE-200Exposure of Sensitive Information HIGH
CWE-209Error Information Disclosure MEDIUM
CWE-213Exposure of Sensitive Information Due to Incompatible Policies HIGH
CWE-215Insertion of Sensitive Information Into Debugging Code MEDIUM
CWE-312Cleartext Storage of Sensitive Information HIGH
CWE-359Exposure of Private Personal Information (PII) HIGH
CWE-522Insufficiently Protected Credentials CRITICAL
CWE-532Insertion of Sensitive Information into Log File MEDIUM
CWE-538Insertion of Sensitive Information into Externally-Accessible File HIGH
CWE-540Inclusion of Sensitive Information in Source Code HIGH

Frequently Asked Questions

How does middleBrick detect data exposure in my Express API?
middleBrick performs black-box scanning of your running Express API, testing endpoints for sensitive data exposure without requiring credentials or code access. It analyzes API responses for patterns like passwords, tokens, API keys, and PII, checks error responses for stack traces, and examines headers for verbose information. The scanner runs in 5-15 seconds and provides a security risk score with prioritized findings and remediation guidance.
What's the difference between data exposure and information disclosure?
Data exposure specifically refers to sensitive business data (user PII, credentials, financial data) being returned in API responses, while information disclosure is broader, including implementation details like stack traces, file paths, and technology stack information. In Express, data exposure often manifests as serializing entire database objects, while information disclosure appears in verbose error messages and headers. Both are covered by middleBrick's Data Exposure check.