HIGH insufficient loggingsails

Insufficient Logging in Sails

How Insufficient Logging Manifests in Sails

Sails.js applications often suffer from insufficient logging in several critical areas that directly impact security visibility. The framework's convention-over-configuration approach can lead developers to overlook logging in places where it's essential for detecting attacks.

A common manifestation occurs in Sails's policy system. When authentication policies fail, Sails doesn't automatically log the failure by default. An attacker can brute-force API endpoints without triggering any security alerts. For example:

// In api/policies/authPolicy.js
module.exports = async function (req, res, next) {
  const token = req.headers.authorization;
  if (!token || !await validateToken(token)) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  next();
};

This policy returns a 401 response but doesn't log the failed authentication attempt. An attacker can repeatedly call this endpoint with invalid tokens, and you'll have no record of the attack pattern.

Another critical area is Sails's Waterline ORM operations. When database queries fail due to authorization issues, Sails often returns generic error messages without logging the context. Consider this controller action:

// In api/controllers/UserController.js
module.exports = {
  updateProfile: async function (req, res) {
    try {
      const updatedUser = await User.updateOne(
        { id: req.params.id },
        req.body
      ).fetch();
      return res.json(updatedUser);
    } catch (err) {
      return res.status(500).json({ error: 'Update failed' });
    }
  }
};

When a user tries to update another user's profile (a BOLA/IDOR vulnerability), this code catches the error but doesn't log who attempted the unauthorized access or what data they tried to modify. The generic 'Update failed' message gives attackers no indication they've been detected while providing you no forensic information.

Sails's default error handling in production mode is particularly problematic. When sails.config.environment is 'production', the framework suppresses detailed error messages and stack traces. While this prevents information disclosure to attackers, it also means legitimate errors that could indicate security issues go completely unlogged. An attacker exploiting SQL injection or path traversal might trigger database errors that never appear in your logs.

The framework's blueprint routes present another logging blind spot. Sails automatically generates CRUD routes for models, and when these routes are called with invalid parameters or unauthorized access attempts, the default behavior is to return 404 or 403 without any logging. An attacker systematically probing your API surface through blueprint routes leaves no trace in your logging infrastructure.

Sails-Specific Detection

Detecting insufficient logging in Sails applications requires examining both the application code and runtime behavior. Start by reviewing your policies and controllers for missing log statements around security-critical operations.

For authentication failures, look for patterns like:

// Problematic: no logging
if (!await validateToken(token)) {
  return res.status(401).json({ error: 'Unauthorized' });
}

// Secure: proper logging
if (!await validateToken(token)) {
  sails.log.warn('Failed authentication attempt', {
    ip: req.ip,
    userAgent: req.headers['user-agent'],
    endpoint: req.path
  });
  return res.status(401).json({ error: 'Unauthorized' });
}

The secure version uses Sails's built-in logging system to capture the IP address, user agent, and endpoint being accessed. This information is crucial for identifying attack patterns.

For database operations, insufficient logging often manifests as missing audit trails for data modifications. In Sails, you can detect this by checking your model lifecycle callbacks:

// In api/models/User.js
module.exports = {
  afterUpdate: async function (values, proceed) {
    sails.log.info('User data modified', {
      userId: values.id,
      modifiedBy: req.session.userId, // if available
      changes: values,
      timestamp: new Date()
    });
    proceed();
  },
  
  afterDestroy: async function (criteria, proceed) {
    sails.log.warn('User record deleted', {
      deletedBy: req.session.userId,
      criteria: criteria,
      timestamp: new Date()
    });
    proceed();
  }
};

These lifecycle callbacks ensure that any data modifications are logged with context about who made the change and what was changed.

middleBrick's scanner can detect insufficient logging patterns in Sails applications by analyzing the runtime behavior of your API endpoints. The scanner sends requests that should trigger authentication failures, authorization errors, and invalid parameter scenarios, then checks whether the application provides appropriate logging responses. For Sails applications, middleBrick specifically looks for:

  • Missing log statements in policy authentication failures
  • Lack of audit logging for database CRUD operations
  • Absence of error context in production error handling
  • Missing logging for blueprint route access attempts
  • Insufficient logging for rate limiting and throttling events

The scanner can identify these issues without requiring access to your source code, making it ideal for testing deployed Sails applications.

Sails-Specific Remediation

Remediating insufficient logging in Sails requires implementing comprehensive logging across all security-sensitive operations. Start with Sails's built-in logging system, which provides different log levels suitable for security events.

For authentication and authorization failures, implement structured logging that captures attack context:

// Enhanced auth policy with comprehensive logging
const logger = require('sails').log;

module.exports = async function (req, res, next) {
  const startTime = Date.now();
  const token = req.headers.authorization;
  
  if (!token) {
    logger.warn('Missing authentication token', {
      ip: req.ip,
      userAgent: req.headers['user-agent'],
      endpoint: req.path,
      timestamp: new Date(),
      responseTime: Date.now() - startTime
    });
    return res.status(401).json({ 
      error: 'Missing token', 
      requestId: req.id 
    });
  }
  
  try {
    const payload = await validateToken(token);
    if (!payload) {
      logger.warn('Invalid authentication token', {
        ip: req.ip,
        userAgent: req.headers['user-agent'],
        endpoint: req.path,
        timestamp: new Date(),
        responseTime: Date.now() - startTime
      });
      return res.status(401).json({ 
        error: 'Invalid token', 
        requestId: req.id 
      });
    }
    
    req.user = payload;
    next();
  } catch (error) {
    logger.error('Authentication processing error', {
      ip: req.ip,
      userAgent: req.headers['user-agent'],
      endpoint: req.path,
      error: error.message,
      timestamp: new Date(),
      responseTime: Date.now() - startTime
    });
    return res.status(500).json({ 
      error: 'Authentication service unavailable', 
      requestId: req.id 
    });
  }
};

This enhanced policy logs all authentication failures with structured data including IP address, user agent, endpoint, and timing information. The requestId helps correlate logs with client-side debugging.

For database operations, implement comprehensive audit logging using Sails's lifecycle callbacks:

// In api/models/AuditLog.js
module.exports = {
  attributes: {
    action: { type: 'string' },
    model: { type: 'string' },
    recordId: { type: 'string' },
    userId: { type: 'string' },
    changes: { type: 'json' },
    ipAddress: { type: 'string' },
    userAgent: { type: 'string' },
    timestamp: { type: 'number' }
  },
  
  log: async function (action, model, recordId, userId, changes, req) {
    await AuditLog.create({
      action,
      model,
      recordId,
      userId,
      changes,
      ipAddress: req?.ip,
      userAgent: req?.headers['user-agent'],
      timestamp: Date.now()
    });
  }
};

// In api/models/User.js
module.exports = {
  afterUpdate: async function (values, proceed) {
    await AuditLog.log(
      'update',
      'User',
      values.id,
      this.req?.user?.id,
      values,
      this.req
    );
    proceed();
  },
  
  afterDestroy: async function (criteria, proceed) {
    await AuditLog.log(
      'delete',
      'User',
      criteria.id,
      this.req?.user?.id,
      { criteria },
      this.req
    );
    proceed();
  }
};

This audit logging system captures all data modifications with comprehensive context, creating an immutable trail of changes that's essential for security incident response.

For error handling in production, implement a centralized error logging system that captures security-relevant information without exposing it to attackers:

// In config/http.js
module.exports.http = {
  middleware: {
    order: [...],
    securityErrorHandler: function (req, res, next) {
      return function (err, req, res, next) {
        if (err.status === 401 || err.status === 403) {
          sails.log.warn('Authorization error', {
            ip: req.ip,
            userAgent: req.headers['user-agent'],
            endpoint: req.path,
            error: err.message,
            timestamp: new Date()
          });
        } else if (err.status >= 400 && err.status < 500) {
          sails.log.info('Client error', {
            ip: req.ip,
            userAgent: req.headers['user-agent'],
            endpoint: req.path,
            error: err.message,
            timestamp: new Date()
          });
        } else {
          sails.log.error('Server error', {
            ip: req.ip,
            userAgent: req.headers['user-agent'],
            endpoint: req.path,
            error: err.stack,
            timestamp: new Date()
          });
        }
        
        if (req.wantsJSON) {
          return res.status(err.status || 500).json({
            error: err.message || 'Internal Server Error',
            requestId: req.id
          });
        }
      };
    }
  }
};

This error handler ensures that all errors are logged with appropriate severity levels and context, while still providing appropriate responses to clients.

Finally, integrate your logging with centralized log management systems. Sails can easily integrate with Winston, Bunyan, or other Node.js logging libraries that support structured logging and centralized aggregation:

// In config/log.js
module.exports.log = {
  level: 'info',
  inspect: false,
  custom: function (obj) {
    if (process.env.NODE_ENV === 'production') {
      const winston = require('winston');
      const logger = winston.createLogger({
        transports: [
          new winston.transports.File({ filename: 'security.log' }),
          new winston.transports.Console()
        ]
      });
      
      if (obj.level === 'warn' || obj.level === 'error') {
        logger.log({
          level: obj.level,
          message: obj.message,
          metadata: obj.data
        });
      }
    }
  }
};

This configuration ensures that security-relevant log events are captured in a dedicated security log file that can be monitored and analyzed separately from general application logs.

Frequently Asked Questions

How does insufficient logging in Sails differ from other Node.js frameworks?
Sails's convention-over-configuration approach means many logging decisions are implicit rather than explicit. The framework's automatic blueprint routes and model lifecycle callbacks can mask security events if you don't add explicit logging. Unlike Express where you control every middleware, Sails generates routes and policies automatically, creating blind spots where security events occur without logging. Additionally, Sails's Waterline ORM abstracts database operations, making it harder to track authorization failures without implementing custom lifecycle callbacks.
Can middleBrick detect insufficient logging in my Sails application without access to source code?
Yes, middleBrick performs black-box scanning that tests your API endpoints' runtime behavior. The scanner sends requests designed to trigger authentication failures, authorization errors, and invalid operations, then analyzes the responses and any logging behavior it can detect. For Sails applications, middleBrick specifically looks for missing audit trails in CRUD operations, absent logging for blueprint route access attempts, and lack of structured logging for security events. The scanner provides a security score and detailed findings with remediation guidance, all without requiring credentials or source code access.