UNPKG

mlld

Version:

mlld: a modular prompt scripting language

1,502 lines (1,495 loc) 323 kB
import { ErrorFormatSelector } from './chunk-JO67PGXR.mjs'; export { ErrorFormatSelector } from './chunk-JO67PGXR.mjs'; import { ImmutableCache } from './chunk-RO3YC7AJ.mjs'; import { createMlldHelpers, prepareParamsForShadow } from './chunk-3BKXIDNI.mjs'; import { formatOutput, formatMarkdown } from './chunk-S3VE3NJA.mjs'; import { HashUtils, resolveShadowEnvironment, DebugUtils, evaluate } from './chunk-FYMHT7UG.mjs'; import { isVariable, resolveValue, ResolutionContext } from './chunk-7ASO6AZA.mjs'; import { PathMatcher, LocalResolver } from './chunk-I65NRWMI.mjs'; import { GitHubResolver } from './chunk-X7Y3Q2Z4.mjs'; import { HTTPResolver } from './chunk-7T5PMNS2.mjs'; import { ProjectPathResolver, findProjectRoot } from './chunk-P46TIXQR.mjs'; import { llmxmlInstance } from './chunk-GPFWQ7PB.mjs'; import { jsonToXml } from './chunk-4IHDDJBM.mjs'; import { parse as parse$1 } from './chunk-VI5FKUWW.mjs'; import { RegistryResolver } from './chunk-2U4LJYI7.mjs'; import { TaintTracker } from './chunk-KYJC7SAY.mjs'; import { logger } from './chunk-XGMRAGIT.mjs'; import { MlldImportError, MlldFileSystemError, MlldError, MlldResolutionError, MlldCommandExecutionError, VariableRedefinitionError, MlldParseError } from './chunk-YMCO2JI3.mjs'; export { MlldError } from './chunk-YMCO2JI3.mjs'; import { isTextLike, isPipelineInput, VariableTypeGuards } from './chunk-V5L6FBQT.mjs'; import { createSimpleTextVariable, createObjectVariable, createPathVariable } from './chunk-TU56GBG3.mjs'; import { __name, __publicField, __require } from './chunk-OMKLS24H.mjs'; import * as path8 from 'path'; import { isAbsolute, resolve } from 'path'; import { parse } from 'shell-quote'; import { exec, execSync, spawnSync } from 'child_process'; import * as crypto from 'crypto'; import { createHash } from 'crypto'; import * as readline from 'readline/promises'; import * as fs7 from 'fs'; import * as os2 from 'os'; import * as fs3 from 'fs/promises'; import { URL as URL$1 } from 'url'; import minimatch from 'minimatch'; import { promisify } from 'util'; import * as vm from 'vm'; import Module from 'module'; import * as acorn from 'acorn'; // security/policy/patterns.ts var IMMUTABLE_SECURITY_PATTERNS = Object.freeze({ // Paths that can NEVER be read protectedReadPaths: Object.freeze([ "~/.ssh/**", "~/.aws/**", "~/.gnupg/**", "~/.docker/config.json", "~/.kube/config", "~/.npmrc", "~/.netrc", "~/.git-credentials", "~/.env*", "**/.env*", "**/secrets/**", "**/private/**", "/etc/shadow", "/etc/sudoers", "C:\\Windows\\System32\\config\\**" // Windows SAM ]), // Paths that can NEVER be written protectedWritePaths: Object.freeze([ "~/.mlld/**", "mlld.lock.json", "**/mlld.lock.json", "/etc/**", "/usr/**", "/bin/**", "/sbin/**", "/System/**", "/Library/**", "C:\\Windows\\**", "C:\\Program Files\\**", "/proc/**", "/sys/**", "/dev/**" // Device files ]), // Commands that are ALWAYS blocked blockedCommands: Object.freeze([ "rm -rf /", "rm -rf /*", ":(){ :|:& };:", "dd if=/dev/zero of=/dev/sda", "mkfs", "> /dev/sda", "format c:" ]), // Shell injection patterns (OWASP) injectionPatterns: Object.freeze([ /;/, /&&/, /\|\|/, /\|/, /\$\(/, /`/, />/, />>/, /</, /\n|\r/, /\${.*}/ ]), // Patterns indicating data exfiltration exfiltrationPatterns: Object.freeze([ /curl.*\.ssh/i, /wget.*\.aws/i, /nc.*\.env/i, /base64.*\.pem/i, /cat.*\|.*curl/i, /cat.*\|.*nc/i ]), // LLM command patterns to detect llmCommandPatterns: Object.freeze([ /^(claude|anthropic|ai)/i, /^(gpt|openai|chatgpt)/i, /^(llm|ai-|ml-)/i, /^(bard|gemini|palm)/i, /^(mistral|llama|alpaca)/i ]) }); // security/command/analyzer/CommandAnalyzer.ts var _CommandAnalyzer = class _CommandAnalyzer { /** * Analyze a command for security risks */ async analyze(command, taint) { const risks = []; let parsed; try { parsed = parse(command); } catch (error) { risks.push({ type: "INJECTION", severity: "BLOCKED", description: "Failed to parse command - possible malformed input" }); return this.createAnalysis(command, "", [], risks); } const baseCommand = String(parsed[0] || ""); const args = parsed.slice(1).map(String); const injectionRisks = this.checkInjectionPatterns(command); risks.push(...injectionRisks); const commandRisks = this.checkDangerousCommands(baseCommand, args); risks.push(...commandRisks); const exfiltrationRisks = this.checkExfiltration(command); risks.push(...exfiltrationRisks); if (taint === "llm_output" || taint === "network") { risks.push({ type: "INJECTION", severity: "CRITICAL", description: `Attempting to execute ${taint} as command - requires explicit approval` }); try { const astAnalysis = await runASTAnalysis(command); if (astAnalysis.warnings?.length > 0) { risks.push({ type: "INJECTION", severity: "HIGH", description: "Suspicious patterns detected in command" }); } } catch (error) { risks.push({ type: "INJECTION", severity: "HIGH", description: "Command appears obfuscated or malformed" }); } } return this.createAnalysis(command, baseCommand, args, risks); } checkInjectionPatterns(command) { const risks = []; const patterns2 = [ { regex: /;/, desc: "Command separator (;)" }, { regex: /&&/, desc: "Command chaining (&&)" }, { regex: /\|\|/, desc: "Conditional execution (||)" }, { regex: /\|/, desc: "Pipe operator (|)" }, { regex: /\$\(/, desc: "Command substitution $()" }, { regex: /`/, desc: "Backtick substitution" }, { regex: />/, desc: "Output redirection (>)" }, { regex: />>/, desc: "Append redirection (>>)" }, { regex: /</, desc: "Input redirection (<)" }, { regex: /\n|\r/, desc: "Newline injection" } ]; for (const { regex, desc } of patterns2) { if (regex.test(command)) { risks.push({ type: "INJECTION", severity: "HIGH", pattern: regex.source, description: `Shell injection pattern detected: ${desc}` }); } } return risks; } checkDangerousCommands(baseCommand, args) { const risks = []; if (IMMUTABLE_SECURITY_PATTERNS.blockedCommands.includes(baseCommand)) { risks.push({ type: "DANGEROUS_COMMAND", severity: "BLOCKED", description: `Command is absolutely forbidden: ${baseCommand}` }); return risks; } const dangerousCommands = { critical: [ "rm", "dd", "format", "fdisk", "mkfs" ], high: [ "curl", "wget", "nc", "netcat", "ssh", "scp" ], medium: [ "chmod", "chown", "kill", "sudo", "su" ] }; for (const [severity, commands] of Object.entries(dangerousCommands)) { if (commands.includes(baseCommand)) { if (baseCommand === "rm" && args.includes("-rf")) { const target = args[args.indexOf("-rf") + 1] || args[args.indexOf("-rf") - 1]; if (target === "/" || target === "/*") { risks.push({ type: "DANGEROUS_COMMAND", severity: "BLOCKED", description: "Attempting to delete root filesystem!" }); continue; } } risks.push({ type: "DANGEROUS_COMMAND", severity: severity.toUpperCase(), description: `${baseCommand} is a potentially dangerous command` }); } } return risks; } checkExfiltration(command) { const risks = []; const sensitivePatterns = [ { pattern: /\.ssh/, desc: "SSH keys" }, { pattern: /\.aws/, desc: "AWS credentials" }, { pattern: /\.env/, desc: "Environment files" }, { pattern: /private[_-]?key/i, desc: "Private keys" }, { pattern: /password|passwd|pwd/i, desc: "Password files" }, { pattern: /secret|token/i, desc: "Secrets or tokens" }, { pattern: /\.pem|\.key|\.crt/i, desc: "Certificate files" }, { pattern: /\/etc\/shadow/, desc: "System password file" } ]; for (const { pattern, desc } of sensitivePatterns) { if (pattern.test(command)) { if (/curl|wget|nc|netcat|ssh|scp/.test(command)) { risks.push({ type: "EXFILTRATION", severity: "CRITICAL", description: `Possible exfiltration of ${desc}` }); } else { risks.push({ type: "EXFILTRATION", severity: "HIGH", description: `Accessing sensitive data: ${desc}` }); } } } return risks; } createAnalysis(command, baseCommand, args, risks) { const blocked = risks.some((r) => r.severity === "BLOCKED"); const critical = risks.some((r) => r.severity === "CRITICAL"); const high = risks.some((r) => r.severity === "HIGH"); return { command, baseCommand, args, risks, suspicious: risks.length > 0, blocked, requiresApproval: !blocked && (critical || high) }; } }; __name(_CommandAnalyzer, "CommandAnalyzer"); var CommandAnalyzer = _CommandAnalyzer; // core/config/utils.ts function parseDuration(duration) { if (typeof duration === "number") { return duration; } const match = duration.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/i); if (!match) { throw new Error(`Invalid duration format: ${duration}`); } const value = parseFloat(match[1]); const unit = match[2]?.toLowerCase() || "ms"; const multipliers = { "ms": 1, "s": 1e3, "m": 60 * 1e3, "h": 60 * 60 * 1e3, "d": 24 * 60 * 60 * 1e3 }; if (!(unit in multipliers)) { throw new Error(`Unknown duration unit: ${unit}`); } return Math.floor(value * multipliers[unit]); } __name(parseDuration, "parseDuration"); function parseSize(size) { if (typeof size === "number") { return size; } const match = size.match(/^(\d+(?:\.\d+)?)\s*([KMGT]?B?)$/i); if (!match) { throw new Error(`Invalid size format: ${size}`); } const value = parseFloat(match[1]); const unit = match[2].toUpperCase() || "B"; const multipliers = { "B": 1, "KB": 1024, "MB": 1024 * 1024, "GB": 1024 * 1024 * 1024, "TB": 1024 * 1024 * 1024 * 1024 }; if (!(unit in multipliers)) { throw new Error(`Unknown size unit: ${unit}`); } return Math.floor(value * multipliers[unit]); } __name(parseSize, "parseSize"); // core/config/loader.ts function isObject(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } __name(isObject, "isObject"); function isMlldConfig(value) { if (!isObject(value)) return false; if (value.security !== void 0 && !isObject(value.security)) return false; if (value.cache !== void 0 && !isObject(value.cache)) return false; if (value.output !== void 0 && !isObject(value.output)) return false; return true; } __name(isMlldConfig, "isMlldConfig"); function parseConfig(content) { const parsed = JSON.parse(content); if (!isMlldConfig(parsed)) { throw new Error("Invalid configuration format"); } return parsed; } __name(parseConfig, "parseConfig"); var _ConfigLoader = class _ConfigLoader { constructor(projectPathOrContext) { __publicField(this, "globalConfigPath"); __publicField(this, "projectConfigPath"); __publicField(this, "cachedConfig"); __publicField(this, "pathContext"); this.globalConfigPath = path8.join(os2.homedir(), ".config", "mlld", "mlld.lock.json"); if (typeof projectPathOrContext === "string") { this.projectConfigPath = path8.join(projectPathOrContext, "mlld.config.json"); } else if (projectPathOrContext) { this.pathContext = projectPathOrContext; this.projectConfigPath = path8.join(projectPathOrContext.projectRoot, "mlld.config.json"); } else { this.projectConfigPath = path8.join(process.cwd(), "mlld.config.json"); } } /** * Load and merge configurations */ load() { if (this.cachedConfig) { return this.cachedConfig; } const globalConfig = this.loadConfigFile(this.globalConfigPath); const projectConfig = this.loadConfigFile(this.projectConfigPath); this.cachedConfig = this.mergeConfigs(globalConfig, projectConfig); return this.cachedConfig; } /** * Load a single config file */ loadConfigFile(filePath) { try { if (fs7.existsSync(filePath)) { const content = fs7.readFileSync(filePath, "utf8"); return parseConfig(content); } } catch (error) { if (error instanceof Error) { console.warn(`Failed to load config from ${filePath}:`, error.message); } else { console.warn(`Failed to load config from ${filePath}:`, String(error)); } } return {}; } /** * Deep merge two config objects */ mergeConfigs(global2, project) { const merged = {}; if (global2.security || project.security) { merged.security = { urls: this.mergeURLSecurity(global2.security?.urls, project.security?.urls) }; } if (global2.cache || project.cache) { merged.cache = { urls: this.mergeURLCache(global2.cache?.urls, project.cache?.urls) }; } if (global2.output || project.output) { merged.output = this.mergeOutputConfig(global2.output, project.output); } return merged; } mergeURLSecurity(global2, project) { if (!global2 && !project) return void 0; const merged = { enabled: project?.enabled ?? global2?.enabled ?? false, // Handle various array fields allow: this.mergeArrays(global2?.allow, project?.allow), allowedDomains: this.mergeArrays(global2?.allowedDomains, project?.allowedDomains), blockedDomains: this.mergeArrays(global2?.blockedDomains, project?.blockedDomains), allowedProtocols: project?.allowedProtocols ?? global2?.allowedProtocols, // Handle optional properties ...project?.maxSize !== void 0 || global2?.maxSize !== void 0 ? { maxSize: project?.maxSize ?? global2?.maxSize } : {}, ...project?.timeout !== void 0 || global2?.timeout !== void 0 ? { timeout: project?.timeout ?? global2?.timeout } : {}, ...project?.warnOnInsecureProtocol !== void 0 || global2?.warnOnInsecureProtocol !== void 0 ? { warnOnInsecureProtocol: project?.warnOnInsecureProtocol ?? global2?.warnOnInsecureProtocol } : {}, ...project?.requireReviewOnUpdate !== void 0 || global2?.requireReviewOnUpdate !== void 0 ? { requireReviewOnUpdate: project?.requireReviewOnUpdate ?? global2?.requireReviewOnUpdate } : {}, ...project?.gists !== void 0 || global2?.gists !== void 0 ? { gists: this.mergeGistSecurity(global2?.gists, project?.gists) } : {} }; return merged; } mergeArrays(global2, project) { if (!global2 && !project) return void 0; return [ ...global2 || [], ...project || [] ]; } mergeGistSecurity(global2, project) { if (!global2 && !project) return void 0; return { enabled: project?.enabled ?? global2?.enabled ?? false, ...this.mergeArrays(global2?.allowedUsers, project?.allowedUsers) ? { allowedUsers: this.mergeArrays(global2?.allowedUsers, project?.allowedUsers) } : {}, ...this.mergeArrays(global2?.allowedGists, project?.allowedGists) ? { allowedGists: this.mergeArrays(global2?.allowedGists, project?.allowedGists) } : {}, ...project?.pinToVersion !== void 0 || global2?.pinToVersion !== void 0 ? { pinToVersion: project?.pinToVersion ?? global2?.pinToVersion } : {}, ...project?.transformUrls !== void 0 || global2?.transformUrls !== void 0 ? { transformUrls: project?.transformUrls ?? global2?.transformUrls } : {} }; } mergeURLCache(global2, project) { if (!global2 && !project) return void 0; const merged = { enabled: project?.enabled ?? global2?.enabled ?? false, ...project?.defaultTTL !== void 0 || global2?.defaultTTL !== void 0 ? { defaultTTL: project?.defaultTTL ?? global2?.defaultTTL } : {}, ...project?.rules !== void 0 || global2?.rules !== void 0 ? { rules: this.mergeCacheRules(global2?.rules, project?.rules) } : {}, ...project?.immutable !== void 0 || global2?.immutable !== void 0 ? { immutable: project?.immutable ?? global2?.immutable } : {}, ...project?.autoRefresh !== void 0 || global2?.autoRefresh !== void 0 ? { autoRefresh: this.mergeAutoRefresh(global2?.autoRefresh, project?.autoRefresh) } : {}, ...project?.storageLocation !== void 0 || global2?.storageLocation !== void 0 ? { storageLocation: project?.storageLocation ?? global2?.storageLocation } : {} }; return merged; } mergeAutoRefresh(global2, project) { if (!global2 && !project) return void 0; return { enabled: project?.enabled ?? global2?.enabled ?? false, ...project?.defaultTTL !== void 0 || global2?.defaultTTL !== void 0 ? { defaultTTL: project?.defaultTTL ?? global2?.defaultTTL } : {}, ...project?.rules !== void 0 || global2?.rules !== void 0 ? { rules: this.mergeCacheRules(global2?.rules, project?.rules) } : {}, ...project?.requireReview !== void 0 || global2?.requireReview !== void 0 ? { requireReview: project?.requireReview ?? global2?.requireReview } : {} }; } mergeCacheRules(global2, project) { if (!global2 && !project) return void 0; const globalRules = global2 || []; const projectRules = project || []; if (projectRules.length === 0) return globalRules; const projectPatterns = new Set(projectRules.map((r) => r.pattern)); const filteredGlobal = globalRules.filter((r) => !projectPatterns.has(r.pattern)); return [ ...projectRules, ...filteredGlobal ]; } mergeOutputConfig(global2, project) { if (!global2 && !project) return void 0; const merged = { ...project?.showProgress !== void 0 || global2?.showProgress !== void 0 ? { showProgress: project?.showProgress ?? global2?.showProgress } : {}, ...project?.maxOutputLines !== void 0 || global2?.maxOutputLines !== void 0 ? { maxOutputLines: project?.maxOutputLines ?? global2?.maxOutputLines } : {}, ...project?.errorBehavior !== void 0 || global2?.errorBehavior !== void 0 ? { errorBehavior: project?.errorBehavior ?? global2?.errorBehavior } : {}, ...project?.collectErrors !== void 0 || global2?.collectErrors !== void 0 ? { collectErrors: project?.collectErrors ?? global2?.collectErrors } : {}, ...project?.progressStyle !== void 0 || global2?.progressStyle !== void 0 ? { progressStyle: project?.progressStyle ?? global2?.progressStyle } : {}, ...project?.preserveFullOutput !== void 0 || global2?.preserveFullOutput !== void 0 ? { preserveFullOutput: project?.preserveFullOutput ?? global2?.preserveFullOutput } : {}, ...project?.logOutputToFile !== void 0 || global2?.logOutputToFile !== void 0 ? { logOutputToFile: project?.logOutputToFile ?? global2?.logOutputToFile } : {}, ...project?.showCommandContext !== void 0 || global2?.showCommandContext !== void 0 ? { showCommandContext: project?.showCommandContext ?? global2?.showCommandContext } : {}, ...project?.errorFormatting !== void 0 || global2?.errorFormatting !== void 0 ? { errorFormatting: this.mergeErrorFormatting(global2?.errorFormatting, project?.errorFormatting) } : {} }; return merged; } mergeErrorFormatting(global2, project) { if (!global2 && !project) return void 0; return { ...project?.useColors !== void 0 || global2?.useColors !== void 0 ? { useColors: project?.useColors ?? global2?.useColors } : {}, ...project?.useSourceContext !== void 0 || global2?.useSourceContext !== void 0 ? { useSourceContext: project?.useSourceContext ?? global2?.useSourceContext } : {}, ...project?.contextLines !== void 0 || global2?.contextLines !== void 0 ? { contextLines: project?.contextLines ?? global2?.contextLines } : {}, ...project?.showCommandDetails !== void 0 || global2?.showCommandDetails !== void 0 ? { showCommandDetails: project?.showCommandDetails ?? global2?.showCommandDetails } : {} }; } /** * Resolve configuration to runtime values */ resolveURLConfig(config) { const urlConfig = config.security?.urls; if (!urlConfig) return void 0; return { enabled: urlConfig.enabled || false, allowedDomains: urlConfig.allowedDomains || [], blockedDomains: urlConfig.blockedDomains || [], allowedProtocols: urlConfig.allowedProtocols || [ "https", "http" ], maxSize: urlConfig.maxSize ? parseSize(urlConfig.maxSize) : 5 * 1024 * 1024, timeout: urlConfig.timeout ? parseDuration(urlConfig.timeout) : 3e4, warnOnInsecureProtocol: urlConfig.warnOnInsecureProtocol ?? true, cache: { enabled: config.cache?.urls?.enabled ?? true, defaultTTL: config.cache?.urls?.defaultTTL ? parseDuration(config.cache.urls.defaultTTL) : 5 * 60 * 1e3, rules: this.resolveCacheRules(config.cache?.urls?.rules || []) } }; } resolveCacheRules(rules) { return rules.map((rule) => ({ pattern: this.patternToRegex(rule.pattern), ttl: parseDuration(rule.ttl) })); } patternToRegex(pattern) { const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*"); return new RegExp(`^${escaped}$`); } /** * Resolve output configuration to runtime values */ resolveOutputConfig(config) { const outputConfig = config.output; return { showProgress: outputConfig?.showProgress ?? true, maxOutputLines: outputConfig?.maxOutputLines ?? 50, errorBehavior: outputConfig?.errorBehavior ?? "continue", collectErrors: outputConfig?.collectErrors ?? true, progressStyle: outputConfig?.progressStyle ?? "emoji", preserveFullOutput: outputConfig?.preserveFullOutput ?? false, logOutputToFile: outputConfig?.logOutputToFile ?? false, showCommandContext: outputConfig?.showCommandContext ?? true, errorFormatting: { useColors: outputConfig?.errorFormatting?.useColors ?? true, useSourceContext: outputConfig?.errorFormatting?.useSourceContext ?? true, contextLines: outputConfig?.errorFormatting?.contextLines ?? 2, showCommandDetails: outputConfig?.errorFormatting?.showCommandDetails ?? true } }; } }; __name(_ConfigLoader, "ConfigLoader"); var ConfigLoader = _ConfigLoader; var _ImportApproval = class _ImportApproval { constructor(projectPath) { __publicField(this, "config"); __publicField(this, "configLoader"); __publicField(this, "projectPath"); this.projectPath = projectPath; this.configLoader = new ConfigLoader(projectPath); const config = this.configLoader.load(); this.config = config.security?.imports || { requireApproval: true, pinByDefault: true, allowed: [] }; } /** * Check if an import is approved, prompting user if needed */ async checkApproval(url, content) { if (!this.config.requireApproval) { return true; } if (process.env.CI || process.env.NODE_ENV === "test" || process.env.MLLD_TEST === "1" || process.env.VITEST || process.env.VITEST_WORKER_ID || process.env.VITEST_POOL_ID !== void 0 || // Also check if we're in a non-TTY environment (common in tests) !process.stdin.isTTY) { return true; } const hash = this.calculateHash(content); const existingApproval = this.config.allowed?.find((entry) => entry.url === url); if (existingApproval) { if (existingApproval.pinnedVersion) { if (existingApproval.hash === hash) { return true; } else { return this.promptForUpdate(url, content, existingApproval, hash); } } else { return true; } } return this.promptForApproval(url, content, hash); } calculateHash(content) { return createHash("sha256").update(content, "utf8").digest("hex"); } async promptForApproval(url, content, hash) { if (process.env.MLLD_TEST === "1") { return true; } console.log(` \u26A0\uFE0F Import requires approval:`); console.log(` ${url} `); const preview = this.getContentPreview(content); console.log(" [Preview of first 20 lines]"); console.log(preview); const commands = this.detectCommands(content); if (commands.length > 0) { console.log(` This import contains ${commands.length} run command(s):`); commands.slice(0, 5).forEach((cmd) => console.log(` - ${cmd}`)); if (commands.length > 5) { console.log(` ... and ${commands.length - 5} more`); } } const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); try { console.log("\n Allow this import?"); console.log(" [y] This version only (recommended)"); console.log(" [f] This + future updates"); console.log(" [n] Never (cancel)"); console.log(" [v] View full content\n"); let choice = await rl.question(" Choice: "); if (choice.toLowerCase() === "v") { console.log("\n=== Full Content ==="); console.log(content); console.log("=== End Content ===\n"); choice = await rl.question(" Choice: "); } switch (choice.toLowerCase()) { case "y": await this.saveApproval(url, hash, true, commands); console.log(" \u2705 Import approved and cached\n"); return true; case "f": await this.saveApproval(url, hash, false, commands); console.log(" \u2705 Import approved for this and future versions\n"); return true; case "n": default: console.log(" \u274C Import cancelled\n"); return false; } } finally { rl.close(); } } async promptForUpdate(url, content, existing, newHash) { if (process.env.MLLD_TEST === "1") { return true; } console.log(` \u26A0\uFE0F Cached import has changed:`); console.log(` ${url} `); console.log(` Previously approved: ${new Date(existing.allowedAt).toLocaleDateString()}`); console.log(` Content has been modified since approval. `); const preview = this.getContentPreview(content); console.log(" [Preview of new content]"); console.log(preview); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); try { console.log("\n Accept updated content?"); console.log(" [y] Yes, update to new version"); console.log(" [n] No, cancel import\n"); const choice = await rl.question(" Choice: "); if (choice.toLowerCase() === "y") { await this.updateApproval(url, newHash, existing.pinnedVersion); console.log(" \u2705 Import updated and cached\n"); return true; } else { console.log(" \u274C Import cancelled\n"); return false; } } finally { rl.close(); } } getContentPreview(content, lines = 20) { const contentLines = content.split("\n"); const preview = contentLines.slice(0, lines).join("\n"); if (contentLines.length > lines) { return preview + "\n ..."; } return preview; } detectCommands(content) { const commands = []; const runRegex = /@(?:run|exec)\s+(?:\[([^\]]+)\]|`([^`]+)`|([^\n\[`]+))/g; let match; while ((match = runRegex.exec(content)) !== null) { const command = match[1] || match[2] || match[3]; if (command) { const baseCommand = command.trim().split(/\s+/)[0]; if (baseCommand && !commands.includes(baseCommand)) { commands.push(baseCommand); } } } return commands; } async saveApproval(url, hash, pinnedVersion, detectedCommands) { const entry = { url, hash, pinnedVersion, allowedAt: (/* @__PURE__ */ new Date()).toISOString(), detectedCommands }; const config = this.configLoader.load(); if (!config.security) config.security = {}; if (!config.security.imports) config.security.imports = {}; if (!config.security.imports.allowed) config.security.imports.allowed = []; const existingIndex = config.security.imports.allowed.findIndex((e) => e.url === url); if (existingIndex >= 0) { config.security.imports.allowed[existingIndex] = entry; } else { config.security.imports.allowed.push(entry); } const configPath = path8.join(this.projectPath, "mlld.config.json"); await fs3.writeFile(configPath, JSON.stringify(config, null, 2)); this.config = config.security.imports; } async updateApproval(url, newHash, pinnedVersion) { const config = this.configLoader.load(); if (config.security?.imports?.allowed) { const entry = config.security.imports.allowed.find((e) => e.url === url); if (entry) { entry.hash = newHash; entry.allowedAt = (/* @__PURE__ */ new Date()).toISOString(); const configPath = path8.join(this.projectPath, "mlld.config.json"); await fs3.writeFile(configPath, JSON.stringify(config, null, 2)); } } } /** * Check if running in CI/non-interactive mode */ isInteractive() { return process.stdin.isTTY && process.stdout.isTTY; } }; __name(_ImportApproval, "ImportApproval"); var ImportApproval = _ImportApproval; var _ImmutableCache = class _ImmutableCache { constructor(projectPath) { __publicField(this, "cacheDir"); this.cacheDir = path8.join(projectPath, ".mlld", "cache", "imports"); } /** * Get cached content by URL and hash */ async get(url, expectedHash) { const urlHash = this.hashUrl(url); const cachePath = path8.join(this.cacheDir, urlHash); try { const metaPath = `${cachePath}.meta.json`; const metaContent = await fs3.readFile(metaPath, "utf8"); const meta = JSON.parse(metaContent); if (expectedHash && meta.contentHash !== expectedHash) { return null; } const content = await fs3.readFile(cachePath, "utf8"); const actualHash = createHash("sha256").update(content, "utf8").digest("hex"); if (actualHash !== meta.contentHash) { await this.remove(url); return null; } return content; } catch (error) { return null; } } /** * Store content in cache */ async set(url, content) { await fs3.mkdir(this.cacheDir, { recursive: true }); const urlHash = this.hashUrl(url); const cachePath = path8.join(this.cacheDir, urlHash); const contentHash = createHash("sha256").update(content, "utf8").digest("hex"); await fs3.writeFile(cachePath, content, "utf8"); const meta = { url, contentHash, cachedAt: (/* @__PURE__ */ new Date()).toISOString(), size: content.length }; await fs3.writeFile(`${cachePath}.meta.json`, JSON.stringify(meta, null, 2)); return contentHash; } /** * Remove cached entry */ async remove(url) { const urlHash = this.hashUrl(url); const cachePath = path8.join(this.cacheDir, urlHash); try { await fs3.unlink(cachePath); await fs3.unlink(`${cachePath}.meta.json`); } catch { } } /** * Clear entire cache */ async clear() { try { await fs3.rm(this.cacheDir, { recursive: true, force: true }); } catch { } } /** * Get cache statistics */ async getStats() { try { const files = await fs3.readdir(this.cacheDir); const metaFiles = files.filter((f) => f.endsWith(".meta.json")); let totalSize = 0; const urls = []; for (const metaFile of metaFiles) { const metaPath = path8.join(this.cacheDir, metaFile); const metaContent = await fs3.readFile(metaPath, "utf8"); const meta = JSON.parse(metaContent); totalSize += meta.size || 0; urls.push(meta.url); } return { entries: metaFiles.length, totalSize, urls }; } catch { return { entries: 0, totalSize: 0, urls: [] }; } } hashUrl(url) { return createHash("sha256").update(url, "utf8").digest("hex"); } }; __name(_ImmutableCache, "ImmutableCache"); var ImmutableCache2 = _ImmutableCache; var DEFAULT_URL_CONFIG = { enabled: true, allowedProtocols: [ "https", "http" ], allowedDomains: [], blockedDomains: [], timeout: 3e4, maxResponseSize: 10 * 1024 * 1024, cache: { enabled: true, ttl: 36e5, maxEntries: 100, rules: [] } }; var _URLValidator = class _URLValidator { constructor(config = DEFAULT_URL_CONFIG) { __publicField(this, "config"); this.config = config; } /** * Validate a URL against security policies */ async validate(urlString) { if (!this.config.enabled) { return { valid: true }; } try { const url = new URL$1(urlString); if (!this.config.allowedProtocols.includes(url.protocol.replace(":", ""))) { return { valid: false, reason: `Protocol ${url.protocol} not allowed. Allowed: ${this.config.allowedProtocols.join(", ")}` }; } if (this.config.blockedDomains.length > 0) { const isBlocked = this.config.blockedDomains.some((domain) => url.hostname === domain || url.hostname.endsWith(`.${domain}`)); if (isBlocked) { return { valid: false, reason: `Domain ${url.hostname} is blocked` }; } } if (this.config.allowedDomains.length > 0) { const isAllowed = this.config.allowedDomains.some((domain) => url.hostname === domain || url.hostname.endsWith(`.${domain}`)); if (!isAllowed) { return { valid: false, reason: `Domain ${url.hostname} not in allowed list: ${this.config.allowedDomains.join(", ")}` }; } } return { valid: true }; } catch (error) { return { valid: false, reason: `Invalid URL: ${error.message}` }; } } /** * Check if URL is for an import (requires special handling) */ isImportURL(urlString) { return urlString.endsWith(".mld") || urlString.endsWith(".mlld"); } }; __name(_URLValidator, "URLValidator"); var URLValidator = _URLValidator; // security/registry/RegistryResolver.ts function isRegistry(obj) { return typeof obj === "object" && obj !== null && "version" in obj && "modules" in obj && typeof obj.version === "string" && typeof obj.modules === "object"; } __name(isRegistry, "isRegistry"); var _RegistryResolver = class _RegistryResolver { constructor(cache) { __publicField(this, "cache"); this.cache = cache; } /** * Check if a URL is a registry URL */ isRegistryURL(url) { return url.startsWith("mlld://registry/"); } /** * Check if a URL is a gist URL */ isGistURL(url) { return url.startsWith("mlld://gist/"); } /** * Resolve a registry URL to a gist URL * Example: mlld://registry/prompts/code-review → mlld://gist/anthropics/abc123 */ async resolveRegistryURL(registryURL) { if (!this.isRegistryURL(registryURL)) { throw new MlldImportError(`Not a registry URL: ${registryURL}`); } const moduleName = registryURL.replace("mlld://registry/", ""); const registry = await this.fetchRegistry(); const module2 = registry.modules[moduleName]; if (!module2) { throw new MlldImportError(`Unknown registry module: ${moduleName} Available modules: ${Object.keys(registry.modules).join(", ")}`); } return `mlld://gist/${module2.gist}`; } /** * Get information about a registry module */ async getModuleInfo(moduleName) { const registry = await this.fetchRegistry(); return registry.modules[moduleName] || null; } /** * Search registry modules */ async searchModules(query) { const registry = await this.fetchRegistry(); const lowerQuery = query.toLowerCase(); return Object.entries(registry.modules).filter(([name, module2]) => name.toLowerCase().includes(lowerQuery) || module2.description.toLowerCase().includes(lowerQuery) || module2.tags.some((tag) => tag.toLowerCase().includes(lowerQuery))).slice(0, 10); } /** * Fetch registry with caching */ async fetchRegistry() { try { const cached = await this.cache.get(_RegistryResolver.REGISTRY_URL); if (cached) { return JSON.parse(cached); } } catch { } const response = await fetch(_RegistryResolver.REGISTRY_URL); if (!response.ok) { throw new MlldImportError(`Failed to fetch registry: ${response.statusText}`); } const text = await response.text(); const registryData = JSON.parse(text); if (!isRegistry(registryData)) { throw new MlldImportError("Invalid registry format"); } const registry = registryData; await this.cache.set(_RegistryResolver.REGISTRY_URL, text); return registry; } /** * Transform a gist URL to the correct format * Handles both mlld://gist/ and GitHub URLs */ transformGistURL(url) { if (url.startsWith("mlld://gist/")) { return url; } if (url.includes("gist.github.com/")) { const match = url.match(/gist\.github\.com\/([^/]+)\/([a-f0-9]+)/); if (match) { return `mlld://gist/${match[1]}/${match[2]}`; } } return url; } /** * Extract gist info from mlld://gist/ URL */ parseGistURL(gistURL) { if (!this.isGistURL(gistURL)) { throw new MlldImportError(`Not a gist URL: ${gistURL}`); } const parts = gistURL.split("/"); if (parts.length < 4) { throw new MlldImportError(`Invalid gist URL format. Expected: mlld://gist/username/gistId`); } return { username: parts[2], gistId: parts[3] }; } }; __name(_RegistryResolver, "RegistryResolver"); __publicField(_RegistryResolver, "REGISTRY_URL", "https://raw.githubusercontent.com/mlld-lang/registry/main/registry.json"); __publicField(_RegistryResolver, "CACHE_KEY", "registry:main"); __publicField(_RegistryResolver, "CACHE_TTL", 36e5); var RegistryResolver2 = _RegistryResolver; var _AdvisoryChecker = class _AdvisoryChecker { constructor(cache) { __publicField(this, "cache"); this.cache = cache; } /** * Check if a module or gist has advisories */ async checkForAdvisories(moduleName, gistId) { const advisories = await this.fetchAdvisories(); return advisories.filter((advisory) => { if (moduleName && advisory.affects.includes(moduleName)) { return true; } if (gistId && advisory.gists.some((g) => g.includes(gistId))) { return true; } return false; }); } /** * Prompt user about advisories and get approval */ async promptUserAboutAdvisories(advisories, importPath) { if (advisories.length === 0) { return true; } console.log("\n\u26A0\uFE0F Security Advisories Found:"); console.log(` Import: ${importPath} `); for (const advisory of advisories) { console.log(` ${this.formatSeverity(advisory.severity)}: ${advisory.id}`); console.log(` Type: ${advisory.type}`); console.log(` Description: ${advisory.description}`); console.log(` Recommendation: ${advisory.recommendation} `); } const hasCritical = advisories.some((a) => a.severity === "critical"); if (hasCritical) { console.log(" \u26A0\uFE0F CRITICAL security issues detected!"); console.log(" Importing this module is strongly discouraged.\n"); } const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); try { const question = hasCritical ? " Import module with CRITICAL security issues? [y/N]: " : " Import module with security advisories? [y/N]: "; const answer = await rl.question(question); return answer.toLowerCase() === "y"; } finally { rl.close(); } } /** * Format severity with color/emoji */ formatSeverity(severity) { const icons = { critical: "\u{1F534} CRITICAL", high: "\u{1F7E1} HIGH", medium: "\u{1F7E0} MEDIUM", low: "\u{1F7E2} LOW" }; return icons[severity] || severity.toUpperCase(); } /** * Fetch advisories with caching */ async fetchAdvisories() { try { const cached = await this.cache.get(_AdvisoryChecker.ADVISORIES_URL); if (cached) { const data = JSON.parse(cached); return data.advisories; } } catch (error) { } try { const response = await fetch(_AdvisoryChecker.ADVISORIES_URL); if (!response.ok) { console.warn("\u26A0\uFE0F Could not fetch security advisories"); return []; } const text = await response.text(); const data = JSON.parse(text); await this.cache.set(_AdvisoryChecker.ADVISORIES_URL, text); return data.advisories || []; } catch (error) { console.warn("\u26A0\uFE0F Could not check security advisories:", error.message); return []; } } /** * Get all advisories for audit purposes */ async getAllAdvisories() { return this.fetchAdvisories(); } }; __name(_AdvisoryChecker, "AdvisoryChecker"); __publicField(_AdvisoryChecker, "ADVISORIES_URL", "https://raw.githubusercontent.com/mlld-lang/registry/main/advisories.json"); __publicField(_AdvisoryChecker, "CACHE_KEY", "advisories:main"); __publicField(_AdvisoryChecker, "CACHE_TTL", 36e5); var AdvisoryChecker = _AdvisoryChecker; // security/registry/adapters/GistAdapter.ts function isGistResponse(obj) { return typeof obj === "object" && obj !== null && "id" in obj && "files" in obj && "history" in obj && Array.isArray(obj.history) && obj.history.length > 0 && typeof obj.history[0].version === "string"; } __name(isGistResponse, "isGistResponse"); var _GistAdapter = class _GistAdapter { constructor() { __publicField(this, "gistUrlPattern", /^mlld:\/\/gist\/([^/]+)\/([a-f0-9]+)$/); __publicField(this, "httpGistPattern", /gist\.github\.com\/([^/]+)\/([a-f0-9]+)/); } canHandle(reference) { return this.gistUrlPattern.test(reference) || this.httpGistPattern.test(reference); } async fetch(reference, options) { const parsed = this.parseReference(reference); const { username, gistId } = parsed.parts; const response = await fetch(`https://api.github.com/gists/${gistId}`, { headers: this.buildHeaders(options?.token), signal: options?.timeout ? AbortSignal.timeout(options.timeout) : void 0 }); if (!response.ok) { throw new MlldImportError(`Failed to fetch gist: ${response.statusText}`, { reference, status: response.status }); } const gistData = await response.json(); if (!isGistResponse(gistData)) { throw new MlldImportError("Invalid gist response from GitHub API", { reference }); } const mldFile = Object.values(gistData.files).find((f) => f.filename.endsWith(".mld") || f.filename.endsWith(".mlld")); if (!mldFile) { throw new MlldImportError("No .mld or .mlld file found in gist", { reference, availableFiles: Object.keys(gistData.files) }); } const revision = gistData.history[0].version; const immutableUrl = `https://gist.githubusercontent.com/${username}/${gistId}/raw/${revision}/${mldFile.filename}`; const contentResponse = await fetch(immutableUrl, { headers: this.buildHeaders(options?.token), signal: options?.timeout ? AbortSignal.timeout(options.timeout) : void 0 }); if (!contentResponse.ok) { throw new MlldImportError(`Failed to fetch gist content: ${contentResponse.statusText}`, { reference, status: contentResponse.status }); } const content = await contentResponse.text(); return { content, metadata: { provider: "github-gist", author: username, revision, sourceUrl: gistData.html_url, immutableUrl, timestamp: new Date(gistData.history[0].committed_at), extra: { gistId, filename: mldFile.filename, description: gistData.description } } }; } validateResponse(data) { return isGistResponse(data); } getCacheKey(reference) { const parsed = this.parseReference(reference); return `gist:${parsed.parts.username}:${parsed.parts.gistId}`; } parseReference(reference) { let match = reference.match(this.gistUrlPattern); if (match) { return { provider: "github-gist", parts: { username: match[1], gistId: match[2] }, raw: reference }; } match = reference.match(this.httpGistPattern); if (match) { return { provider: "github-gist", parts: { username: match[1], gistId: match[2] }, raw: reference }; } throw new MlldImportError("Invalid gist reference format", { reference }); } buildHeaders(token) { const headers = { "Accept": "application/vnd.github.v3+json", "User-Agent": "mlld-resolver" }; if (token) { headers["Authorization"] = `token ${token}`; } return headers; } }; __name(_GistAdapter, "GistAdapter"); var GistAdapter = _GistAdapter; // security/registry/adapters/RepositoryAdapter.ts function isGitHubContentItem(obj) { return typeof obj === "object" && obj !== null && "name" in obj && "path" in obj && "sha" in obj && "type" in obj && typeof obj.name === "string" && typeof obj.type === "string"; } __name(isGitHubContentItem, "isGitHubContentItem"); function isGitHubRepoInfo(obj) { return typeof obj === "object" && obj !== null && "default_branch" in obj && "full_name" in obj && "owner" in obj && typeof obj.default_branch === "string"; } __name(isGitHubRepoInfo, "isGitHubRepoInfo"); var _RepositoryAdapter = class _RepositoryAdapter { constructor() { __publicField(this, "repoUrlPattern", /^mlld:\/\/github\/([^/]+)\/([^/]+)\/(.+)$/); __publicField(this, "httpRepoPattern", /github\.com\/([^/]+)\/([^/]+)\/(?:blob|raw)\/([^/]+)\/(.+\.mlld?)$/); __publicField(this, "rawUrlPattern", /raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/([^/]+)\/(.+\.mlld?)$/); } canHandle(reference) { return this.repoUrlPattern.test(reference) || this.httpRepoPattern.test(reference) || this.rawUrlPattern.test(reference); } async fetch(reference, options) { const parsed = this.parseReference(reference); const { owner, repo, path: path21 } = parsed.parts; const branch = options?.revision || parsed.parts.branch || await this.getDefaultBranch(owner, repo, options); const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path21}`; try { const response2 = await fetch(rawUrl, { headers: this.buildHeaders(options?.token), signal: options?.timeout ? AbortSignal.timeout(options.timeout) : void 0 }); if (response2.ok) { const content2 = await response2.text(); const commitInfo = await this.getLatestCommit(owner, repo, branch, path21, options); return { content: content2, metadata: { provider: "github-repo", author: owner, revision: commitInfo.sha, sourceUrl: `https://github.com/${owner}/${repo}/blob/${branch}/${path21}`, immutableUrl: `https://raw.githubusercontent.com/${owner}/${repo}/${commitInfo.sha}/${path21}`, timestamp: new Date(commitInfo.date), path: path21, extra: { repository: `${owner}/${repo}`, branch, committer: commitInfo.committer } } }; } } catch (error) { } const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${path21}?ref=${branch}`; const response = await fetch(apiUrl, { headers: this.buildHeaders(options?.token), signal: options?.timeout ? AbortSignal.timeout(options.timeout) : void 0 }); if (!response.ok) { throw new MlldImportError(`Failed to fetch from repository: ${response.statusText}`, { reference, status: response.status }); } const data = await response.json(); if (!isGitHubContentItem(data)) { throw new MlldImportError("Invalid response from GitHub Contents API", { reference }); } if (data.type !== "file") { throw new MlldImportError(`Path is not a file: ${path21}`, { reference, type: data.type }); } if (!data.content) { throw new MlldImportError(`No content fou