firewalla-mcp-server
Version:
Model Context Protocol (MCP) server for Firewalla MSP API - Provides real-time network monitoring, security analysis, and firewall management through 28 specialized tools compatible with any MCP client
235 lines • 8.57 kB
JavaScript
/**
* Parameter Sanitization Utilities for Firewalla MCP Server
* Provides early sanitization to prevent null/undefined from reaching Object operations
*/
import { ErrorType, createErrorResponse } from './error-handler.js';
/**
* Default sanitization configuration
*/
const DEFAULT_SANITIZATION_CONFIG = {
allowNull: false,
allowUndefined: false,
convertNumbers: true,
trimStrings: true,
normalizeEmpty: true,
};
/**
* Parameter sanitization errors
*/
export class ParameterSanitizationError extends Error {
constructor(errors, originalArgs) {
super(`Parameter sanitization failed: ${errors.join(', ')}`);
this.name = 'ParameterSanitizationError';
this.errors = errors;
this.originalArgs = originalArgs;
}
}
/**
* Early parameter sanitizer to prevent null/undefined from reaching Object operations
*/
export class ParameterSanitizer {
/**
* Sanitize parameters early in the validation pipeline
*
* @param args - Raw arguments from MCP client
* @param config - Sanitization configuration
* @returns Sanitization result with cleaned parameters
*/
static sanitizeParameters(args, config = {}) {
const finalConfig = { ...DEFAULT_SANITIZATION_CONFIG, ...config };
const errors = [];
// First-level null/undefined check
if (args === null) {
if (!finalConfig.allowNull) {
errors.push('Parameters cannot be null');
return { isValid: false, errors };
}
return { isValid: true, sanitizedArgs: {}, errors: [] };
}
if (args === undefined) {
if (!finalConfig.allowUndefined) {
errors.push('Parameters cannot be undefined');
return { isValid: false, errors };
}
return { isValid: true, sanitizedArgs: {}, errors: [] };
}
// Type check - must be an object
if (typeof args !== 'object') {
errors.push(`Parameters must be an object, got ${typeof args}`);
return { isValid: false, errors };
}
// Array check - arrays are objects but not valid parameter containers
if (Array.isArray(args)) {
errors.push('Parameters cannot be an array');
return { isValid: false, errors };
}
// Deep sanitization of object properties
try {
const sanitizedArgs = this.sanitizeObject(args, finalConfig);
return { isValid: true, sanitizedArgs: sanitizedArgs, errors: [] };
}
catch (error) {
if (error instanceof ParameterSanitizationError) {
return { isValid: false, errors: error.errors };
}
return { isValid: false, errors: [`Sanitization failed: ${error instanceof Error ? error.message : 'Unknown error'}`] };
}
}
/**
* Safely sanitize an object without risking Object operations on null/undefined
*/
static sanitizeObject(obj, config) {
// Additional safety check
if (obj === null || obj === undefined) {
return {};
}
if (typeof obj !== 'object' || Array.isArray(obj)) {
throw new ParameterSanitizationError(['Invalid object type during sanitization'], obj);
}
const sanitized = {};
const errors = [];
// Safe iteration over object properties
try {
for (const [key, value] of Object.entries(obj)) {
try {
const sanitizedValue = this.sanitizeValue(value, config, key);
// Only include non-undefined values unless specifically allowed
if (sanitizedValue !== undefined || config.allowUndefined) {
sanitized[key] = sanitizedValue;
}
}
catch (error) {
errors.push(`Error sanitizing property '${key}': ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
}
catch (error) {
throw new ParameterSanitizationError([`Failed to iterate object properties: ${error instanceof Error ? error.message : 'Unknown error'}`], obj);
}
if (errors.length > 0) {
throw new ParameterSanitizationError(errors, obj);
}
return sanitized;
}
/**
* Sanitize individual parameter values
*/
static sanitizeValue(value, config, propertyName) {
// Handle null values
if (value === null) {
if (!config.allowNull) {
return undefined; // Convert null to undefined for consistency
}
return null;
}
// Handle undefined values
if (value === undefined) {
return undefined;
}
// Handle string values
if (typeof value === 'string') {
let sanitizedString = value;
if (config.trimStrings) {
sanitizedString = sanitizedString.trim();
}
if (config.normalizeEmpty && sanitizedString === '') {
return undefined;
}
// Attempt number conversion for numeric strings
if (config.convertNumbers && this.isNumericString(sanitizedString)) {
const numValue = Number(sanitizedString);
if (Number.isFinite(numValue)) {
return numValue;
}
}
return sanitizedString;
}
// Handle arrays (recursively sanitize elements)
if (Array.isArray(value)) {
return value.map((item, index) => this.sanitizeValue(item, config, `${propertyName}[${index}]`));
}
// Handle nested objects (recursively sanitize)
if (typeof value === 'object') {
return this.sanitizeObject(value, config);
}
// Return primitive values as-is (numbers, booleans)
return value;
}
/**
* Check if a string represents a valid number
*/
static isNumericString(str) {
if (str === '' || str.includes(' ')) {
return false;
}
return !isNaN(Number(str)) && isFinite(Number(str));
}
/**
* Create error response for sanitization failures
*/
static createSanitizationErrorResponse(toolName, sanitizationResult) {
return createErrorResponse(toolName, 'Parameter sanitization failed', ErrorType.VALIDATION_ERROR, {
sanitization_errors: sanitizationResult.errors,
details: 'Parameters failed early sanitization checks',
troubleshooting: 'Ensure all parameters are properly formatted and not null/undefined',
}, sanitizationResult.errors);
}
/**
* Safe wrapper for Object operations that might receive null/undefined
*/
static safeObjectEntries(obj) {
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
return [];
}
try {
return Object.entries(obj);
}
catch (_error) {
// If Object.entries fails, return empty array
return [];
}
}
/**
* Safe wrapper for Object.keys that might receive null/undefined
*/
static safeObjectKeys(obj) {
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
return [];
}
try {
return Object.keys(obj);
}
catch (_error) {
// If Object.keys fails, return empty array
return [];
}
}
/**
* Safe wrapper for Object.values that might receive null/undefined
*/
static safeObjectValues(obj) {
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
return [];
}
try {
return Object.values(obj);
}
catch (_error) {
// If Object.values fails, return empty array
return [];
}
}
}
/**
* Convenient function to validate and sanitize parameters in one step
*/
export function validateAndSanitizeParameters(args, toolName, config) {
const sanitizationResult = ParameterSanitizer.sanitizeParameters(args, config);
if (!sanitizationResult.isValid) {
return {
errorResponse: ParameterSanitizer.createSanitizationErrorResponse(toolName, sanitizationResult),
};
}
return { sanitizedArgs: sanitizationResult.sanitizedArgs };
}
//# sourceMappingURL=parameter-sanitizer.js.map