Initial commit: Elydora Open Source — self-hosted audit platform for AI agents

Full self-hosted port of the Elydora tamper-evident audit protocol:
- Hono API server with PostgreSQL, Redis, MinIO adapters
- Ed25519 signed operations, chain hashing, Merkle epoch rollups
- PBKDF2 password hashing, HMAC-SHA256 JWT, 5-role RBAC
- Next.js management console
- Node.js, Python, and Go SDKs
- Docker Compose for local deployment
- Helm chart for Kubernetes
- Comprehensive security hardening and audit fixes applied

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 4.6 committed 2026-03-02 13:41:42 -05:00
1 parent 15547a8c35
commit e390e68594
207 files changed
+32790 -63

No files matched your search

@@ -0,0 +1,545 @@
/**
* Agent service — business logic for agent lifecycle management.
*
* Handles registration, lookup, freezing, and key revocation. All
* administrative mutations are recorded as admin_events for audit.
*/
import type {
Agent,
AgentKey,
RegisterAgentRequest,
RegisterAgentResponse,
GetAgentResponse,
ListAgentsResponse,
AdminAction,
} from '../shared/index.js';
import { generateUUIDv7 } from '../utils/uuid.js';
import { base64urlDecode } from '../utils/crypto.js';
import { AppError } from '../middleware/error-handler.js';
import type { Database, PreparedStatement } from '../adapters/interfaces.js';
// ---------------------------------------------------------------------------
// Register agent
// ---------------------------------------------------------------------------
export async function registerAgent(
db: Database,
body: RegisterAgentRequest,
orgId: string,
actor: string,
): Promise<RegisterAgentResponse> {
const now = Date.now();
// Check if agent already exists
const existing = await db
.prepare('SELECT agent_id FROM agents WHERE agent_id = ?')
.bind(body.agent_id)
.first<{ agent_id: string }>();
if (existing) {
throw new AppError(409, 'VALIDATION_ERROR', { key: 'agent.alreadyExists', params: { id: body.agent_id } });
}
// Validate at least one key is provided
if (!body.keys || body.keys.length === 0) {
throw new AppError(400, 'VALIDATION_ERROR', { key: 'agent.atLeastOneKey' });
}
// Validate each public key is a valid 32-byte Ed25519 key
for (const k of body.keys) {
if (k.algorithm !== 'ed25519') {
throw new AppError(400, 'VALIDATION_ERROR', { key: 'agent.unsupportedAlgorithm', params: { algorithm: k.algorithm } });
}
try {
const keyBytes = base64urlDecode(k.public_key);
if (keyBytes.length !== 32) {
throw new AppError(400, 'VALIDATION_ERROR', { key: 'agent.invalidPublicKeyLength', params: { kid: k.kid, expected: 32, actual: keyBytes.length } });
}
} catch (e) {
if (e instanceof AppError) throw e;
throw new AppError(400, 'VALIDATION_ERROR', { key: 'agent.invalidPublicKeyEncoding', params: { kid: k.kid } });
}
}
// Build the agent record
const agent: Agent = {
agent_id: body.agent_id,
org_id: orgId,
display_name: body.display_name ?? body.agent_id,
responsible_entity: body.responsible_entity ?? '',
integration_type: body.integration_type ?? 'sdk',
status: 'active',
created_at: now,
updated_at: now,
};
// Build key records
const keys: AgentKey[] = body.keys.map((k) => ({
kid: k.kid,
agent_id: body.agent_id,
public_key: k.public_key,
algorithm: k.algorithm,
status: 'active' as const,
created_at: now,
retired_at: null,
}));
// Use a batch to insert atomically
const statements: PreparedStatement[] = [];
// Insert agent
statements.push(
db
.prepare(
`INSERT INTO agents (agent_id, org_id, display_name, responsible_entity, integration_type, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
)
.bind(
agent.agent_id,
agent.org_id,
agent.display_name,
agent.responsible_entity,
agent.integration_type,
agent.status,
agent.created_at,
agent.updated_at,
),
);
// Insert keys
for (const key of keys) {
statements.push(
db
.prepare(
`INSERT INTO agent_keys (kid, agent_id, public_key, algorithm, status, created_at, retired_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
)
.bind(
key.kid,
key.agent_id,
key.public_key,
key.algorithm,
key.status,
key.created_at,
key.retired_at,
),
);
}
// Insert admin event
const eventId = generateUUIDv7();
const action: AdminAction = 'agent.register';
statements.push(
db
.prepare(
`INSERT INTO admin_events (event_id, org_id, actor, action, target_type, target_id, details, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
)
.bind(
eventId,
orgId,
actor,
action,
'agent',
body.agent_id,
JSON.stringify({ display_name: agent.display_name, integration_type: agent.integration_type, key_count: keys.length }),
now,
),
);
await db.batch(statements);
return { agent, keys };
}
// ---------------------------------------------------------------------------
// List agents
// ---------------------------------------------------------------------------
export async function listAgents(
db: Database,
orgId: string,
): Promise<ListAgentsResponse> {
const result = await db
.prepare('SELECT * FROM agents WHERE org_id = ? ORDER BY created_at DESC')
.bind(orgId)
.all<Agent>();
return { agents: result.results ?? [] };
}
// ---------------------------------------------------------------------------
// Get agent
// ---------------------------------------------------------------------------
export async function getAgent(
db: Database,
agentId: string,
orgId: string,
): Promise<GetAgentResponse> {
const agentRow = await db
.prepare('SELECT * FROM agents WHERE agent_id = ? AND org_id = ?')
.bind(agentId, orgId)
.first<Agent>();
if (!agentRow) {
throw new AppError(404, 'NOT_FOUND', { key: 'agent.notFound', params: { id: agentId } });
}
const keysResult = await db
.prepare('SELECT * FROM agent_keys WHERE agent_id = ?')
.bind(agentId)
.all<AgentKey>();
const keys = keysResult.results ?? [];
return { agent: agentRow, keys };
}
// ---------------------------------------------------------------------------
// Update agent integration type
// ---------------------------------------------------------------------------
const VALID_INTEGRATION_TYPES = [
'sdk', 'claudecode', 'cursor', 'gemini', 'kirocli', 'kiroide',
'opencode', 'copilot', 'letta', 'codex', 'kimi',
'enterprise', 'gui', 'other',
] as const;
export async function updateAgentIntegrationType(
db: Database,
agentId: string,
integrationValue: string,
orgId: string,
actor: string,
): Promise<{ agent: Agent }> {
const now = Date.now();
const agentRow = await db
.prepare('SELECT * FROM agents WHERE agent_id = ? AND org_id = ?')
.bind(agentId, orgId)
.first<Agent>();
if (!agentRow) {
throw new AppError(404, 'NOT_FOUND', { key: 'agent.notFound', params: { id: agentId } });
}
if (!(VALID_INTEGRATION_TYPES as readonly string[]).includes(integrationValue)) {
throw new AppError(
400,
'VALIDATION_ERROR',
{ key: 'agent.invalidIntegrationType', params: { value: integrationValue, valid: VALID_INTEGRATION_TYPES.join(', ') } },
);
}
const statements: PreparedStatement[] = [];
statements.push(
db
.prepare('UPDATE agents SET integration_type = ?, updated_at = ? WHERE agent_id = ?')
.bind(integrationValue, now, agentId),
);
const eventId = generateUUIDv7();
const action: AdminAction = 'agent.update';
statements.push(
db
.prepare(
`INSERT INTO admin_events (event_id, org_id, actor, action, target_type, target_id, details, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
)
.bind(
eventId,
orgId,
actor,
action,
'agent',
agentId,
JSON.stringify({ integration_type: integrationValue, previous_integration_type: agentRow.integration_type }),
now,
),
);
await db.batch(statements);
const updatedAgent: Agent = {
...agentRow,
integration_type: integrationValue,
updated_at: now,
};
return { agent: updatedAgent };
}
// ---------------------------------------------------------------------------
// Freeze agent
// ---------------------------------------------------------------------------
export async function freezeAgent(
db: Database,
agentId: string,
reason: string,
orgId: string,
actor: string,
): Promise<{ agent: Agent }> {
const now = Date.now();
// Look up the agent
const agentRow = await db
.prepare('SELECT * FROM agents WHERE agent_id = ? AND org_id = ?')
.bind(agentId, orgId)
.first<Agent>();
if (!agentRow) {
throw new AppError(404, 'NOT_FOUND', { key: 'agent.notFound', params: { id: agentId } });
}
if (agentRow.status === 'frozen') {
throw new AppError(409, 'VALIDATION_ERROR', { key: 'agent.alreadyFrozen', params: { id: agentId } });
}
if (agentRow.status === 'revoked') {
throw new AppError(409, 'VALIDATION_ERROR', { key: 'agent.permanentlyRevoked', params: { id: agentId } });
}
const statements: PreparedStatement[] = [];
// Update agent status
statements.push(
db
.prepare('UPDATE agents SET status = ?, updated_at = ? WHERE agent_id = ?')
.bind('frozen', now, agentId),
);
// Log admin event
const eventId = generateUUIDv7();
const action: AdminAction = 'agent.freeze';
statements.push(
db
.prepare(
`INSERT INTO admin_events (event_id, org_id, actor, action, target_type, target_id, details, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
)
.bind(eventId, orgId, actor, action, 'agent', agentId, JSON.stringify({ reason }), now),
);
await db.batch(statements);
const updatedAgent: Agent = {
...agentRow,
status: 'frozen',
updated_at: now,
};
return { agent: updatedAgent };
}
// ---------------------------------------------------------------------------
// Unfreeze agent
// ---------------------------------------------------------------------------
export async function unfreezeAgent(
db: Database,
agentId: string,
reason: string,
orgId: string,
actor: string,
): Promise<{ agent: Agent }> {
const now = Date.now();
const agentRow = await db
.prepare('SELECT * FROM agents WHERE agent_id = ? AND org_id = ?')
.bind(agentId, orgId)
.first<Agent>();
if (!agentRow) {
throw new AppError(404, 'NOT_FOUND', { key: 'agent.notFound', params: { id: agentId } });
}
if (agentRow.status === 'active') {
throw new AppError(409, 'VALIDATION_ERROR', { key: 'agent.notFrozen', params: { id: agentId } });
}
if (agentRow.status === 'revoked') {
throw new AppError(409, 'VALIDATION_ERROR', { key: 'agent.permanentlyRevokedCannotUnfreeze', params: { id: agentId } });
}
const statements: PreparedStatement[] = [];
statements.push(
db
.prepare('UPDATE agents SET status = ?, updated_at = ? WHERE agent_id = ?')
.bind('active', now, agentId),
);
const eventId = generateUUIDv7();
const action: AdminAction = 'agent.unfreeze';
statements.push(
db
.prepare(
`INSERT INTO admin_events (event_id, org_id, actor, action, target_type, target_id, details, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
)
.bind(eventId, orgId, actor, action, 'agent', agentId, JSON.stringify({ reason }), now),
);
await db.batch(statements);
const updatedAgent: Agent = {
...agentRow,
status: 'active',
updated_at: now,
};
return { agent: updatedAgent };
}
// ---------------------------------------------------------------------------
// Delete agent
// ---------------------------------------------------------------------------
export async function deleteAgent(
db: Database,
agentId: string,
orgId: string,
actor: string,
): Promise<void> {
const now = Date.now();
// Verify the agent belongs to this org
const agentRow = await db
.prepare('SELECT * FROM agents WHERE agent_id = ? AND org_id = ?')
.bind(agentId, orgId)
.first<Agent>();
if (!agentRow) {
throw new AppError(404, 'NOT_FOUND', { key: 'agent.notFound', params: { id: agentId } });
}
const statements: PreparedStatement[] = [];
// Delete receipts for this agent's operations (receipts FK → operations)
statements.push(
db.prepare(
'DELETE FROM receipts WHERE operation_id IN (SELECT operation_id FROM operations WHERE agent_id = ?)',
).bind(agentId),
);
// Delete operations (operations FK → agents)
statements.push(
db.prepare('DELETE FROM operations WHERE agent_id = ?').bind(agentId),
);
// Delete associated keys (agent_keys FK → agents)
statements.push(
db.prepare('DELETE FROM agent_keys WHERE agent_id = ?').bind(agentId),
);
// Delete the agent
statements.push(
db.prepare('DELETE FROM agents WHERE agent_id = ? AND org_id = ?').bind(agentId, orgId),
);
// Log admin event
const eventId = generateUUIDv7();
const action: AdminAction = 'agent.delete';
statements.push(
db
.prepare(
`INSERT INTO admin_events (event_id, org_id, actor, action, target_type, target_id, details, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
)
.bind(
eventId,
orgId,
actor,
action,
'agent',
agentId,
JSON.stringify({ display_name: agentRow.display_name }),
now,
),
);
await db.batch(statements);
}
// ---------------------------------------------------------------------------
// Revoke key
// ---------------------------------------------------------------------------
export async function revokeKey(
db: Database,
agentId: string,
kid: string,
reason: string,
orgId: string,
actor: string,
): Promise<{ key: AgentKey }> {
const now = Date.now();
// Verify the agent belongs to this org
const agentRow = await db
.prepare('SELECT * FROM agents WHERE agent_id = ? AND org_id = ?')
.bind(agentId, orgId)
.first<Agent>();
if (!agentRow) {
throw new AppError(404, 'NOT_FOUND', { key: 'agent.notFound', params: { id: agentId } });
}
// Look up the key
const keyRow = await db
.prepare('SELECT * FROM agent_keys WHERE kid = ? AND agent_id = ?')
.bind(kid, agentId)
.first<AgentKey>();
if (!keyRow) {
throw new AppError(404, 'NOT_FOUND', { key: 'agent.keyNotFound', params: { kid, id: agentId } });
}
if (keyRow.status === 'revoked') {
throw new AppError(409, 'VALIDATION_ERROR', { key: 'agent.keyAlreadyRevoked', params: { kid } });
}
const statements: PreparedStatement[] = [];
// Revoke the key
statements.push(
db
.prepare('UPDATE agent_keys SET status = ?, retired_at = ? WHERE kid = ?')
.bind('revoked', now, kid),
);
// Log admin event
const eventId = generateUUIDv7();
const action: AdminAction = 'key.revoke';
statements.push(
db
.prepare(
`INSERT INTO admin_events (event_id, org_id, actor, action, target_type, target_id, details, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
)
.bind(
eventId,
orgId,
actor,
action,
'agent_key',
kid,
JSON.stringify({ agent_id: agentId, reason }),
now,
),
);
await db.batch(statements);
const updatedKey: AgentKey = {
...keyRow,
status: 'revoked',
retired_at: now,
};
return { key: updatedKey };
}