mlld
Version:
mlld: a modular prompt scripting language
1,502 lines (1,495 loc) • 323 kB
JavaScript
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