sfcc-dev-mcp
Version:
MCP server for Salesforce B2C Commerce Cloud development assistance including logs, debugging, and development tools
143 lines • 5.67 kB
JavaScript
/**
* Configuration loader for SFCC MCP Server
*
* This module handles secure loading and validation of dw.json files
* with comprehensive security checks for file access.
*/
import { readFileSync, existsSync, statSync } from 'fs';
import { resolve, basename, extname } from 'path';
/**
* Validates that a file path is safe to access and prevents path traversal attacks
*
* @param filePath - The file path to validate
* @returns The validated and resolved path
* @throws Error if the path is unsafe
*/
function validateSecurePath(filePath) {
// Prevent null bytes and other dangerous characters
if (filePath.includes('\0') || filePath.includes('\x00')) {
throw new Error('Invalid characters in file path');
}
// Prevent excessively long paths that could cause DoS
if (filePath.length > 1000) {
throw new Error('File path too long');
}
// Check if file extension is allowed (only .json files)
const ext = extname(filePath).toLowerCase();
if (ext !== '.json') {
throw new Error('Only .json files are allowed');
}
// Normalize the path to prevent path traversal
const resolvedPath = resolve(filePath);
// Additional check: ensure the resolved path still ends with .json
// This prevents attacks like "file.json/../../../etc/passwd"
if (!resolvedPath.toLowerCase().endsWith('.json')) {
throw new Error('Invalid file path');
}
// Prevent access to system directories (additional security layer)
// Allow CI workspace paths like /home/runner/work/ but block sensitive system paths
const dangerousPaths = ['/etc/', '/proc/', '/sys/', '/dev/', '/root/'];
const lowerPath = resolvedPath.toLowerCase();
// Check for dangerous system paths
if (dangerousPaths.some(dangerous => lowerPath.includes(dangerous))) {
throw new Error('Access to system directories not allowed');
}
// Block /home/ but allow CI workspace paths (/home/runner/work/)
if (lowerPath.includes('/home/') && !lowerPath.includes('/home/runner/work/')) {
throw new Error('Access to system directories not allowed');
}
return resolvedPath;
}
/**
* Validates file size to prevent reading excessively large files
*
* @param filePath - The file path to check
* @throws Error if file is too large
*/
function validateFileSize(filePath) {
try {
const stats = statSync(filePath);
const maxSize = 1024 * 1024; // 1MB limit for config files
if (stats.size > maxSize) {
throw new Error('Configuration file too large');
}
}
catch (error) {
if (error instanceof Error && error.message === 'Configuration file too large') {
throw error;
}
// Don't expose detailed file system errors
throw new Error('Unable to access configuration file');
}
}
/**
* Validates the content of a dw.json configuration
*
* @param dwConfig - The parsed dw.json configuration
* @throws Error if required fields are missing or invalid
*/
function validateDwJsonContent(dwConfig) {
// Validate required fields
if (!dwConfig.hostname || !dwConfig.username || !dwConfig.password) {
throw new Error('Configuration file must contain hostname, username, and password fields');
}
// Additional validation for hostname format (trim whitespace first)
const trimmedHostname = dwConfig.hostname.trim();
if (!trimmedHostname?.match(/^[a-zA-Z0-9.-]+(?::[0-9]+)?$/)) {
throw new Error('Invalid hostname format in configuration');
}
}
/**
* Securely loads and parses a dw.json file with comprehensive validation
*
* This function handles all security aspects of file loading including path validation,
* file size checks, and content validation. It returns the raw parsed JSON without
* any transformation to SFCCConfig format.
*
* @param dwJsonPath - Path to the dw.json file
* @returns Parsed and validated dw.json configuration
* @throws Error if file doesn't exist, is invalid, or fails security checks
*/
export function loadSecureDwJson(dwJsonPath) {
let resolvedPath;
try {
// Validate and secure the path
resolvedPath = validateSecurePath(dwJsonPath);
}
catch (error) {
throw new Error(`Invalid file path: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
if (!existsSync(resolvedPath)) {
// Don't expose the full path in error messages for security
throw new Error(`Configuration file not found: ${basename(dwJsonPath)}`);
}
// Validate file size before reading
try {
validateFileSize(resolvedPath);
}
catch (error) {
throw new Error(`File validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
try {
const dwJsonContent = readFileSync(resolvedPath, 'utf-8');
// Additional validation: ensure the content is not empty and doesn't contain suspicious patterns
if (!dwJsonContent.trim()) {
throw new Error('Configuration file is empty');
}
// Basic check for potential binary content (null bytes)
if (dwJsonContent.includes('\0')) {
throw new Error('Configuration file contains invalid content');
}
const dwConfig = JSON.parse(dwJsonContent);
// Validate the parsed configuration
validateDwJsonContent(dwConfig);
return dwConfig;
}
catch (error) {
if (error instanceof SyntaxError) {
throw new Error(`Invalid JSON in configuration file: ${error.message}`);
}
throw error;
}
}
//# sourceMappingURL=dw-json-loader.js.map