UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

211 lines (210 loc) 7.14 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. Object.defineProperty(exports, "__esModule", { value: true }); /** * Security utilities for input validation and sanitization */ class SecurityUtilities { // Maximum upload size: 700MB static MAX_UPLOAD_SIZE = 700 * 1024 * 1024; // Maximum number of files in a ZIP static MAX_ZIP_FILES = 50000; // Maximum decompressed size: 500MB static MAX_DECOMPRESSED_SIZE = 500 * 1024 * 1024; // Rate limiting: max attempts per IP static authAttempts = new Map(); static MAX_AUTH_ATTEMPTS = 5; static AUTH_WINDOW_MS = 15 * 60 * 1000; // 15 minutes /** * Validates that a path doesn't contain directory traversal sequences */ static validatePath(path) { if (!path) { return false; } // Normalize the path const normalized = path.replace(/\\/g, "/"); // Check for directory traversal patterns if (normalized.includes("../") || normalized.includes("/..") || normalized.startsWith("..") || normalized.includes("/../") || normalized.match(/[/\\]\.\./) || normalized.includes("\0") // null byte ) { return false; } // Check for absolute paths if (normalized.startsWith("/") || normalized.match(/^[a-zA-Z]:/) // Windows drive letter ) { return false; } return true; } /** * Validates that a path doesn't contain directory traversal sequences. * Unlike validatePath, this allows leading slashes for storage-relative paths. */ static validatePathTraversal(path) { if (!path) { return false; } // Normalize the path const normalized = path.replace(/\\/g, "/"); // Check for directory traversal patterns if (normalized.includes("../") || normalized.includes("/..") || normalized.startsWith("..") || normalized.includes("/../") || normalized.match(/[/\\]\.\./) || normalized.includes("\0") // null byte ) { return false; } return true; } /** * Sanitizes a storage path by removing dangerous characters while preserving leading slash. * For use with internal storage system that expects paths like "/images/file.png". */ static sanitizeStoragePath(path) { if (!path) { return ""; } // Remove null bytes path = path.replace(/\0/g, ""); // Normalize slashes path = path.replace(/\\/g, "/"); // Remove drive letters path = path.replace(/^[a-zA-Z]:/, ""); // Split into segments and validate each (filter out . and ..) const hasLeadingSlash = path.startsWith("/"); const segments = path.split("/").filter((segment) => { return segment && segment !== "." && segment !== ".."; }); return (hasLeadingSlash ? "/" : "") + segments.join("/"); } /** * Sanitizes a path by removing dangerous characters and sequences */ static sanitizePath(path) { if (!path) { return ""; } // Remove null bytes path = path.replace(/\0/g, ""); // Normalize slashes path = path.replace(/\\/g, "/"); // Remove leading slashes and drive letters path = path.replace(/^[a-zA-Z]:/, ""); path = path.replace(/^\/+/, ""); // Split into segments and validate each const segments = path.split("/").filter((segment) => { return segment && segment !== "." && segment !== ".."; }); return segments.join("/"); } /** * Validates that a file size is within acceptable limits */ static validateFileSize(size, maxSize = SecurityUtilities.MAX_UPLOAD_SIZE) { return size > 0 && size <= maxSize; } /** * Validates Minecraft command input to prevent injection */ static sanitizeCommand(command) { if (!command) { return ""; } // Remove control characters (including newlines, carriage returns, etc.) command = command.replace(/[\x00-\x1F\x7F]/g, ""); // Trim whitespace command = command.trim(); // Remove leading slash if present (will be added by server) if (command.startsWith("/")) { command = command.substring(1); } return command; } /** * Validates that a command is safe to execute */ static isCommandSafe(command) { if (!command) { return false; } // Check for command separators or multiple commands if (command.includes("\n") || command.includes("\r") || command.includes(";")) { return false; } // Check for excessive length if (command.length > 1000) { return false; } return true; } /** * Rate limiting for authentication attempts */ static checkAuthRateLimit(identifier) { const now = Date.now(); const attempt = SecurityUtilities.authAttempts.get(identifier); if (!attempt) { SecurityUtilities.authAttempts.set(identifier, { count: 1, resetTime: now + SecurityUtilities.AUTH_WINDOW_MS }); return true; } // Reset if window has passed if (now > attempt.resetTime) { SecurityUtilities.authAttempts.set(identifier, { count: 1, resetTime: now + SecurityUtilities.AUTH_WINDOW_MS }); return true; } // Check if under limit if (attempt.count < SecurityUtilities.MAX_AUTH_ATTEMPTS) { attempt.count++; return true; } return false; } /** * Reset rate limit for an identifier (on successful auth) */ static resetAuthRateLimit(identifier) { SecurityUtilities.authAttempts.delete(identifier); } /** * Validates JSON object doesn't contain prototype pollution */ static sanitizeJsonObject(obj) { if (obj === null || typeof obj !== "object") { return obj; } // Remove dangerous properties const dangerous = ["__proto__", "constructor", "prototype"]; for (const key of dangerous) { if (key in obj) { delete obj[key]; } } // Recursively sanitize nested objects for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { obj[key] = SecurityUtilities.sanitizeJsonObject(obj[key]); } } return obj; } /** * Validates that a string contains only safe characters for player names */ static sanitizePlayerName(name) { if (!name) { return ""; } // Only allow alphanumeric, spaces, underscores, and hyphens return name.replace(/[^a-zA-Z0-9 _-]/g, ""); } } exports.default = SecurityUtilities;