Missing Standardized Error Handling Mechanism

Description

Missing Standardized Error Handling Mechanism is a vulnerability where a product does not use a standardized method for handling errors throughout the code, leading to inconsistent error handling. Without a centralized or standardized approach, different parts of the application may handle errors differently—some logging them, some silently ignoring them, some exposing details to users. This inconsistency creates gaps where errors may be mishandled, security-relevant errors may go undetected, and the application's behavior becomes unpredictable during failure conditions.

Risk

Inconsistent error handling creates multiple security risks. When some code paths handle errors properly while others don't, attackers can probe for weaknesses by triggering errors in different areas. Error conditions that bypass logging allow attacks to go undetected. Inconsistent user-facing error messages may reveal sensitive information in some cases but not others, creating reconnaissance opportunities. Without standardized handling, developers may forget to handle certain error types, leading to uncaught exceptions that crash the application or leave it in an insecure state. The lack of uniformity also makes security audits difficult as reviewers must examine each error handling instance individually.

Solution

Implement a standardized error handling mechanism across the entire application. Create centralized error handling components or middleware that process all errors uniformly. Define error handling policies that specify how different error categories should be logged, reported, and presented to users. Use exception hierarchies and error codes to categorize errors consistently. Implement global exception handlers as a safety net. Ensure all errors are logged with appropriate detail for debugging while presenting sanitized messages to users. Create error handling documentation and enforce standards through code review and static analysis tools.

Common Consequences

ImpactDetails
IntegrityScope: Integrity

Alter Execution Logic - Inconsistent error handling can cause the application to behave unpredictably, potentially bypassing security checks or entering insecure states.
AccountabilityScope: Accountability, Non-Repudiation

Hide Activities - Without standardized logging of errors, security-relevant events may go unrecorded, preventing detection of attacks and hindering forensic analysis.
ConfidentialityScope: Confidentiality

Read Application Data - Inconsistent error messages may expose sensitive information in some code paths while properly hiding it in others.

Example Code

Vulnerable Code

// Vulnerable: No standardized error handling
public class VulnerableApplication {

    public void processPayment(String userId, double amount) {
        // Vulnerable: Different error handling approaches scattered throughout
        try {
            User user = userService.findUser(userId);
            if (user == null) {
                // Just return silently - no logging
                return;
            }
        } catch (DatabaseException e) {
            // Print stack trace to console - might be visible
            e.printStackTrace();
        }

        try {
            paymentService.charge(userId, amount);
        } catch (PaymentException e) {
            // Throw raw exception - exposes details
            throw new RuntimeException("Payment failed: " + e.getMessage());
        }
    }

    public void updateProfile(String userId, Map<String, String> data) {
        try {
            validateInput(data);
        } catch (ValidationException e) {
            // Different approach: log and swallow
            System.out.println("Validation failed: " + e);
        }

        try {
            profileService.update(userId, data);
        } catch (Exception e) {
            // Yet another approach: generic catch-all
            // Security errors mixed with regular errors
        }
    }

    public String fetchDocument(String docId) {
        try {
            return documentService.get(docId);
        } catch (AccessDeniedException e) {
            // Vulnerable: Reveals authorization details
            return "Error: You don't have permission to access " + docId +
                   " (required role: " + e.getRequiredRole() + ")";
        } catch (DocumentNotFoundException e) {
            // Different message format
            return null;  // Caller doesn't know it was not found vs error
        }
    }
}
# Vulnerable: Inconsistent error handling across modules
# module_a.py
def process_order(order_id):
    try:
        order = db.get_order(order_id)
    except DatabaseError as e:
        # Vulnerable: Logs sensitive query
        print(f"Database error: {e.query}")
        return None

# module_b.py
def update_inventory(product_id, quantity):
    try:
        inventory.update(product_id, quantity)
    except Exception:
        # Vulnerable: Silently swallows all errors
        pass

# module_c.py
def charge_customer(customer_id, amount):
    try:
        payment.process(customer_id, amount)
    except PaymentError as e:
        # Vulnerable: Exposes details to user
        raise ValueError(f"Payment failed for {customer_id}: {e.details}")
    except Exception as e:
        # Vulnerable: Re-raises with full stack trace
        raise

# module_d.py
def send_notification(user_id, message):
    # Vulnerable: No error handling at all
    notification_service.send(user_id, message)
// Vulnerable: Inconsistent Express.js error handling
const express = require('express');
const app = express();

// Route 1: Returns error details to client
app.get('/api/users/:id', async (req, res) => {
    try {
        const user = await db.getUser(req.params.id);
        res.json(user);
    } catch (error) {
        // Vulnerable: Full error exposed
        res.status(500).json({ error: error.message, stack: error.stack });
    }
});

// Route 2: Silent failure
app.post('/api/orders', async (req, res) => {
    try {
        await orderService.create(req.body);
        res.json({ success: true });
    } catch (error) {
        // Vulnerable: No logging, no useful response
        res.json({ success: false });
    }
});

// Route 3: Different error format
app.put('/api/settings', async (req, res) => {
    try {
        await settingsService.update(req.body);
        res.sendStatus(200);
    } catch (error) {
        // Different format than other routes
        res.status(400).send(error.toString());
    }
});

// No global error handler - unhandled errors crash the app
<?php
// Vulnerable: No standardized error handling
class VulnerableController {

    public function createUser($data) {
        // Vulnerable: Error suppression
        $result = @$this->userService->create($data);
        if (!$result) {
            // No indication of what went wrong
            return false;
        }
        return $result;
    }

    public function deleteUser($id) {
        try {
            $this->userService->delete($id);
        } catch (PDOException $e) {
            // Vulnerable: Database error details exposed
            die("Database error: " . $e->getMessage());
        }
    }

    public function updateUser($id, $data) {
        try {
            $this->userService->update($id, $data);
            return true;
        } catch (Exception $e) {
            // Vulnerable: Logs to publicly accessible file
            error_log($e->getMessage(), 3, '/var/www/html/errors.log');
            return false;
        }
    }

    public function getUser($id) {
        // Vulnerable: No error handling at all
        return $this->userService->find($id);
    }
}
?>

Fixed Code

// Fixed: Standardized error handling mechanism
public class SecureApplication {
    private static final Logger logger = LoggerFactory.getLogger(SecureApplication.class);
    private final ErrorHandler errorHandler;

    public SecureApplication(ErrorHandler errorHandler) {
        this.errorHandler = errorHandler;
    }

    public Result<Void> processPayment(String userId, double amount) {
        try {
            User user = userService.findUser(userId);
            if (user == null) {
                return Result.failure(ErrorCode.USER_NOT_FOUND, "User not found");
            }

            paymentService.charge(userId, amount);
            return Result.success();

        } catch (DatabaseException e) {
            return errorHandler.handle(e, ErrorCategory.DATABASE);
        } catch (PaymentException e) {
            return errorHandler.handle(e, ErrorCategory.PAYMENT);
        } catch (Exception e) {
            return errorHandler.handleUnexpected(e);
        }
    }

    public Result<Void> updateProfile(String userId, Map<String, String> data) {
        try {
            validateInput(data);
            profileService.update(userId, data);
            return Result.success();

        } catch (ValidationException e) {
            return errorHandler.handle(e, ErrorCategory.VALIDATION);
        } catch (Exception e) {
            return errorHandler.handleUnexpected(e);
        }
    }
}

// Fixed: Centralized error handler
public class ErrorHandler {
    private static final Logger logger = LoggerFactory.getLogger(ErrorHandler.class);

    public <T> Result<T> handle(Exception e, ErrorCategory category) {
        // Fixed: Consistent logging for all errors
        String errorId = generateErrorId();
        logger.error("Error [{}] in {}: {}", errorId, category, e.getMessage(), e);

        // Fixed: Consistent user-facing messages
        String userMessage = getUserMessage(category);
        return Result.failure(errorId, userMessage);
    }

    public <T> Result<T> handleUnexpected(Exception e) {
        String errorId = generateErrorId();
        logger.error("Unexpected error [{}]: {}", errorId, e.getMessage(), e);

        // Fixed: Never expose unexpected error details
        return Result.failure(errorId, "An unexpected error occurred. Reference: " + errorId);
    }

    private String getUserMessage(ErrorCategory category) {
        // Fixed: Standardized, non-revealing messages
        switch (category) {
            case DATABASE: return "Unable to complete the operation. Please try again.";
            case PAYMENT: return "Payment could not be processed. Please verify your payment details.";
            case VALIDATION: return "Invalid input provided. Please check your data.";
            case AUTHORIZATION: return "You do not have permission for this action.";
            default: return "An error occurred. Please try again later.";
        }
    }

    private String generateErrorId() {
        return UUID.randomUUID().toString().substring(0, 8);
    }
}

// Fixed: Result type for consistent return values
public class Result<T> {
    private final boolean success;
    private final T data;
    private final String errorId;
    private final String errorMessage;

    // Constructors, getters, factory methods...
}
# Fixed: Standardized error handling framework
import logging
import uuid
from functools import wraps
from enum import Enum

logger = logging.getLogger(__name__)

class ErrorCategory(Enum):
    DATABASE = "database"
    VALIDATION = "validation"
    AUTHORIZATION = "authorization"
    EXTERNAL_SERVICE = "external_service"
    UNEXPECTED = "unexpected"

class AppError(Exception):
    """Base application error with standardized structure."""
    def __init__(self, category: ErrorCategory, message: str, details: dict = None):
        self.error_id = str(uuid.uuid4())[:8]
        self.category = category
        self.message = message
        self.details = details or {}
        super().__init__(message)

class ErrorHandler:
    """Centralized error handler for consistent processing."""

    USER_MESSAGES = {
        ErrorCategory.DATABASE: "Unable to complete the operation. Please try again.",
        ErrorCategory.VALIDATION: "Invalid input provided. Please check your data.",
        ErrorCategory.AUTHORIZATION: "You do not have permission for this action.",
        ErrorCategory.EXTERNAL_SERVICE: "Service temporarily unavailable.",
        ErrorCategory.UNEXPECTED: "An unexpected error occurred.",
    }

    @classmethod
    def handle(cls, error: Exception, category: ErrorCategory = None) -> dict:
        """Handle an error with consistent logging and response."""
        error_id = getattr(error, 'error_id', str(uuid.uuid4())[:8])

        if category is None:
            category = getattr(error, 'category', ErrorCategory.UNEXPECTED)

        # Fixed: Consistent logging with error ID
        logger.error(
            f"Error [{error_id}] {category.value}: {str(error)}",
            exc_info=True,
            extra={'error_id': error_id, 'category': category.value}
        )

        # Fixed: Consistent user-facing response
        return {
            'success': False,
            'error_id': error_id,
            'message': cls.USER_MESSAGES.get(category, cls.USER_MESSAGES[ErrorCategory.UNEXPECTED])
        }

def handle_errors(category: ErrorCategory = ErrorCategory.UNEXPECTED):
    """Decorator for standardized error handling."""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            try:
                result = func(*args, **kwargs)
                return {'success': True, 'data': result}
            except AppError as e:
                return ErrorHandler.handle(e, e.category)
            except Exception as e:
                return ErrorHandler.handle(e, category)
        return wrapper
    return decorator

# Fixed: Usage with consistent error handling
@handle_errors(ErrorCategory.DATABASE)
def process_order(order_id):
    order = db.get_order(order_id)
    return order

@handle_errors(ErrorCategory.DATABASE)
def update_inventory(product_id, quantity):
    inventory.update(product_id, quantity)
    return True

@handle_errors(ErrorCategory.EXTERNAL_SERVICE)
def charge_customer(customer_id, amount):
    payment.process(customer_id, amount)
    return True
// Fixed: Standardized Express.js error handling
const express = require('express');
const winston = require('winston');
const { v4: uuidv4 } = require('uuid');

const app = express();

// Fixed: Centralized logger
const logger = winston.createLogger({
    level: 'error',
    format: winston.format.json(),
    transports: [new winston.transports.File({ filename: 'error.log' })]
});

// Fixed: Application error class
class AppError extends Error {
    constructor(category, message, statusCode = 500) {
        super(message);
        this.errorId = uuidv4().substring(0, 8);
        this.category = category;
        this.statusCode = statusCode;
        this.isOperational = true;
    }
}

// Fixed: Error categories
const ErrorCategory = {
    VALIDATION: 'validation',
    DATABASE: 'database',
    AUTHORIZATION: 'authorization',
    NOT_FOUND: 'not_found'
};

// Fixed: Standardized user messages
const userMessages = {
    [ErrorCategory.VALIDATION]: 'Invalid input provided.',
    [ErrorCategory.DATABASE]: 'Unable to complete the operation.',
    [ErrorCategory.AUTHORIZATION]: 'You do not have permission for this action.',
    [ErrorCategory.NOT_FOUND]: 'The requested resource was not found.'
};

// Fixed: Async error wrapper
const asyncHandler = (fn) => (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
};

// Fixed: Routes with consistent error handling
app.get('/api/users/:id', asyncHandler(async (req, res) => {
    const user = await db.getUser(req.params.id);
    if (!user) {
        throw new AppError(ErrorCategory.NOT_FOUND, 'User not found', 404);
    }
    res.json({ success: true, data: user });
}));

app.post('/api/orders', asyncHandler(async (req, res) => {
    const order = await orderService.create(req.body);
    res.json({ success: true, data: order });
}));

// Fixed: Global error handler
app.use((err, req, res, next) => {
    const errorId = err.errorId || uuidv4().substring(0, 8);
    const category = err.category || 'unexpected';

    // Fixed: Consistent logging
    logger.error({
        errorId,
        category,
        message: err.message,
        stack: err.stack,
        path: req.path,
        method: req.method
    });

    // Fixed: Consistent response format
    const statusCode = err.statusCode || 500;
    const userMessage = userMessages[category] || 'An unexpected error occurred.';

    res.status(statusCode).json({
        success: false,
        errorId,
        message: userMessage
    });
});

// Fixed: Unhandled rejection handler
process.on('unhandledRejection', (reason, promise) => {
    logger.error({ type: 'unhandledRejection', reason });
});
<?php
// Fixed: Standardized PHP error handling

// Fixed: Custom exception hierarchy
class AppException extends Exception {
    protected string $errorId;
    protected string $category;
    protected string $userMessage;

    public function __construct(string $category, string $message, string $userMessage) {
        $this->errorId = substr(uniqid(), 0, 8);
        $this->category = $category;
        $this->userMessage = $userMessage;
        parent::__construct($message);
    }

    public function getErrorId(): string { return $this->errorId; }
    public function getCategory(): string { return $this->category; }
    public function getUserMessage(): string { return $this->userMessage; }
}

class DatabaseException extends AppException {
    public function __construct(string $message) {
        parent::__construct('database', $message, 'Unable to complete the operation.');
    }
}

class ValidationException extends AppException {
    public function __construct(string $message) {
        parent::__construct('validation', $message, 'Invalid input provided.');
    }
}

// Fixed: Centralized error handler
class ErrorHandler {
    private static $logger;

    public static function init() {
        self::$logger = new Logger('/var/log/app/error.log');

        // Fixed: Global exception handler
        set_exception_handler([self::class, 'handleException']);

        // Fixed: Global error handler
        set_error_handler([self::class, 'handleError']);
    }

    public static function handleException(Throwable $e): array {
        $errorId = $e instanceof AppException ? $e->getErrorId() : substr(uniqid(), 0, 8);
        $category = $e instanceof AppException ? $e->getCategory() : 'unexpected';

        // Fixed: Consistent logging
        self::$logger->error([
            'error_id' => $errorId,
            'category' => $category,
            'message' => $e->getMessage(),
            'file' => $e->getFile(),
            'line' => $e->getLine(),
            'trace' => $e->getTraceAsString()
        ]);

        // Fixed: Consistent user response
        $userMessage = $e instanceof AppException
            ? $e->getUserMessage()
            : 'An unexpected error occurred.';

        return [
            'success' => false,
            'error_id' => $errorId,
            'message' => $userMessage
        ];
    }

    public static function handleError($severity, $message, $file, $line): bool {
        throw new ErrorException($message, 0, $severity, $file, $line);
    }
}

// Fixed: Controller with standardized error handling
class SecureController {

    public function createUser(array $data): array {
        try {
            $this->validateUserData($data);
            $user = $this->userService->create($data);
            return ['success' => true, 'data' => $user];
        } catch (AppException $e) {
            return ErrorHandler::handleException($e);
        }
    }

    public function deleteUser(int $id): array {
        try {
            $this->userService->delete($id);
            return ['success' => true];
        } catch (AppException $e) {
            return ErrorHandler::handleException($e);
        }
    }
}
?>

CVE Examples

No specific CVEs are listed in the MITRE database for this CWE. However, inconsistent error handling contributes to many vulnerabilities:

  • Information disclosure through inconsistent error messages
  • Authentication bypasses due to improperly handled errors
  • Denial of service from unhandled exceptions

References

  1. MITRE Corporation. "CWE-544: Missing Standardized Error Handling Mechanism." https://cwe.mitre.org/data/definitions/544.html
  2. OWASP. "Error Handling Cheat Sheet."
  3. CERT. "ERR00-J. Do not suppress or ignore checked exceptions."