const SAFE_INTERNAL_HOSTS = new Set(["web", "acai-web", "localhost", "127.0.0.1"]); function parseUrl(url, fieldName, context) { try { return new URL(url); } catch { throw new Error(`[${context}] Invalid ${fieldName}: ${url || ""}`); } } export function assertSafeCmsTarget(target, context = "cms") { const publicUrl = typeof target === "string" ? target : (target?.web_url || ""); const apiUrl = typeof target === "string" ? target : (target?.api_web_url || ""); const mode = typeof target === "string" ? "local" : (target?.mode || "local"); if (!apiUrl) { throw new Error( `[${context}] ACAI_API_WEB_URL is required. Refusing to use ACAI_WEB_URL directly for CMS requests.` ); } const parsedApiUrl = parseUrl(apiUrl, "ACAI_API_WEB_URL", context); if (!["http:", "https:"].includes(parsedApiUrl.protocol)) { throw new Error( `[${context}] Unsafe ACAI_API_WEB_URL protocol "${parsedApiUrl.protocol}".` ); } // Modo "production": el .acai del proyecto autoriza explicitamente apuntar // al sitio real. Saltamos el whitelist de hosts internos. Usar SOLO para // testing/debug controlado — el agente IA puede modificar produccion. if (mode === "production") { return { publicUrl, apiUrl, forgeHost: typeof target === "string" ? null : (target?.forge_host || null), }; } if (!SAFE_INTERNAL_HOSTS.has(parsedApiUrl.hostname)) { throw new Error( `[${context}] Unsafe ACAI_API_WEB_URL host "${parsedApiUrl.hostname}". ` + `Only approved local hosts are allowed: ${Array.from(SAFE_INTERNAL_HOSTS).join(", ")}. ` + `Set "mode": "production" in .acai to bypass this check (intended for testing only).` ); } if (publicUrl) { const parsedPublicUrl = parseUrl(publicUrl, "ACAI_WEB_URL", context); const publicIsSafeInternal = SAFE_INTERNAL_HOSTS.has(parsedPublicUrl.hostname); if (!publicIsSafeInternal && parsedPublicUrl.host === parsedApiUrl.host) { throw new Error( `[${context}] ACAI_API_WEB_URL resolves to the same public host as ACAI_WEB_URL (${parsedApiUrl.host}).` ); } } return { publicUrl, apiUrl, forgeHost: typeof target === "string" ? null : (target?.forge_host || null), }; } export function isSafeInternalHost(hostname) { return SAFE_INTERNAL_HOSTS.has(hostname); }