UNPKG

dtamind-components

Version:

DTAmindai Components

302 lines 11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SecureZodSchemaParser = void 0; const zod_1 = require("zod"); /** * This parser safely handles Zod schema strings without allowing arbitrary code execution */ class SecureZodSchemaParser { /** * Safely parse a Zod schema string into a Zod schema object * @param schemaString The Zod schema as a string (e.g., "z.object({name: z.string()})") * @returns A Zod schema object * @throws Error if the schema is invalid or contains unsafe patterns */ static parseZodSchema(schemaString) { try { // Remove comments and normalize whitespace const cleanedSchema = this.cleanSchemaString(schemaString); // Parse the schema structure const parsed = this.parseSchemaStructure(cleanedSchema); // Build the Zod schema securely return this.buildZodSchema(parsed); } catch (error) { throw new Error(`Failed to parse Zod schema: ${error.message}`); } } static cleanSchemaString(schema) { // Remove single-line comments schema = schema.replace(/\/\/.*$/gm, ''); // Remove multi-line comments schema = schema.replace(/\/\*[\s\S]*?\*\//g, ''); // Normalize whitespace schema = schema.replace(/\s+/g, ' ').trim(); return schema; } static parseSchemaStructure(schema) { // This is a simplified parser that handles common Zod patterns safely // It does NOT use eval/Function and only handles predefined safe patterns if (!schema.startsWith('z.object(')) { throw new Error('Schema must start with z.object()'); } // Extract the object content const objectMatch = schema.match(/z\.object\(\s*\{([\s\S]*)\}\s*\)/); if (!objectMatch) { throw new Error('Invalid z.object() syntax'); } const objectContent = objectMatch[1]; return this.parseObjectProperties(objectContent); } static parseObjectProperties(content) { const properties = {}; // Split by comma, but handle nested structures const props = this.splitProperties(content); for (const prop of props) { const [key, value] = this.parseProperty(prop); if (key && value) { properties[key] = value; } } return properties; } static splitProperties(content) { const properties = []; let current = ''; let depth = 0; let inString = false; let stringChar = ''; for (let i = 0; i < content.length; i++) { const char = content[i]; if (!inString && (char === '"' || char === "'")) { inString = true; stringChar = char; } else if (inString && char === stringChar && content[i - 1] !== '\\') { inString = false; } else if (!inString) { if (char === '(' || char === '[' || char === '{') { depth++; } else if (char === ')' || char === ']' || char === '}') { depth--; } else if (char === ',' && depth === 0) { properties.push(current.trim()); current = ''; continue; } } current += char; } if (current.trim()) { properties.push(current.trim()); } return properties; } static parseProperty(prop) { const colonIndex = prop.indexOf(':'); if (colonIndex === -1) return [null, null]; const key = prop.substring(0, colonIndex).trim().replace(/['"]/g, ''); const value = prop.substring(colonIndex + 1).trim(); return [key, this.parseZodType(value)]; } static parseZodType(typeStr) { const type = { base: '', modifiers: [] }; // Handle chained methods like z.string().max(500).optional() const parts = typeStr.split('.'); for (let i = 0; i < parts.length; i++) { const part = parts[i].trim(); if (i === 0) { // First part should be 'z' if (part !== 'z') { throw new Error(`Expected 'z' but got '${part}'`); } continue; } if (i === 1) { // Second part is the base type const baseMatch = part.match(/^(\w+)(\(.*\))?$/); if (!baseMatch) { throw new Error(`Invalid base type: ${part}`); } type.base = baseMatch[1]; if (baseMatch[2]) { // Parse arguments for base type (e.g., enum values) const args = this.parseArguments(baseMatch[2]); type.baseArgs = args; } } else { // Subsequent parts are modifiers const modMatch = part.match(/^(\w+)(\(.*\))?$/); if (!modMatch) { throw new Error(`Invalid modifier: ${part}`); } const modName = modMatch[1]; const modArgs = modMatch[2] ? this.parseArguments(modMatch[2]) : []; type.modifiers.push({ name: modName, args: modArgs }); } } return type; } static parseArguments(argsStr) { // Remove outer parentheses const inner = argsStr.slice(1, -1).trim(); if (!inner) return []; // Simple argument parsing for basic cases if (inner.startsWith('[') && inner.endsWith(']')) { // Array argument const arrayContent = inner.slice(1, -1); return [this.parseArrayContent(arrayContent)]; } else if (inner.match(/^\d+$/)) { // Number argument return [parseInt(inner, 10)]; } else if (inner.startsWith('"') && inner.endsWith('"')) { // String argument return [inner.slice(1, -1)]; } else { // Try to parse as comma-separated values return inner.split(',').map((arg) => { arg = arg.trim(); if (arg.match(/^\d+$/)) return parseInt(arg, 10); if (arg.startsWith('"') && arg.endsWith('"')) return arg.slice(1, -1); return arg; }); } } static parseArrayContent(content) { const items = []; let current = ''; let inString = false; let stringChar = ''; for (let i = 0; i < content.length; i++) { const char = content[i]; if (!inString && (char === '"' || char === "'")) { inString = true; stringChar = char; current += char; } else if (inString && char === stringChar && content[i - 1] !== '\\') { inString = false; current += char; } else if (!inString && char === ',') { items.push(current.trim().replace(/^["']|["']$/g, '')); current = ''; } else { current += char; } } if (current.trim()) { items.push(current.trim().replace(/^["']|["']$/g, '')); } return items; } static buildZodSchema(parsed) { const schemaObj = {}; for (const [key, typeInfo] of Object.entries(parsed)) { schemaObj[key] = this.buildZodType(typeInfo); } return zod_1.z.object(schemaObj); } static buildZodType(typeInfo) { let zodType; // Build base type switch (typeInfo.base) { case 'string': zodType = zod_1.z.string(); break; case 'number': zodType = zod_1.z.number(); break; case 'boolean': zodType = zod_1.z.boolean(); break; case 'date': zodType = zod_1.z.date(); break; case 'enum': if (typeInfo.baseArgs && typeInfo.baseArgs[0] && Array.isArray(typeInfo.baseArgs[0])) { const enumValues = typeInfo.baseArgs[0]; zodType = zod_1.z.enum(enumValues); } else { throw new Error('enum requires array of values'); } break; default: throw new Error(`Unsupported base type: ${typeInfo.base}`); } // Apply modifiers for (const modifier of typeInfo.modifiers || []) { switch (modifier.name) { case 'int': if (zodType._def?.typeName === 'ZodNumber') { zodType = zodType.int(); } break; case 'max': if (modifier.args[0] !== undefined) { if (zodType._def?.typeName === 'ZodString') { zodType = zodType.max(modifier.args[0]); } else if (zodType._def?.typeName === 'ZodArray') { zodType = zodType.max(modifier.args[0]); } } break; case 'min': if (modifier.args[0] !== undefined) { if (zodType._def?.typeName === 'ZodString') { zodType = zodType.min(modifier.args[0]); } else if (zodType._def?.typeName === 'ZodArray') { zodType = zodType.min(modifier.args[0]); } } break; case 'optional': zodType = zodType.optional(); break; case 'array': zodType = zod_1.z.array(zodType); break; case 'describe': if (modifier.args[0]) { zodType = zodType.describe(modifier.args[0]); } break; default: // Ignore unknown modifiers for compatibility break; } } return zodType; } } exports.SecureZodSchemaParser = SecureZodSchemaParser; SecureZodSchemaParser.ALLOWED_TYPES = [ 'string', 'number', 'int', 'boolean', 'date', 'object', 'array', 'enum', 'optional', 'max', 'min', 'describe' ]; //# sourceMappingURL=secureZodParser.js.map