agregarr_agregarr/server/utils/errorResponse.ts
bitr8 a4e6ebaecd
fix(api): Sanitize error responses to prevent information disclosure (#282)
* fix(api): Sanitize error responses to prevent information disclosure

Add error response sanitization to prevent internal implementation details
from leaking to clients in production:

- New errorResponse.ts utility with whitelist-based message filtering
- Safe message patterns allow user-friendly errors through
- Sensitive patterns block stack traces, file paths, credentials, IPs
- Development mode bypasses sanitization for debugging
- Updated global error handler to use sanitization

This prevents information disclosure while maintaining helpful error
messages for common user-facing issues like "not found" or "invalid".

* style: Fix prettier formatting

* ci: trigger rebuild

* fix(types): Add missing type parameters for Record and Set

- Add <string, string> to Record type declarations for mimeTypeMap
- Add <string> to Set constructor for assetPaths

Fixes TypeScript errors in CI.

* style: format with prettier

---------

Co-authored-by: bitr8 <bitr8@users.noreply.github.com>
2026-01-11 22:24:06 +13:00

87 lines
2.3 KiB
TypeScript

/**
* Utility for creating safe API error responses
* Prevents leaking internal implementation details in production
*/
import logger from '@server/logger';
const isDev = process.env.NODE_ENV !== 'production';
/**
* Patterns that indicate SAFE, user-friendly error messages
*/
const SAFE_MESSAGE_PATTERNS = [
/^(not found|invalid|missing|required|failed to|unable to|cannot|unauthorized|forbidden)/i,
/^(no .+ found|.+ is required|.+ not configured)/i,
/^(connection|network|timeout)/i,
];
/**
* Patterns that indicate internal/sensitive error details
*/
const SENSITIVE_PATTERNS = [
/at\s+\S+\s+\([^)]+\)/i, // Stack trace lines
/\/home\/|\/root\/|\/var\/|\/usr\/|\/mnt\/|C:\\|D:\\/i, // File paths
/ENOENT|EACCES|EPERM|ECONNREFUSED|ETIMEDOUT|ENOTFOUND/i, // System errors
/password|secret|token|apikey|api_key|authorization/i, // Credentials
/node_modules|\.ts:\d+|\.js:\d+/i, // Internal paths/source locations
/sql|query|database|table|column|constraint/i, // Database internals
/localhost|127\.0\.0\.1|192\.168\.|10\.\d+\.|172\.(1[6-9]|2\d|3[01])\./i, // Internal IPs
];
/**
* Check if an error message is safe to show to users
*/
function isSafeMessage(message: string): boolean {
if (SENSITIVE_PATTERNS.some((pattern) => pattern.test(message))) {
return false;
}
return SAFE_MESSAGE_PATTERNS.some((pattern) => pattern.test(message));
}
/**
* Sanitize an error message for client response
* In production, only shows messages that are explicitly safe
*/
export function sanitizeErrorMessage(
error: unknown,
fallbackMessage = 'An unexpected error occurred'
): string {
const message = error instanceof Error ? error.message : String(error);
if (isDev) {
return message;
}
if (isSafeMessage(message)) {
return message;
}
return fallbackMessage;
}
/**
* Create a standardized error response object
*/
export function createErrorResponse(
error: unknown,
label: string,
userMessage: string
): { error: string; message: string } {
const fullMessage = error instanceof Error ? error.message : String(error);
const stack = error instanceof Error ? error.stack : undefined;
logger.error(`${userMessage}: ${fullMessage}`, {
label,
error: fullMessage,
stack,
});
const safeMessage = sanitizeErrorMessage(error, userMessage);
return {
error: userMessage,
message: safeMessage,
};
}