package plugins import "fmt" // GenerateGuardScript returns a self-contained agent status guard. func GenerateGuardScript(agentName string, agentID string) string { return generateGuardScript(agentName, agentID, "", false) } func generateGuardScript(agentName, agentID, successOutput string, failClosed bool) string { return fmt.Sprintf(`#!/usr/bin/env node 'use strict'; // Elydora agent guard for %s. Generated by Elydora SDK. const crypto = require('node:crypto'); const fs = require('node:fs'); const path = require('node:path'); const os = require('node:os'); const AGENT_NAME = %q; const AGENT_ID = %q; const SUCCESS_OUTPUT = %q; const FAIL_CLOSED = %t; const ELYDORA_DIR = path.join(os.homedir(), '.elydora'); const CONFIG_PATH = path.join(ELYDORA_DIR, AGENT_ID, 'config.json'); const STATUS_CACHE_PATH = path.join(ELYDORA_DIR, AGENT_ID, 'status-cache.json'); const CACHE_TTL_MS = 60000; %s function validateStatus(value, source) { if (!['active', 'frozen', 'revoked'].includes(value)) { throw new Error(source + ' contains an invalid agent status'); } return value; } function blockAgent(status) { const state = status === 'frozen' ? 'is frozen by Elydora' : 'is revoked in Elydora'; const message = 'Agent "' + AGENT_NAME + '" ' + state + '.'; if (SUCCESS_OUTPUT) { process.stdout.write(JSON.stringify({ permission: 'deny', userMessage: message, agentMessage: message + ' Tool execution is blocked.', }) + '\n'); } process.stderr.write('[Elydora guard] ' + message + ' Tool execution blocked.\n'); process.exitCode = 2; } async function readInput() { const chunks = []; for await (const chunk of process.stdin) chunks.push(chunk); const raw = Buffer.concat(chunks).toString('utf-8').trim(); if (!raw) { if (FAIL_CLOSED) throw new Error('Hook input is empty'); process.stderr.write( '[Elydora guard] Hook input is empty; enforcement continued in fail-open mode.\n', ); return; } let value; try { value = JSON.parse(raw); } catch (error) { if (FAIL_CLOSED) throw new Error('Hook input is invalid JSON: ' + error.message); process.stderr.write( '[Elydora guard] Hook input is invalid JSON; ' + 'enforcement continued in fail-open mode: ' + error.message + '\n', ); return; } if (!value || typeof value !== 'object' || Array.isArray(value)) { if (FAIL_CLOSED) throw new Error('Hook input must contain a JSON object'); process.stderr.write( '[Elydora guard] Hook input must contain a JSON object; ' + 'enforcement continued in fail-open mode.\n', ); } } function readConfig() { const raw = readProtectedFile( CONFIG_PATH, 'Agent config', MAX_PROTECTED_CONFIG_BYTES, ).toString('utf-8'); let config; try { config = JSON.parse(raw); } catch (error) { throw new Error('Agent config is invalid JSON: ' + error.message); } if (!config || typeof config !== 'object' || Array.isArray(config)) { throw new Error('Agent config must contain a JSON object'); } if (config.agent_id !== AGENT_ID || (config.agent_name !== undefined && config.agent_name !== AGENT_NAME)) { throw new Error('Agent config identity does not match the generated guard'); } return config; } function readStatusCache() { let raw; try { raw = readProtectedFile( STATUS_CACHE_PATH, 'Status cache', MAX_PROTECTED_CONFIG_BYTES, ).toString('utf-8'); } catch (error) { if (hasRuntimeErrorCode(error, 'ENOENT')) return undefined; throw new Error('Failed to read status cache: ' + error.message); } let cache; try { cache = JSON.parse(raw); } catch (error) { throw new Error('Status cache is invalid JSON: ' + error.message); } if (!cache || typeof cache !== 'object' || Array.isArray(cache) || !Number.isFinite(cache.cached_at)) { throw new Error('Status cache has an invalid shape'); } const age = Date.now() - cache.cached_at; if (age < 0) throw new Error('Status cache timestamp is in the future'); return { status: validateStatus(cache.status, 'Status cache'), age }; } function writeStatusCache(status) { writeProtectedJson(STATUS_CACHE_PATH, 'Status cache', { status: status, cached_at: Date.now(), }); } function resolveBaseURL(config) { const value = config.base_url || 'https://api.elydora.com'; if (typeof value !== 'string' || /[\\\u0000-\u0020]/.test(value)) { throw new Error('Agent config base_url must be an absolute HTTP or HTTPS URL'); } let parsed; try { parsed = new URL(value); } catch (error) { throw new Error('Agent config base_url must be an absolute HTTP or HTTPS URL'); } if (!['http:', 'https:'].includes(parsed.protocol) || !parsed.hostname) { throw new Error('Agent config base_url must be an absolute HTTP or HTTPS URL'); } if (parsed.username || parsed.password || parsed.search || parsed.hash) { throw new Error( 'Agent config base_url must exclude credentials, query parameters, and fragments', ); } return value.replace(/\/+$/, ''); } async function resolveRemoteStatus(config) { const baseURL = resolveBaseURL(config); const token = config.token || ''; if (typeof token !== 'string') throw new Error('Agent config token must be a string'); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 3000); try { const headers = { Accept: 'application/json' }; if (token) headers.Authorization = 'Bearer ' + token; const response = await fetch(baseURL + '/v1/agents/' + encodeURIComponent(AGENT_ID), { headers: headers, signal: controller.signal, }); if (!response.ok) throw new Error('Agent status API returned HTTP ' + response.status); const data = await response.json(); return validateStatus(data && data.agent && data.agent.status, 'Agent status API'); } finally { clearTimeout(timeout); } } async function main() { await readInput(); let config; try { config = readConfig(); } catch (error) { if (FAIL_CLOSED) throw new Error('Failed to read agent config: ' + error.message); process.stderr.write('[Elydora guard] Failed to read agent config: ' + error.message + '\n'); return; } try { const cache = readStatusCache(); if (cache && cache.age < CACHE_TTL_MS) { if (cache.status !== 'active') blockAgent(cache.status); return; } } catch (error) { if (FAIL_CLOSED) throw error; process.stderr.write('[Elydora guard] ' + error.message + '\n'); } try { const status = await resolveRemoteStatus(config); writeStatusCache(status); if (status !== 'active') blockAgent(status); } catch (error) { if (FAIL_CLOSED) throw new Error('Failed to resolve agent status: ' + error.message); process.stderr.write( '[Elydora guard] Failed to resolve agent status: ' + error.message + '\n', ); } } main().then(() => { if ((!process.exitCode || process.exitCode === 0) && SUCCESS_OUTPUT) { process.stdout.write(SUCCESS_OUTPUT); } }).catch((error) => { process.stderr.write( '[Elydora guard] ' + (error.stack || error.message || String(error)) + '\n', ); process.exitCode = 1; }); `, agentName, agentName, agentID, successOutput, failClosed, protectedRuntimeFileReader) }