@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
261 lines (260 loc) • 11.9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BasicValidators = exports.DANGEROUS_CODE_PATTERNS = exports.ALLOWED_SHARE_PATHS = exports.MAX_SHARED_TOTAL_SIZE = exports.MAX_SHARED_FILES = void 0;
const Utilities_1 = __importDefault(require("../core/Utilities"));
const StorageUtilities_1 = __importDefault(require("./StorageUtilities"));
const obscenity_1 = require("obscenity");
/**
* Maximum number of files allowed in shared content
*/
exports.MAX_SHARED_FILES = 100;
/**
* Maximum total size of shared content in bytes (1MB)
*/
exports.MAX_SHARED_TOTAL_SIZE = 1024 * 1024;
/**
* Allowed path patterns for shareable content.
* Only files within these paths can be shared.
*/
exports.ALLOWED_SHARE_PATHS = [
/^\/scripts\//i, // Scripts folder
/^\/behavior_packs?\//i, // Behavior packs
/^\/resource_packs?\//i, // Resource packs
/^\/texts\//i, // Localization
/^\/functions\//i, // mcfunction files folder
/^\/items\//i, // Items folder
/^\/entities\//i, // Entities folder
/^\/blocks\//i, // Blocks folder
/^\/recipes\//i, // Recipes folder
/^\/loot_tables\//i, // Loot tables
/^\/trading\//i, // Trading tables
/^\/spawn_rules\//i, // Spawn rules
/^\/animations?\//i, // Animations
/^\/animation_controllers?\//i, // Animation controllers
/^\/models?\//i, // Models (geometry)
/^\/render_controllers?\//i, // Render controllers
/^\/attachables?\//i, // Attachables
/^\/biomes?\//i, // Biomes
/^\/features?\//i, // Features
/^\/feature_rules?\//i, // Feature rules
/^\/structures?\//i, // Structures
];
/**
* Dangerous code patterns that should not be allowed in shared TypeScript/JavaScript files.
* These patterns could enable code execution, file system access, or other security risks.
*/
exports.DANGEROUS_CODE_PATTERNS = [
{ pattern: /import\s+.*from\s+['"]child_process['"]/i, description: "child_process import" },
{ pattern: /require\s*\(\s*['"]child_process['"]\s*\)/i, description: "child_process require" },
{ pattern: /import\s+.*from\s+['"]fs['"]/i, description: "fs module import" },
{ pattern: /require\s*\(\s*['"]fs['"]\s*\)/i, description: "fs module require" },
{ pattern: /import\s+.*from\s+['"]path['"]/i, description: "path module import" },
{ pattern: /require\s*\(\s*['"]path['"]\s*\)/i, description: "path module require" },
{ pattern: /import\s+.*from\s+['"]os['"]/i, description: "os module import" },
{ pattern: /require\s*\(\s*['"]os['"]\s*\)/i, description: "os module require" },
{ pattern: /import\s+.*from\s+['"]net['"]/i, description: "net module import" },
{ pattern: /require\s*\(\s*['"]net['"]\s*\)/i, description: "net module require" },
{ pattern: /import\s+.*from\s+['"]dgram['"]/i, description: "dgram module import" },
{ pattern: /require\s*\(\s*['"]dgram['"]\s*\)/i, description: "dgram module require" },
{ pattern: /import\s+.*from\s+['"]cluster['"]/i, description: "cluster module import" },
{ pattern: /require\s*\(\s*['"]cluster['"]\s*\)/i, description: "cluster module require" },
{ pattern: /import\s+.*from\s+['"]vm['"]/i, description: "vm module import" },
{ pattern: /require\s*\(\s*['"]vm['"]\s*\)/i, description: "vm module require" },
{ pattern: /import\s+.*from\s+['"]worker_threads['"]/i, description: "worker_threads module import" },
{ pattern: /require\s*\(\s*['"]worker_threads['"]\s*\)/i, description: "worker_threads module require" },
{ pattern: /\beval\s*\(/i, description: "eval() call" },
{ pattern: /\bFunction\s*\(\s*['"`]/i, description: "Function constructor with string" },
{ pattern: /\bexec\s*\(/i, description: "exec() call" },
{ pattern: /\bexecSync\s*\(/i, description: "execSync() call" },
{ pattern: /\bspawn\s*\(/i, description: "spawn() call" },
{ pattern: /\bspawnSync\s*\(/i, description: "spawnSync() call" },
{ pattern: /\bfork\s*\(/i, description: "fork() call" },
{ pattern: /process\.exit/i, description: "process.exit call" },
{ pattern: /process\.env/i, description: "process.env access" },
{ pattern: /process\.cwd/i, description: "process.cwd access" },
{ pattern: /__dirname/i, description: "__dirname access" },
{ pattern: /__filename/i, description: "__filename access" },
{ pattern: /import\s*\(\s*[^)]*\+/i, description: "dynamic import with concatenation" },
{ pattern: /require\s*\(\s*[^)]*\+/i, description: "dynamic require with concatenation" },
{ pattern: /globalThis\s*\[/i, description: "globalThis bracket access" },
{ pattern: /global\s*\[/i, description: "global bracket access" },
{ pattern: /window\s*\[/i, description: "window bracket access" },
];
class BasicValidators {
static contentMatcher = undefined;
/**
* Checks if a file's content contains dangerous code patterns that could pose security risks.
* This is used to validate shared TypeScript/JavaScript files.
* @param content The file content to check
* @returns An object with isUnsafe boolean and matched patterns, or undefined if safe
*/
static hasUnsafeCodePatterns(content) {
if (!content || typeof content !== "string") {
return undefined;
}
const matches = [];
for (const { pattern, description } of exports.DANGEROUS_CODE_PATTERNS) {
if (pattern.test(content)) {
matches.push(description);
}
}
if (matches.length > 0) {
return { isUnsafe: true, matches };
}
return undefined;
}
/**
* Validates if a file path is within the allowed shareable paths.
* @param filePath The storage-relative path to validate
* @returns true if the path is allowed for sharing, false otherwise
*/
static isPathAllowedForSharing(filePath) {
if (!filePath) {
return false;
}
// Normalize path
const normalizedPath = filePath.replace(/\\/g, "/");
// Check against allowed patterns
return exports.ALLOWED_SHARE_PATHS.some((pattern) => pattern.test(normalizedPath));
}
static async isFolderSharingValid(folder, isChildFolder, stats) {
// Initialize stats on first call
if (!stats) {
stats = { fileCount: 0, totalSize: 0 };
}
if (!this.isFolderNameOKForSharing(folder.name)) {
return folder.name + " is an unsupported folder name.";
}
if (!folder.isLoaded) {
await folder.load();
}
if (!isChildFolder && folder.fileCount > 0) {
return "Folder that contains files at the root.";
}
for (const childFileName in folder.files) {
const childFile = folder.files[childFileName];
if (childFile) {
// Check file count limit
stats.fileCount++;
if (stats.fileCount > exports.MAX_SHARED_FILES) {
return `Too many files in shared content (limit: ${exports.MAX_SHARED_FILES}).`;
}
const result = this.isFileNameOKForSharing(childFile.name);
if (!result) {
return childFile.name + " is an unsupported file name.";
}
// Load content to check for strong language and unsafe patterns
if (!childFile.isContentLoaded) {
await childFile.loadContent();
}
// Check total size
if (childFile.content && typeof childFile.content === "string") {
stats.totalSize += childFile.content.length;
if (stats.totalSize > exports.MAX_SHARED_TOTAL_SIZE) {
return `Total shared content size exceeds limit (${exports.MAX_SHARED_TOTAL_SIZE} bytes).`;
}
}
const res = await this.hasStrongLanguageContent(childFile);
if (res) {
return childFile.name + " has unsupported content.";
}
// Check for unsafe code patterns in TypeScript/JavaScript files
const ext = StorageUtilities_1.default.getTypeFromName(childFile.name);
if (ext === "ts" || ext === "js") {
if (childFile.content && typeof childFile.content === "string") {
const unsafeResult = this.hasUnsafeCodePatterns(childFile.content);
if (unsafeResult && unsafeResult.isUnsafe) {
return `${childFile.name} contains potentially unsafe code patterns: ${unsafeResult.matches.join(", ")}.`;
}
}
}
}
}
for (const childFolderName in folder.folders) {
const childFolder = folder.folders[childFolderName];
if (childFolder) {
const result = await this.isFolderSharingValid(childFolder, true, stats);
if (result) {
return result;
}
}
}
return undefined;
}
static isFileNameOKForSharing(fileName) {
fileName = fileName.toLowerCase();
const ext = StorageUtilities_1.default.getTypeFromName(fileName);
if (ext !== "ts" && ext !== "json" && ext !== "lang") {
return false;
}
if (fileName.startsWith(".") ||
fileName.startsWith("just.config") ||
fileName.endsWith(".config.ts") ||
fileName.endsWith(".config.js") ||
(fileName.startsWith("manifest") && fileName.endsWith("json")) ||
(fileName.startsWith("package") && fileName.endsWith("json"))) {
return false;
}
if (!Utilities_1.default.isUsableAsObjectKey(fileName)) {
return false;
}
return true;
}
static isFolderNameOKForSharing(folderName) {
if (folderName.startsWith(".") ||
folderName === "lib" ||
folderName === "node_modules" ||
folderName === ".git" ||
folderName === "dist" ||
folderName === "build") {
return false;
}
return true;
}
static async hasStrongLanguageContent(file) {
if (!file.isContentLoaded) {
await file.loadContent();
}
if (file.isBinary) {
return undefined;
}
const str = file.content;
if (!str) {
return undefined;
}
if (typeof str !== "string") {
return undefined;
}
if (str.length < 1) {
return undefined;
}
const content = str.toLowerCase();
if (this.contentMatcher === undefined) {
this.contentMatcher = new obscenity_1.RegExpMatcher({
...obscenity_1.englishDataset.build(),
...obscenity_1.englishRecommendedTransformers,
});
}
if (this.contentMatcher.hasMatch(content)) {
const matches = this.contentMatcher.getAllMatches(content);
let strMatches = [];
const strMatchesSet = new Set();
for (let i = 0; i < matches.length && i < 100; i++) {
const match = matches[i];
if (match) {
const result = content.substring(match.startIndex, match.endIndex + 1);
if (!strMatchesSet.has(result)) {
strMatches.push(result);
strMatchesSet.add(result);
}
}
}
return strMatches.join(", ");
}
return undefined;
}
}
exports.BasicValidators = BasicValidators;