Insufficient Logging in Feathersjs
How Insufficient Logging Manifests in Feathersjs
Insufficient logging in Feathersjs applications creates blind spots that attackers exploit during reconnaissance and post-exploitation phases. The framework's service-based architecture and event-driven nature mean logging gaps can hide critical security events.
A common manifestation occurs in authentication failures. Many Feathersjs apps use the built-in authentication service but fail to log failed login attempts. Without logging, you miss brute-force attacks targeting user credentials. Consider this vulnerable pattern:
app.service('authentication').create({
strategy: 'local',
email: '[email protected]',
password: 'password123'
}); // No logging of failed attempts
Another critical gap appears in service method calls. Feathersjs services expose find, get, create, update, patch, and remove methods. Without logging, you cannot detect unauthorized data access or enumeration attempts:
// Vulnerable: No logging of service access
app.service('users').find({
query: { isAdmin: true } // Attacker probing for admin accounts
});
Event-driven hooks present another logging challenge. Feathersjs hooks run before, after, or on errors for service methods. If hooks that modify sensitive data lack logging, you lose audit trails:
// Vulnerable hook with no logging
app.service('payments').hooks({
before: {
patch: context => {
if (context.data.status === 'fraudulent') {
context.data.locked = true;
// Missing: log the fraud detection and account lock
}
}
}
});
WebSocket connections via Feathersjs real-time features create additional blind spots. Without logging connection attempts and disconnections, you cannot detect connection flooding or unauthorized real-time data access:
// Missing: log WebSocket authentication failures
const io = require('socket.io')(server);
io.use((socket, next) => {
const token = socket.handshake.query.token;
if (!validateToken(token)) {
// Missing: log failed authentication attempt
return next(new Error('Authentication failed'));
}
next();
});
Configuration changes represent another overlooked area. Feathersjs apps often modify security settings at runtime through configuration APIs. Without logging these changes, you cannot detect configuration tampering:
// Vulnerable: configuration changes not logged
app.configure(require('feathers-authentication'))
.defaults({
header: 'Authorization',
schemes: ['jwt', 'local']
});
// Missing: log when authentication schemes changeFeathersjs-Specific Detection
Detecting insufficient logging in Feathersjs requires examining both the application code and runtime behavior. Start with static code analysis to identify services and hooks that lack logging statements.
For authentication-related logging gaps, search for authentication service usage without corresponding log statements. The following pattern indicates a logging deficiency:
// Vulnerable: authentication without logging
const auth = require('feathers-authentication');
app.configure(auth({
local: {
entity: 'user',
service: 'users',
usernameField: 'email',
passwordField: 'password'
}
}));
// Missing: log successful and failed authentication attempts
Service method calls require logging at the service level. Use Feathersjs's extensibility to add logging middleware:
const { BadRequest, Forbidden, NotFound } = require('@feathersjs/errors');
module.exports = function loggingMiddleware() {
return async context => {
const { method, type, params, path } = context;
// Log all service calls
console.log(`Service ${path}.${method} called`, {
timestamp: new Date().toISOString(),
user: params.provider ? 'external' : 'internal',
method: type,
data: context.data
});
// Log errors specifically
if (context.error) {
console.error(`Service error in ${path}.${method}`, {
error: context.error.message,
stack: context.error.stack,
timestamp: new Date().toISOString()
});
}
};
}
// Apply to all services
app.hooks({
before: loggingMiddleware(),
after: loggingMiddleware(),
error: loggingMiddleware()
});
middleBrick's scanning approach specifically targets Feathersjs logging gaps. The scanner examines service definitions and hook chains to identify unlogged sensitive operations. For authentication services, middleBrick checks for:
- Missing login attempt logging
- Unlogged password reset operations
- Missing session management logging
For data access patterns, middleBrick analyzes:
- Service methods without audit trail logging
- Hook chains that modify data without logging
- Missing authorization failure logging
Dynamic analysis with middleBrick involves scanning your Feathersjs API endpoints to detect unlogged sensitive operations. The scanner sends test requests that trigger various service methods and checks for corresponding log entries or audit trail creation.
# Scan a Feathersjs API with middleBrick
middlebrick scan https://api.example.com
# Results show logging gaps:
# - Authentication service: FAILED (no login attempt logging)
# - Users service: FAILED (no audit trail for data access)
# - Payments service: FAILED (no fraud detection logging)Feathersjs-Specific Remediation
Remediating insufficient logging in Feathersjs requires implementing comprehensive logging across the application's service layer, authentication mechanisms, and event-driven components.
Start with authentication logging using Feathersjs's extensibility. Create a dedicated authentication logging hook:
const winston = require('winston');
const logger = winston.createLogger({
transports: [
new winston.transports.File({ filename: 'auth.log' })
]
});
module.exports = function authLoggingHook() {
return async context => {
const { type, method, result, error } = context;
const timestamp = new Date().toISOString();
if (type === 'before' && method === 'create' &&
context.path === 'authentication') {
const { strategy, email } = context.data;
logger.info(`Auth attempt: ${strategy} ${email}`, {
timestamp,
ip: context.params.ip,
userAgent: context.params.headers['user-agent']
});
}
if (type === 'after' && method === 'create' &&
context.path === 'authentication') {
const success = !context.error;
const userId = success ? result.data.user.id : null;
logger.info(`Auth ${success ? 'success' : 'failure'}`, {
timestamp,
userId,
email: context.data.email,
strategy: context.data.strategy,
ip: context.params.ip
});
}
if (error && context.path === 'authentication') {
logger.error(`Auth error: ${error.message}`, {
timestamp,
email: context.data?.email,
strategy: context.data?.strategy,
ip: context.params.ip,
stack: error.stack
});
}
};
}
// Apply to authentication service
app.service('authentication').hooks({
before: authLoggingHook(),
after: authLoggingHook(),
error: authLoggingHook()
});
Implement comprehensive service logging using Feathersjs's hook system. Create a generic logging hook that captures all service operations:
const { BadRequest, Forbidden, NotFound } = require('@feathersjs/errors');
const logger = require('./logger'); // Your configured logger
module.exports = function auditLoggingHook(options = {}) {
const { ignorePaths = [] } = options;
return async context => {
const { type, method, path, params, result, error } = context;
const timestamp = new Date().toISOString();
const userId = params.user?.id || 'anonymous';
const ip = params.ip || 'unknown';
// Skip ignored paths
if (ignorePaths.includes(path)) return context;
// Log before operations
if (type === 'before') {
logger.info(`Service ${path}.${method} called`, {
timestamp,
userId,
ip,
method: type,
data: context.data,
query: context.params.query
});
}
// Log after operations
if (type === 'after') {
logger.info(`Service ${path}.${method} completed`, {
timestamp,
userId,
ip,
method: type,
result: result.data || result
});
}
// Log errors
if (error) {
logger.error(`Service ${path}.${method} error`, {
timestamp,
userId,
ip,
error: error.message,
code: error.code,
stack: error.stack
});
}
return context;
};
}
// Apply to all services
app.hooks({
before: auditLoggingHook(),
after: auditLoggingHook(),
error: auditLoggingHook()
});
For real-time WebSocket connections, implement logging for Feathersjs socket.io integration:
const io = require('socket.io')(server);
const logger = require('./logger');
io.use((socket, next) => {
const timestamp = new Date().toISOString();
const ip = socket.handshake.headers['x-forwarded-for'] ||
socket.conn.remoteAddress;
logger.info('WebSocket connection attempt', {
timestamp,
ip,
userAgent: socket.handshake.headers['user-agent']
});
next();
});
io.on('connection', socket => {
const timestamp = new Date().toISOString();
const ip = socket.handshake.headers['x-forwarded-for'] ||
socket.conn.remoteAddress;
logger.info('WebSocket connected', {
timestamp,
socketId: socket.id,
ip,
userAgent: socket.handshake.headers['user-agent']
});
socket.on('disconnect', reason => {
logger.info('WebSocket disconnected', {
timestamp,
socketId: socket.id,
ip,
reason,
userAgent: socket.handshake.headers['user-agent']
});
});
});
Configure centralized logging with structured data for better analysis:
const winston = require('winston');
const { combine, timestamp, printf } = winston.format;
const logFormat = printf(({ level, message, timestamp, ...meta }) => {
return JSON.stringify({
level,
message,
timestamp,
...meta
});
});
const logger = winston.createLogger({
level: 'info',
format: combine(
timestamp(),
logFormat
),
transports: [
new winston.transports.File({ filename: 'api.log' }),
new winston.transports.Console()
]
});
module.exports = logger;