UNPKG

@openguardrails/moltguard

Version:

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

130 lines 4.71 kB
/** * ConfigSync - Periodically pulls team-level configuration from Core. * * Only active when the agent's account is on the "business" plan. * Pulls policies and gateway config every 5 minutes from * GET /api/v1/business/config and applies them locally. */ // ============================================================================= // Constants // ============================================================================= /** Poll interval in ms (5 minutes) */ const POLL_INTERVAL_MS = 5 * 60_000; /** Timeout for Core API calls */ const API_TIMEOUT_MS = 5_000; // ============================================================================= // ConfigSync Class // ============================================================================= export class ConfigSync { enabled = false; options; log; credentials = null; pollInterval = null; /** Last fetched config (for change detection) */ lastConfigHash = ""; /** Last successfully fetched config */ currentConfig = null; constructor(options, log) { this.options = options; this.log = log; } // ─── Lifecycle ─────────────────────────────────────────────────── /** * Initialize config sync. Only enables if plan is "business". */ async initialize(plan) { if (plan !== "business") { this.log.debug?.(`ConfigSync: plan is "${plan}", not enabling`); return; } this.enabled = true; // Pull immediately on startup await this.pull(); // Start periodic polling this.startPolling(); this.log.info("ConfigSync: enabled, polling every 5 minutes"); } /** Set Core credentials */ setCredentials(credentials) { this.credentials = credentials; } /** Get current config (may be null if not yet fetched) */ getConfig() { return this.currentConfig; } /** Whether sync is active */ isEnabled() { return this.enabled; } /** Stop polling */ stop() { if (this.pollInterval) { clearInterval(this.pollInterval); this.pollInterval = null; } this.enabled = false; } // ─── Polling ─────────────────────────────────────────────────── startPolling() { if (this.pollInterval) return; this.pollInterval = setInterval(() => { this.pull().catch((err) => { this.log.debug?.(`ConfigSync: poll error: ${err}`); }); }, POLL_INTERVAL_MS); this.pollInterval.unref(); } /** Pull config from Core */ async pull() { if (!this.enabled || !this.credentials) return null; const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); try { const response = await fetch(`${this.options.coreUrl}/api/v1/business/config`, { headers: { Authorization: `Bearer ${this.credentials.apiKey}`, }, signal: controller.signal, }); if (!response.ok) { this.log.debug?.(`ConfigSync: pull failed with ${response.status}`); return null; } const json = (await response.json()); if (!json.success || !json.data) { return null; } const config = json.data; // Check if config changed (simple JSON hash) const hash = JSON.stringify(config); if (hash !== this.lastConfigHash) { this.lastConfigHash = hash; this.currentConfig = config; // Notify callback if (this.options.onUpdate) { try { this.options.onUpdate(config); } catch (err) { this.log.error(`ConfigSync: onUpdate callback error: ${err}`); } } this.log.debug?.(`ConfigSync: config updated (${config.policies.length} policies)`); } return config; } catch (err) { if (err.name !== "AbortError") { this.log.debug?.(`ConfigSync: pull error: ${err}`); } return null; } finally { clearTimeout(timer); } } } //# sourceMappingURL=config-sync.js.map