Skip to content

Security: feathersjs/feathers-elasticsearch

Security

docs/SECURITY.md

Security Policy

Overview

This document outlines the security considerations, known issues, and best practices for using the Feathers Elasticsearch adapter in production environments.

Last Security Review: 2025-11-03
Last Security Update: 2025-11-03
Overall Risk Level: LOW (after v4.0.0 security improvements)
Production Ready: Yes


βœ… Security Features Implemented (v4.0.0)

The following security improvements have been implemented in version 4.0.0:

1. Query Depth Validation βœ…

  • What: Prevents stack overflow attacks via deeply nested queries
  • Default: Maximum depth of 50 levels
  • Configuration: security.maxQueryDepth
  • Impact: Blocks malicious queries like { $or: [{ $or: [...] }] } nested 1000+ levels deep

2. Bulk Operation Limits βœ…

  • What: Prevents DoS via mass update/delete operations
  • Default: Maximum 10,000 documents per bulk operation
  • Configuration: security.maxBulkOperations
  • Impact: Prevents accidental or malicious operations affecting millions of documents

3. Raw Method Whitelist βœ…

  • What: Restricts which Elasticsearch API methods can be called via raw()
  • Default: All methods disabled (empty whitelist)
  • Configuration: security.allowedRawMethods
  • Impact: BREAKING CHANGE - Must explicitly enable raw methods needed

4. Query String Sanitization βœ…

  • What: Prevents regex DoS attacks in $sqs (simple query string) operator
  • Default: Validates against catastrophic backtracking patterns, 500 char limit
  • Configuration: security.maxQueryStringLength
  • Impact: Blocks patterns like /.*.*.*.* that cause CPU exhaustion

5. Security Configuration API βœ…

  • What: Centralized security settings with sensible defaults
  • Access: Via service.security property
  • Configuration: Pass security object in service options

πŸ”§ Security Configuration

Configure security settings when creating the service:

import { Client } from '@elastic/elasticsearch';
import service from 'feathers-elasticsearch';

const client = new Client({ node: 'http://localhost:9200' });

app.use('/my-service', service({
  Model: client,
  index: 'my-index',
  
  // Security configuration
  security: {
    // Query complexity limits
    maxQueryDepth: 50,              // Max nesting for $or/$and/$nested (default: 50)
    maxArraySize: 10000,            // Max items in $in/$nin arrays (default: 10000)
    
    // Bulk operation limits
    maxBulkOperations: 10000,       // Max documents in bulk patch/remove (default: 10000)
    
    // Document size limits
    maxDocumentSize: 10485760,      // 10MB max document size (default: 10MB)
    
    // Query string limits for $sqs
    maxQueryStringLength: 500,      // Max length of $sqs queries (default: 500)
    
    // Raw method whitelist (IMPORTANT: empty by default = all disabled)
    allowedRawMethods: [
      'search',                      // Allow search operations
      'count',                       // Allow count operations
      // 'indices.delete',           // DON'T enable destructive operations!
    ],
    
    // Cross-index query restrictions
    allowedIndices: [],             // Empty = only service's index allowed
                                    // Or specify: ['index1', 'index2']
    
    // Field restrictions for $sqs queries
    searchableFields: [],           // Empty = all fields searchable
                                    // Or specify: ['name', 'email', 'bio']
    
    // Error verbosity
    enableDetailedErrors: false,    // true in dev, false in production
    
    // Input sanitization
    enableInputSanitization: true,  // Prevent prototype pollution
  }
}));

Default Security Settings

If you don't provide a security configuration, these defaults are used:

{
  maxQueryDepth: 50,
  maxArraySize: 10000,
  maxBulkOperations: 10000,
  maxDocumentSize: 10485760,      // 10MB
  maxQueryStringLength: 500,
  allowedRawMethods: [],           // ⚠️  ALL RAW METHODS DISABLED
  allowedIndices: [],              // Only default index allowed
  searchableFields: [],            // All fields searchable
  enableDetailedErrors: process.env.NODE_ENV !== 'production',
  enableInputSanitization: true
}

Security Review Summary

A comprehensive security review identified no critical vulnerabilities. The high-severity issues found have been addressed in v4.0.0.

Security Status After v4.0.0

  • βœ… Query depth validation - RESOLVED
  • βœ… Bulk operation limits - RESOLVED
  • βœ… Raw method whitelist - RESOLVED
  • βœ… Query string sanitization - RESOLVED
  • βœ… TypeScript strict mode enabled - Excellent type safety
  • βœ… No code injection vulnerabilities - No use of eval(), new Function(), etc.
  • βœ… Strong input validation patterns - Consistent use of validateType()
  • ⚠️ Information disclosure - Error messages detailed in dev mode (by design)
  • ℹ️ Index name validation - Optional, configure via security.allowedIndices

πŸ”΄ High Severity Issues (RESOLVED in v4.0.0)

1. Unrestricted Raw Elasticsearch API Access

Status: βœ… RESOLVED in v4.0.0
Severity: HIGH
Component: raw() method

Description:
The raw() method allows arbitrary Elasticsearch API calls without authentication, authorization, or input validation. This can be exploited to delete indices, modify cluster settings, or access unauthorized data.

Resolution:
As of v4.0.0, the raw() method is disabled by default. All raw methods are blocked unless explicitly whitelisted via security.allowedRawMethods.

Migration Guide:

If your application uses raw(), you must whitelist the methods:

// v3.x - raw() was unrestricted
app.use('/elasticsearch', service({
  Model: client,
  // ... other options
}));

// v4.0+ - Must whitelist methods
app.use('/elasticsearch', service({
  Model: client,
  security: {
    allowedRawMethods: ['search', 'count']  // Only allow safe read operations
  }
}));
app.service('elasticsearch').hooks({
  before: {
    raw: [disallow('external')]  // Block from external clients
  }
});

Option B - Implement strict whitelist:

const ALLOWED_RAW_METHODS = new Set(['search', 'count', 'explain']);

app.service('elasticsearch').hooks({
  before: {
    raw: [
      context => {
        const method = context.arguments[0];
        if (!ALLOWED_RAW_METHODS.has(method)) {
          throw new errors.MethodNotAllowed(`Method '${method}' is not allowed`);
        }
      }
    ]
  }
});

2. Elasticsearch Query DSL Injection

Status: Known Issue
Severity: HIGH
Component: $sqs (simple query string) operator

Description:
The $sqs operator accepts user-controlled query strings passed directly to Elasticsearch without sanitization, potentially allowing query injection attacks or regex DoS.

Mitigation:

// Add validation hook
app.service('elasticsearch').hooks({
  before: {
    find: [
      context => {
        const { query } = context.params;
        
        if (query && query.$sqs) {
          // Validate query string length
          if (query.$sqs.$query.length > 500) {
            throw new errors.BadRequest('Query string too long');
          }
          
          // Prevent regex patterns that could cause catastrophic backtracking
          if (/\/\.\*(\.\*)+/.test(query.$sqs.$query)) {
            throw new errors.BadRequest('Invalid query pattern');
          }
          
          // Whitelist allowed fields
          const allowedFields = ['name', 'description', 'tags'];
          const requestedFields = query.$sqs.$fields || [];
          
          for (const field of requestedFields) {
            const cleanField = field.replace(/\^.*$/, '');
            if (!allowedFields.includes(cleanField)) {
              throw new errors.BadRequest(`Field '${field}' is not searchable`);
            }
          }
        }
      }
    ]
  }
});

3. Denial of Service via Unbounded Operations

Status: Known Issue
Severity: HIGH
Components: Bulk patch, bulk remove, complex queries

Description:
Several operations lack safeguards against resource exhaustion:

  • No maximum limit on bulk operations (could patch/remove millions of documents)
  • No query timeout enforcement
  • No validation on deeply nested queries

Mitigation:

app.service('elasticsearch').hooks({
  before: {
    find: [
      // Limit query complexity
      context => {
        const depth = getQueryDepth(context.params.query);
        if (depth > 50) {
          throw new errors.BadRequest('Query too complex');
        }
      }
    ],
    patch: [
      // Restrict bulk patches
      async context => {
        if (context.id === null) {
          // This is a bulk operation - check how many documents would be affected
          const count = await context.service.find({
            ...context.params,
            paginate: false,
            query: { ...context.params.query, $limit: 0 }
          });
          
          const maxBulk = 1000;
          if (count.total > maxBulk) {
            throw new errors.BadRequest(
              `Bulk operation would affect ${count.total} documents, maximum is ${maxBulk}`
            );
          }
        }
      }
    ],
    remove: [
      // Restrict bulk deletes (or disable entirely)
      context => {
        if (context.id === null) {
          throw new errors.MethodNotAllowed('Bulk deletes not allowed');
        }
      }
    ]
  }
});

// Helper function to calculate query depth
function getQueryDepth(query, depth = 0) {
  if (!query || typeof query !== 'object') return depth;
  
  let maxDepth = depth;
  for (const key of Object.keys(query)) {
    if (key === '$or' || key === '$and') {
      const value = query[key];
      if (Array.isArray(value)) {
        for (const item of value) {
          maxDepth = Math.max(maxDepth, getQueryDepth(item, depth + 1));
        }
      }
    }
  }
  return maxDepth;
}

🟑 Medium Severity Issues

4. Sensitive Information Disclosure in Errors

Status: Known Issue
Severity: MEDIUM
Component: Error handler

Description:
Detailed Elasticsearch error information is returned to clients, potentially exposing internal system details like index structure, field names, and cluster configuration.

Mitigation:

app.service('elasticsearch').hooks({
  error: {
    all: [
      context => {
        if (process.env.NODE_ENV === 'production') {
          // Log full error server-side
          console.error('Elasticsearch error:', context.error);
          
          // Return generic message to client
          if (context.error.details) {
            delete context.error.details;
          }
          if (context.error.stack) {
            delete context.error.stack;
          }
          
          // Use generic messages
          const genericMessages = {
            400: 'Invalid request parameters',
            404: 'Resource not found',
            409: 'Resource conflict',
            500: 'Internal server error'
          };
          
          const status = context.error.code || 500;
          context.error.message = genericMessages[status] || genericMessages[500];
        }
      }
    ]
  }
});

5. Missing Index Name Validation

Status: Known Issue
Severity: MEDIUM
Component: $index filter

Description:
The $index filter allows users to specify arbitrary index names without validation, potentially enabling cross-index data access.

Mitigation:

// Option A - Disable $index filter entirely (recommended)
app.use('/elasticsearch', service({
  Model: client,
  index: 'my-index',
  filters: {
    $index: undefined  // Remove $index filter
  }
}));

// Option B - Implement index whitelist
const allowedIndices = ['my-index', 'my-index-staging'];

app.service('elasticsearch').hooks({
  before: {
    all: [
      context => {
        const requestedIndex = context.params.query?.$index;
        
        if (requestedIndex && !allowedIndices.includes(requestedIndex)) {
          throw new errors.Forbidden(`Access to index '${requestedIndex}' is not allowed`);
        }
      }
    ]
  }
});

6. Prototype Pollution Risk

Status: Known Issue
Severity: MEDIUM
Component: Object operations in multiple files

Description:
User-controlled object properties could potentially be used for prototype pollution attacks through document data or query parameters.

Mitigation:

// Sanitize input data
function sanitizeObject(obj) {
  if (!obj || typeof obj !== 'object') return obj;
  
  const dangerous = ['__proto__', 'constructor', 'prototype'];
  const sanitized = {};
  
  for (const key of Object.keys(obj)) {
    if (dangerous.includes(key)) {
      continue;  // Skip dangerous keys
    }
    
    const value = obj[key];
    sanitized[key] = typeof value === 'object' && value !== null
      ? sanitizeObject(value)
      : value;
  }
  
  return sanitized;
}

app.service('elasticsearch').hooks({
  before: {
    create: [
      context => {
        context.data = sanitizeObject(context.data);
      }
    ],
    update: [
      context => {
        context.data = sanitizeObject(context.data);
      }
    ],
    patch: [
      context => {
        context.data = sanitizeObject(context.data);
      }
    ]
  }
});

7. Dependency Vulnerabilities

Status: Known Issue
Severity: MEDIUM (Development only)
Component: Development dependencies

Description:
npm audit identified 9 vulnerabilities in development dependencies. These do NOT affect production runtime but should be addressed for secure development environments.

Mitigation:

# Update dependencies
npm audit fix

# For unfixable issues, consider removing dtslint if not actively used
npm uninstall dtslint

# Add audit to CI/CD
npm audit --production  # Only check production dependencies

🟒 Low Severity Issues

8. Missing Rate Limiting

Applications should implement rate limiting at the Feathers hooks level to prevent abuse.

9. Missing Request Size Limits

Document size validation should be added for create/update operations.

10. Query Cache Memory Usage

The WeakMap cache could grow indefinitely in long-running processes. Consider implementing an LRU cache with TTL.


πŸ›‘οΈ Production Deployment Security Checklist

Required Actions

  • Disable or restrict raw() method access
  • Implement bulk operation limits (max 1,000-10,000 documents)
  • Add query complexity validation
  • Sanitize error messages in production
  • Validate or disable $index filter
  • Implement input sanitization for all create/update operations
  • Run npm audit fix for development environment

Recommended Actions

  • Enable authentication on all service methods
  • Implement authorization hooks (e.g., feathers-casl)
  • Add rate limiting
  • Configure Elasticsearch client with SSL/TLS
  • Set request timeouts (30 seconds recommended)
  • Enable audit logging for sensitive operations
  • Implement document size validation
  • Add field whitelisting for $sqs queries
  • Set up automated security scanning in CI/CD

Environment Configuration

# Required environment variables
NODE_ENV=production
ELASTICSEARCH_URL=https://your-cluster:9200
ES_USERNAME=app_user
ES_PASSWORD=strong_password

# Security settings
MAX_BULK_OPERATIONS=1000
MAX_QUERY_DEPTH=50
MAX_DOCUMENT_SIZE=10485760  # 10MB
ENABLE_RAW_METHOD=false

πŸ”’ Elasticsearch Client Security

Configure your Elasticsearch client with security best practices:

import { Client } from '@elastic/elasticsearch';

const client = new Client({
  node: process.env.ELASTICSEARCH_URL,
  
  // Authentication
  auth: {
    username: process.env.ES_USERNAME,
    password: process.env.ES_PASSWORD
  },
  
  // SSL/TLS
  ssl: {
    rejectUnauthorized: true,  // Verify certificates
    ca: fs.readFileSync('./ca.crt'),  // CA certificate
  },
  
  // Performance and DoS protection
  maxRetries: 3,
  requestTimeout: 30000,  // 30 second timeout
  sniffOnConnectionFault: false,  // Prevent node enumeration
  maxSockets: 10,  // Limit concurrent connections
  maxFreeSockets: 5
});

πŸ“Š Security Metrics

Category Count Status
Critical Issues 0 βœ… None found
High Severity 3 ⚠️ Mitigations documented
Medium Severity 4 ⚠️ Mitigations documented
Low Severity 3 ℹ️ Optional improvements
Code Coverage 94.21% βœ… Excellent
TypeScript Strict Mode Enabled βœ… Excellent

πŸ› Reporting Security Vulnerabilities

If you discover a security vulnerability in this package, please report it by:

  1. DO NOT open a public GitHub issue
  2. Email the maintainers directly at: [email protected]
  3. Include:
    • Description of the vulnerability
    • Steps to reproduce
    • Potential impact
    • Suggested fix (if any)

We will respond within 48 hours and work with you to address the issue.


πŸ“š Additional Resources


πŸ“ Changelog

2025-11-03

  • Initial security review completed
  • Documented 3 high-severity issues
  • Documented 4 medium-severity issues
  • Added production deployment checklist
  • Created mitigation examples

Security is a shared responsibility. This document provides guidance, but each application must implement appropriate security controls based on its specific requirements and threat model.

There aren’t any published security advisories