UNPKG

@openguardrails/moltguard

Version:

AI agent security plugin for OpenClaw: prompt injection detection, PII sanitization, and monitoring dashboard

200 lines 6.51 kB
/** * Gateway configuration management */ import { readFileSync, existsSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; const DEFAULT_CONFIG_PATH = join(homedir(), ".openclaw", "extensions", "moltguard", "data", "gateway.json"); /** * Load gateway configuration from file or environment */ export function loadConfig(configPath) { const path = configPath || DEFAULT_CONFIG_PATH; // Default configuration const defaultConfig = { port: parseInt(process.env.GATEWAY_PORT || "53669", 10), backends: {}, }; // Try to load from file if (existsSync(path)) { try { const fileContent = readFileSync(path, "utf-8"); const fileConfig = JSON.parse(fileContent); return mergeConfig(defaultConfig, fileConfig); } catch (error) { console.warn(`[ai-security-gateway] Failed to load config from ${path}:`, error); } } // Load from environment variables return loadFromEnv(defaultConfig); } /** * Load backend configs from environment variables */ function loadFromEnv(config) { // Anthropic if (process.env.ANTHROPIC_API_KEY) { config.backends.anthropic = { baseUrl: process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com", apiKey: process.env.ANTHROPIC_API_KEY, type: "anthropic", }; } // OpenAI if (process.env.OPENAI_API_KEY) { config.backends.openai = { baseUrl: process.env.OPENAI_BASE_URL || "https://api.openai.com", apiKey: process.env.OPENAI_API_KEY, type: "openai", }; } // Kimi (Moonshot) — only set if openai backend not already configured if ((process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY) && !config.backends.openai) { config.backends.kimi = { baseUrl: process.env.KIMI_BASE_URL || "https://api.moonshot.cn", apiKey: process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY || "", type: "openai", }; } // Gemini if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) { config.backends.gemini = { baseUrl: process.env.GEMINI_BASE_URL || "https://generativelanguage.googleapis.com", apiKey: process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || "", type: "gemini", }; } // OpenRouter if (process.env.OPENROUTER_API_KEY) { config.backends.openrouter = { baseUrl: process.env.OPENROUTER_BASE_URL || "https://openrouter.ai/api", apiKey: process.env.OPENROUTER_API_KEY, type: "openai", ...(process.env.OPENROUTER_REFERER && { referer: process.env.OPENROUTER_REFERER, }), ...(process.env.OPENROUTER_TITLE && { title: process.env.OPENROUTER_TITLE, }), }; } return config; } /** * Merge file config with default config */ function mergeConfig(defaultConfig, fileConfig) { return { port: fileConfig.port ?? defaultConfig.port, backends: { ...defaultConfig.backends, ...fileConfig.backends, }, routing: fileConfig.routing, defaultBackends: fileConfig.defaultBackends, }; } /** * Validate configuration */ export function validateConfig(config) { if (config.port < 1 || config.port > 65535) { throw new Error(`Invalid port: ${config.port}`); } // Note: Backends are now optional. Gateway will act as transparent proxy. // If no backends configured, gateway will forward requests based on routing rules // or pass through to the original target. // Validate each backend (if any) for (const [name, backend] of Object.entries(config.backends)) { if (!backend.baseUrl) { throw new Error(`Backend ${name} missing baseUrl`); } if (!backend.apiKey) { throw new Error(`Backend ${name} missing apiKey`); } } } /** * Infer API type from backend name */ export function inferApiType(name) { const lower = name.toLowerCase(); if (lower.includes("anthropic") || lower.includes("claude")) { return "anthropic"; } if (lower.includes("gemini") || lower.includes("google")) { return "gemini"; } // Default to OpenAI-compatible for everything else return "openai"; } /** * Get API type for a backend */ export function getBackendApiType(name, config) { const backend = config.backends[name]; if (backend?.type) { return backend.type; } return inferApiType(name); } /** * Find backend by API key */ export function findBackendByApiKey(apiKey, config) { for (const [name, backend] of Object.entries(config.backends)) { if (backend.apiKey === apiKey) { return { name, backend }; } } return null; } /** * Find default backend for an API type */ export function findDefaultBackend(apiType, config) { // Check explicit default first const defaultName = config.defaultBackends?.[apiType]; if (defaultName && config.backends[defaultName]) { return { name: defaultName, backend: config.backends[defaultName] }; } // Find first backend matching the API type for (const [name, backend] of Object.entries(config.backends)) { if (getBackendApiType(name, config) === apiType) { return { name, backend }; } } return null; } /** * Find backend by request path prefix * Matches the longest pathPrefix that is a prefix of the request path */ export function findBackendByPathPrefix(requestPath, config) { let bestMatch = null; let bestMatchLength = 0; for (const [name, backend] of Object.entries(config.backends)) { if (backend.pathPrefix && requestPath.startsWith(backend.pathPrefix)) { if (backend.pathPrefix.length > bestMatchLength) { bestMatch = { name, backend }; bestMatchLength = backend.pathPrefix.length; } } } return bestMatch; } /** * Find backend by model name */ export function findBackendByModel(modelName, config) { for (const [name, backend] of Object.entries(config.backends)) { if (backend.models?.includes(modelName)) { return { name, backend }; } } return null; } //# sourceMappingURL=config.js.map