@agenticmail/enterprise 0.5.263 → 0.5.265

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Database Access System — Public API
3
+ *
4
+ * Enterprise-grade database connectivity for AI agents.
5
+ */
6
+
7
+ export { DatabaseConnectionManager } from './connection-manager.js';
8
+ export { createDatabaseAccessRoutes } from './routes.js';
9
+ export { createDatabaseTools } from './agent-tools.js';
10
+ export { sanitizeQuery, classifyQuery, sanitizeForLogging } from './query-sanitizer.js';
11
+ export type {
12
+ DatabaseType,
13
+ DatabaseConnectionConfig,
14
+ AgentDatabaseAccess,
15
+ DatabasePermission,
16
+ DatabaseQuery,
17
+ QueryResult,
18
+ DatabaseAuditEntry,
19
+ ConnectionPoolStats,
20
+ } from './types.js';
21
+ export { DATABASE_LABELS, DATABASE_CATEGORIES } from './types.js';
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Query Sanitizer — Enterprise Security Layer
3
+ *
4
+ * Validates and sanitizes all database queries before execution.
5
+ * Prevents SQL injection, enforces schema restrictions, blocks dangerous operations.
6
+ */
7
+
8
+ import type { DatabasePermission, DatabaseConnectionConfig, AgentDatabaseAccess } from './types.js';
9
+
10
+ // ─── Dangerous Patterns ──────────────────────────────────────────────────────
11
+
12
+ /** SQL patterns that are ALWAYS blocked regardless of permissions */
13
+ const BLOCKED_PATTERNS = [
14
+ /;\s*DROP\s+/i,
15
+ /;\s*TRUNCATE\s+/i,
16
+ /;\s*ALTER\s+/i,
17
+ /;\s*CREATE\s+/i,
18
+ /;\s*GRANT\s+/i,
19
+ /;\s*REVOKE\s+/i,
20
+ /;\s*SET\s+ROLE/i,
21
+ /LOAD_FILE\s*\(/i,
22
+ /INTO\s+OUTFILE/i,
23
+ /INTO\s+DUMPFILE/i,
24
+ /INFORMATION_SCHEMA/i,
25
+ /pg_catalog\./i,
26
+ /sys\.\w+/i,
27
+ /xp_cmdshell/i,
28
+ /sp_execute/i,
29
+ /EXEC\s*\(/i,
30
+ /EXECUTE\s+IMMEDIATE/i,
31
+ /--\s*$/m, // SQL comments at end of line (common injection)
32
+ /\/\*[\s\S]*?\*\//, // Block comments (hiding malicious code)
33
+ /SLEEP\s*\(/i,
34
+ /BENCHMARK\s*\(/i,
35
+ /WAITFOR\s+DELAY/i,
36
+ /pg_sleep/i,
37
+ ];
38
+
39
+ /** Patterns that require 'execute' permission */
40
+ const EXECUTE_PATTERNS = [
41
+ /\bCALL\s+/i,
42
+ /\bEXEC\s+/i,
43
+ /\bEXECUTE\s+/i,
44
+ ];
45
+
46
+ // ─── Query Classification ────────────────────────────────────────────────────
47
+
48
+ export type QueryOperation = 'read' | 'write' | 'delete' | 'schema' | 'execute' | 'blocked';
49
+
50
+ export function classifyQuery(sql: string): QueryOperation {
51
+ const trimmed = sql.trim().toUpperCase();
52
+
53
+ // Check blocked patterns first
54
+ for (const pattern of BLOCKED_PATTERNS) {
55
+ if (pattern.test(sql)) return 'blocked';
56
+ }
57
+
58
+ // Check execute patterns
59
+ for (const pattern of EXECUTE_PATTERNS) {
60
+ if (pattern.test(sql)) return 'execute';
61
+ }
62
+
63
+ if (trimmed.startsWith('SELECT') || trimmed.startsWith('WITH') || trimmed.startsWith('EXPLAIN')) return 'read';
64
+ if (trimmed.startsWith('INSERT') || trimmed.startsWith('UPDATE') || trimmed.startsWith('UPSERT') || trimmed.startsWith('MERGE')) return 'write';
65
+ if (trimmed.startsWith('DELETE')) return 'delete';
66
+ if (trimmed.startsWith('CREATE') || trimmed.startsWith('ALTER') || trimmed.startsWith('DROP')) return 'schema';
67
+
68
+ // Default to blocked for unrecognized patterns
69
+ return 'blocked';
70
+ }
71
+
72
+ // ─── Permission Check ────────────────────────────────────────────────────────
73
+
74
+ export interface SanitizeResult {
75
+ allowed: boolean;
76
+ operation: QueryOperation;
77
+ reason?: string;
78
+ sanitizedQuery?: string;
79
+ }
80
+
81
+ export function sanitizeQuery(
82
+ sql: string,
83
+ permissions: DatabasePermission[],
84
+ connectionConfig: DatabaseConnectionConfig,
85
+ agentAccess: AgentDatabaseAccess,
86
+ ): SanitizeResult {
87
+ // 1. Classify the query
88
+ const operation = classifyQuery(sql);
89
+
90
+ if (operation === 'blocked') {
91
+ return { allowed: false, operation, reason: 'Query contains blocked patterns (potential injection or dangerous operations)' };
92
+ }
93
+
94
+ // 2. Check agent has required permission
95
+ if (!permissions.includes(operation as DatabasePermission)) {
96
+ return { allowed: false, operation, reason: `Agent lacks '${operation}' permission on this database` };
97
+ }
98
+
99
+ // 3. Check table access
100
+ const tables = extractTableNames(sql);
101
+ const mergedBlocked = mergeBlockedTables(connectionConfig, agentAccess);
102
+ const mergedAllowed = mergeAllowedTables(connectionConfig, agentAccess);
103
+
104
+ for (const table of tables) {
105
+ if (mergedBlocked.has(table.toLowerCase())) {
106
+ return { allowed: false, operation, reason: `Access to table '${table}' is blocked` };
107
+ }
108
+ if (mergedAllowed.size > 0 && !mergedAllowed.has(table.toLowerCase())) {
109
+ return { allowed: false, operation, reason: `Table '${table}' is not in the allowed tables list` };
110
+ }
111
+ }
112
+
113
+ // 4. Check column access (for SELECT, INSERT, UPDATE)
114
+ const columns = extractColumnNames(sql);
115
+ const blockedColumns = new Set([
116
+ ...(connectionConfig.schemaAccess?.blockedColumns || []).map(c => c.toLowerCase()),
117
+ ...(agentAccess.schemaAccess?.blockedColumns || []).map(c => c.toLowerCase()),
118
+ ]);
119
+
120
+ for (const col of columns) {
121
+ if (blockedColumns.has(col.toLowerCase())) {
122
+ return { allowed: false, operation, reason: `Access to column '${col}' is blocked (sensitive data)` };
123
+ }
124
+ }
125
+
126
+ // 5. Add safety limits for read queries
127
+ let sanitizedQuery = sql.trim();
128
+ if (operation === 'read' && !sanitizedQuery.toUpperCase().includes('LIMIT')) {
129
+ const maxRows = agentAccess.queryLimits?.maxRowsRead
130
+ ?? connectionConfig.queryLimits?.maxRowsRead
131
+ ?? 10000;
132
+ sanitizedQuery = `${sanitizedQuery.replace(/;?\s*$/, '')} LIMIT ${maxRows}`;
133
+ }
134
+
135
+ return { allowed: true, operation, sanitizedQuery };
136
+ }
137
+
138
+ // ─── Table/Column Extraction ─────────────────────────────────────────────────
139
+
140
+ function extractTableNames(sql: string): string[] {
141
+ const tables = new Set<string>();
142
+
143
+ // FROM clause
144
+ const fromMatch = sql.match(/\bFROM\s+([`"[\]]?\w+[`"\]]?(?:\s*\.\s*[`"[\]]?\w+[`"\]]?)?)/gi);
145
+ if (fromMatch) {
146
+ for (const m of fromMatch) {
147
+ const name = m.replace(/^FROM\s+/i, '').replace(/[`"[\]]/g, '').trim();
148
+ tables.add(name.split('.').pop() || name);
149
+ }
150
+ }
151
+
152
+ // JOIN clause
153
+ const joinMatch = sql.match(/\bJOIN\s+([`"[\]]?\w+[`"\]]?(?:\s*\.\s*[`"[\]]?\w+[`"\]]?)?)/gi);
154
+ if (joinMatch) {
155
+ for (const m of joinMatch) {
156
+ const name = m.replace(/^JOIN\s+/i, '').replace(/[`"[\]]/g, '').trim();
157
+ tables.add(name.split('.').pop() || name);
158
+ }
159
+ }
160
+
161
+ // INSERT INTO
162
+ const insertMatch = sql.match(/\bINSERT\s+INTO\s+([`"[\]]?\w+[`"\]]?)/i);
163
+ if (insertMatch) tables.add(insertMatch[1].replace(/[`"[\]]/g, ''));
164
+
165
+ // UPDATE
166
+ const updateMatch = sql.match(/\bUPDATE\s+([`"[\]]?\w+[`"\]]?)/i);
167
+ if (updateMatch) tables.add(updateMatch[1].replace(/[`"[\]]/g, ''));
168
+
169
+ // DELETE FROM
170
+ const deleteMatch = sql.match(/\bDELETE\s+FROM\s+([`"[\]]?\w+[`"\]]?)/i);
171
+ if (deleteMatch) tables.add(deleteMatch[1].replace(/[`"[\]]/g, ''));
172
+
173
+ return [...tables];
174
+ }
175
+
176
+ function extractColumnNames(sql: string): string[] {
177
+ // Extract column names from SELECT, INSERT, UPDATE SET clauses
178
+ // This is best-effort — complex queries may not parse perfectly
179
+ const columns = new Set<string>();
180
+
181
+ // SELECT columns (between SELECT and FROM)
182
+ const selectMatch = sql.match(/\bSELECT\s+(.*?)\bFROM\b/is);
183
+ if (selectMatch && !selectMatch[1].includes('*')) {
184
+ const parts = selectMatch[1].split(',');
185
+ for (const p of parts) {
186
+ const col = p.trim().split(/\s+AS\s+/i)[0].trim().split('.').pop();
187
+ if (col && /^\w+$/.test(col)) columns.add(col);
188
+ }
189
+ }
190
+
191
+ return [...columns];
192
+ }
193
+
194
+ function mergeBlockedTables(config: DatabaseConnectionConfig, access: AgentDatabaseAccess): Set<string> {
195
+ return new Set([
196
+ ...(config.schemaAccess?.blockedTables || []).map(t => t.toLowerCase()),
197
+ ...(access.schemaAccess?.blockedTables || []).map(t => t.toLowerCase()),
198
+ ]);
199
+ }
200
+
201
+ function mergeAllowedTables(config: DatabaseConnectionConfig, access: AgentDatabaseAccess): Set<string> {
202
+ const connAllowed = config.schemaAccess?.allowedTables || [];
203
+ const agentAllowed = access.schemaAccess?.allowedTables || [];
204
+
205
+ // If agent has specific allowed tables, use those (most restrictive)
206
+ if (agentAllowed.length > 0) return new Set(agentAllowed.map(t => t.toLowerCase()));
207
+ if (connAllowed.length > 0) return new Set(connAllowed.map(t => t.toLowerCase()));
208
+ return new Set(); // Empty = all tables allowed
209
+ }
210
+
211
+ /**
212
+ * Sanitize a query string for safe logging (remove parameter values).
213
+ */
214
+ export function sanitizeForLogging(sql: string): string {
215
+ // Replace string literals
216
+ let sanitized = sql.replace(/'[^']*'/g, "'?'");
217
+ // Replace numeric literals after = or IN
218
+ sanitized = sanitized.replace(/=\s*\d+/g, '= ?');
219
+ return sanitized;
220
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Database Access — API Routes
3
+ *
4
+ * REST API for managing database connections, agent access, and query execution.
5
+ * All routes require authentication and org-level authorization.
6
+ */
7
+
8
+ import { Hono } from 'hono';
9
+ import type { DatabaseConnectionManager } from './connection-manager.js';
10
+ import type { DatabasePermission } from './types.js';
11
+
12
+ export function createDatabaseAccessRoutes(manager: DatabaseConnectionManager) {
13
+ const router = new Hono();
14
+
15
+ // ─── Connections ─────────────────────────────────────────────────────────
16
+
17
+ /** List all database connections for the org */
18
+ router.get('/connections', async (c) => {
19
+ const orgId = (c as any).get?.('orgId') || 'default';
20
+ const connections = manager.listConnections(orgId);
21
+ // Strip sensitive fields
22
+ const safe = connections.map(conn => ({
23
+ ...conn,
24
+ sslCaCert: conn.sslCaCert ? '***' : undefined,
25
+ sslClientCert: conn.sslClientCert ? '***' : undefined,
26
+ sslClientKey: conn.sslClientKey ? '***' : undefined,
27
+ }));
28
+ return c.json(safe);
29
+ });
30
+
31
+ /** Get single connection */
32
+ router.get('/connections/:id', async (c) => {
33
+ const conn = manager.getConnection(c.req.param('id'));
34
+ if (!conn) return c.json({ error: 'Connection not found' }, 404);
35
+ return c.json(conn);
36
+ });
37
+
38
+ /** Create a new database connection */
39
+ router.post('/connections', async (c) => {
40
+ const body = await c.req.json();
41
+ const orgId = (c as any).get?.('orgId') || 'default';
42
+
43
+ if (!body.name || !body.type) {
44
+ return c.json({ error: 'name and type are required' }, 400);
45
+ }
46
+
47
+ const connection = await manager.createConnection(
48
+ {
49
+ orgId,
50
+ name: body.name,
51
+ type: body.type,
52
+ host: body.host,
53
+ port: body.port,
54
+ database: body.database,
55
+ username: body.username,
56
+ ssl: body.ssl,
57
+ sslRejectUnauthorized: body.sslRejectUnauthorized,
58
+ sshTunnel: body.sshTunnel,
59
+ pool: body.pool,
60
+ queryLimits: body.queryLimits,
61
+ schemaAccess: body.schemaAccess,
62
+ description: body.description,
63
+ tags: body.tags,
64
+ status: 'inactive',
65
+ },
66
+ { password: body.password, connectionString: body.connectionString },
67
+ );
68
+
69
+ return c.json(connection, 201);
70
+ });
71
+
72
+ /** Update a database connection */
73
+ router.put('/connections/:id', async (c) => {
74
+ const body = await c.req.json();
75
+ const updated = await manager.updateConnection(
76
+ c.req.param('id'),
77
+ body,
78
+ { password: body.password, connectionString: body.connectionString },
79
+ );
80
+ if (!updated) return c.json({ error: 'Connection not found' }, 404);
81
+ return c.json(updated);
82
+ });
83
+
84
+ /** Delete a database connection */
85
+ router.delete('/connections/:id', async (c) => {
86
+ const deleted = await manager.deleteConnection(c.req.param('id'));
87
+ if (!deleted) return c.json({ error: 'Connection not found' }, 404);
88
+ return c.json({ ok: true });
89
+ });
90
+
91
+ /** Test a database connection */
92
+ router.post('/connections/:id/test', async (c) => {
93
+ const result = await manager.testConnection(c.req.param('id'));
94
+ return c.json(result);
95
+ });
96
+
97
+ /** Get connection pool stats */
98
+ router.get('/connections/:id/stats', async (c) => {
99
+ const stats = manager.getPoolStats(c.req.param('id'));
100
+ return c.json(stats);
101
+ });
102
+
103
+ // ─── Agent Access ──────────────────────────────────────────────────────────
104
+
105
+ /** List all agents with access to a connection */
106
+ router.get('/connections/:id/agents', async (c) => {
107
+ const agents = manager.getConnectionAgents(c.req.param('id'));
108
+ return c.json(agents);
109
+ });
110
+
111
+ /** Grant agent access to a connection */
112
+ router.post('/connections/:id/agents', async (c) => {
113
+ const body = await c.req.json();
114
+ const orgId = (c as any).get?.('orgId') || 'default';
115
+
116
+ if (!body.agentId) return c.json({ error: 'agentId is required' }, 400);
117
+
118
+ const access = await manager.grantAccess({
119
+ orgId,
120
+ agentId: body.agentId,
121
+ connectionId: c.req.param('id'),
122
+ permissions: body.permissions || ['read'],
123
+ queryLimits: body.queryLimits,
124
+ schemaAccess: body.schemaAccess,
125
+ logAllQueries: body.logAllQueries ?? false,
126
+ requireApproval: body.requireApproval ?? false,
127
+ enabled: true,
128
+ });
129
+
130
+ return c.json(access, 201);
131
+ });
132
+
133
+ /** Update agent access */
134
+ router.put('/connections/:connId/agents/:agentId', async (c) => {
135
+ const body = await c.req.json();
136
+ const updated = await manager.updateAccess(c.req.param('agentId'), c.req.param('connId'), body);
137
+ if (!updated) return c.json({ error: 'Access grant not found' }, 404);
138
+ return c.json(updated);
139
+ });
140
+
141
+ /** Revoke agent access */
142
+ router.delete('/connections/:connId/agents/:agentId', async (c) => {
143
+ await manager.revokeAccess(c.req.param('agentId'), c.req.param('connId'));
144
+ return c.json({ ok: true });
145
+ });
146
+
147
+ /** List all connections an agent has access to */
148
+ router.get('/agents/:agentId/connections', async (c) => {
149
+ const accessList = manager.getAgentAccess(c.req.param('agentId'));
150
+ const result = accessList.map(a => ({
151
+ ...a,
152
+ connection: manager.getConnection(a.connectionId),
153
+ }));
154
+ return c.json(result);
155
+ });
156
+
157
+ // ─── Query Execution ──────────────────────────────────────────────────────
158
+
159
+ /** Execute a query as an agent */
160
+ router.post('/query', async (c) => {
161
+ const body = await c.req.json();
162
+
163
+ if (!body.connectionId || !body.agentId || !body.sql) {
164
+ return c.json({ error: 'connectionId, agentId, and sql are required' }, 400);
165
+ }
166
+
167
+ const result = await manager.executeQuery({
168
+ connectionId: body.connectionId,
169
+ agentId: body.agentId,
170
+ operation: body.operation || 'read',
171
+ sql: body.sql,
172
+ params: body.params,
173
+ });
174
+
175
+ return c.json(result, result.success ? 200 : 403);
176
+ });
177
+
178
+ // ─── Audit Log ─────────────────────────────────────────────────────────────
179
+
180
+ /** Get audit log */
181
+ router.get('/audit', async (c) => {
182
+ const orgId = (c as any).get?.('orgId') || 'default';
183
+ const agentId = c.req.query('agentId');
184
+ const connectionId = c.req.query('connectionId');
185
+ const limit = parseInt(c.req.query('limit') || '100');
186
+ const offset = parseInt(c.req.query('offset') || '0');
187
+
188
+ const logs = await manager.getAuditLog({ orgId, agentId, connectionId, limit, offset });
189
+ return c.json(logs);
190
+ });
191
+
192
+ return router;
193
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Database Access System — Types
3
+ *
4
+ * Enterprise-grade database connectivity for AI agents.
5
+ * Supports: PostgreSQL, MySQL/MariaDB, SQLite, MongoDB, Redis,
6
+ * Microsoft SQL Server, Oracle, CockroachDB, PlanetScale, Turso/LibSQL,
7
+ * DynamoDB, Supabase, Neon, and any ODBC-compatible database.
8
+ */
9
+
10
+ // ─── Database Types ──────────────────────────────────────────────────────────
11
+
12
+ export type DatabaseType =
13
+ | 'postgresql'
14
+ | 'mysql'
15
+ | 'mariadb'
16
+ | 'sqlite'
17
+ | 'mongodb'
18
+ | 'redis'
19
+ | 'mssql'
20
+ | 'oracle'
21
+ | 'cockroachdb'
22
+ | 'planetscale'
23
+ | 'turso'
24
+ | 'dynamodb'
25
+ | 'supabase'
26
+ | 'neon';
27
+
28
+ export const DATABASE_LABELS: Record<DatabaseType, string> = {
29
+ postgresql: 'PostgreSQL',
30
+ mysql: 'MySQL',
31
+ mariadb: 'MariaDB',
32
+ sqlite: 'SQLite',
33
+ mongodb: 'MongoDB',
34
+ redis: 'Redis',
35
+ mssql: 'Microsoft SQL Server',
36
+ oracle: 'Oracle',
37
+ cockroachdb: 'CockroachDB',
38
+ planetscale: 'PlanetScale',
39
+ turso: 'Turso / LibSQL',
40
+ dynamodb: 'AWS DynamoDB',
41
+ supabase: 'Supabase',
42
+ neon: 'Neon',
43
+ };
44
+
45
+ export const DATABASE_CATEGORIES: Record<string, DatabaseType[]> = {
46
+ 'Relational (SQL)': ['postgresql', 'mysql', 'mariadb', 'mssql', 'oracle', 'sqlite'],
47
+ 'Cloud-Native SQL': ['supabase', 'neon', 'planetscale', 'cockroachdb', 'turso'],
48
+ 'NoSQL / Key-Value': ['mongodb', 'redis', 'dynamodb'],
49
+ };
50
+
51
+ // ─── Connection Configuration ────────────────────────────────────────────────
52
+
53
+ export interface DatabaseConnectionConfig {
54
+ id: string;
55
+ orgId: string;
56
+ name: string; // Human-friendly name: "Production DB", "Analytics Warehouse"
57
+ type: DatabaseType;
58
+
59
+ // Connection details (stored encrypted in vault)
60
+ host?: string;
61
+ port?: number;
62
+ database?: string;
63
+ username?: string;
64
+ // password stored in vault, never in this config
65
+
66
+ // Alternative: connection string (stored encrypted in vault)
67
+ // connectionString stored in vault
68
+
69
+ // SSL/TLS
70
+ ssl?: boolean;
71
+ sslCaCert?: string; // Vault reference
72
+ sslClientCert?: string; // Vault reference
73
+ sslClientKey?: string; // Vault reference
74
+ sslRejectUnauthorized?: boolean;
75
+
76
+ // SSH Tunnel
77
+ sshTunnel?: {
78
+ enabled: boolean;
79
+ host: string;
80
+ port?: number;
81
+ username: string;
82
+ // privateKey stored in vault
83
+ localPort?: number;
84
+ };
85
+
86
+ // Connection Pool
87
+ pool?: {
88
+ min?: number;
89
+ max?: number;
90
+ idleTimeoutMs?: number;
91
+ acquireTimeoutMs?: number;
92
+ };
93
+
94
+ // Query Safety
95
+ queryLimits?: {
96
+ maxRowsRead?: number; // Default: 10000
97
+ maxRowsWrite?: number; // Default: 1000
98
+ maxRowsDelete?: number; // Default: 100
99
+ queryTimeoutMs?: number; // Default: 30000
100
+ maxConcurrentQueries?: number; // Default: 5
101
+ };
102
+
103
+ // Schema restrictions
104
+ schemaAccess?: {
105
+ allowedSchemas?: string[]; // Empty = all schemas
106
+ allowedTables?: string[]; // Empty = all tables
107
+ blockedTables?: string[]; // Takes precedence over allowed
108
+ blockedColumns?: string[]; // Sensitive columns: "ssn", "password", etc.
109
+ };
110
+
111
+ // Metadata
112
+ description?: string;
113
+ tags?: string[];
114
+ status: 'active' | 'inactive' | 'error';
115
+ lastTestedAt?: string;
116
+ lastError?: string;
117
+ createdAt: string;
118
+ updatedAt: string;
119
+ }
120
+
121
+ // ─── Agent Access Control ────────────────────────────────────────────────────
122
+
123
+ export type DatabasePermission = 'read' | 'write' | 'delete' | 'schema' | 'execute';
124
+
125
+ export interface AgentDatabaseAccess {
126
+ id: string;
127
+ orgId: string;
128
+ agentId: string;
129
+ connectionId: string;
130
+
131
+ // Granular permissions
132
+ permissions: DatabasePermission[];
133
+
134
+ // Per-agent overrides (stricter than connection defaults)
135
+ queryLimits?: {
136
+ maxRowsRead?: number;
137
+ maxRowsWrite?: number;
138
+ maxRowsDelete?: number;
139
+ queryTimeoutMs?: number;
140
+ maxConcurrentQueries?: number;
141
+ maxQueriesPerMinute?: number; // Rate limiting
142
+ };
143
+
144
+ // Per-agent schema restrictions (further restricts connection-level)
145
+ schemaAccess?: {
146
+ allowedTables?: string[];
147
+ blockedTables?: string[];
148
+ blockedColumns?: string[];
149
+ };
150
+
151
+ // Audit settings
152
+ logAllQueries?: boolean; // Default: true for write/delete, false for read
153
+ requireApproval?: boolean; // Require human approval before write/delete
154
+
155
+ enabled: boolean;
156
+ createdAt: string;
157
+ updatedAt: string;
158
+ }
159
+
160
+ // ─── Query Types ─────────────────────────────────────────────────────────────
161
+
162
+ export interface DatabaseQuery {
163
+ connectionId: string;
164
+ agentId: string;
165
+ operation: 'read' | 'write' | 'delete' | 'schema' | 'execute';
166
+ sql?: string;
167
+ params?: any[];
168
+
169
+ // For NoSQL
170
+ collection?: string;
171
+ filter?: Record<string, any>;
172
+ update?: Record<string, any>;
173
+ document?: Record<string, any>;
174
+
175
+ // For Redis
176
+ command?: string;
177
+ args?: string[];
178
+ }
179
+
180
+ export interface QueryResult {
181
+ success: boolean;
182
+ rows?: any[];
183
+ rowCount?: number;
184
+ affectedRows?: number;
185
+ fields?: { name: string; type: string }[];
186
+ error?: string;
187
+ executionTimeMs: number;
188
+ truncated?: boolean; // True if rows were limited
189
+ queryId: string; // For audit trail
190
+ }
191
+
192
+ // ─── Audit Log ───────────────────────────────────────────────────────────────
193
+
194
+ export interface DatabaseAuditEntry {
195
+ id: string;
196
+ orgId: string;
197
+ agentId: string;
198
+ agentName?: string;
199
+ connectionId: string;
200
+ connectionName?: string;
201
+ operation: DatabasePermission;
202
+ query: string; // Sanitized query (no values)
203
+ paramCount: number;
204
+ rowsAffected: number;
205
+ executionTimeMs: number;
206
+ success: boolean;
207
+ error?: string;
208
+ ipAddress?: string;
209
+ timestamp: string;
210
+ }
211
+
212
+ // ─── Connection Pool Stats ───────────────────────────────────────────────────
213
+
214
+ export interface ConnectionPoolStats {
215
+ connectionId: string;
216
+ totalConnections: number;
217
+ activeConnections: number;
218
+ idleConnections: number;
219
+ waitingRequests: number;
220
+ queriesExecuted: number;
221
+ averageQueryTimeMs: number;
222
+ errorCount: number;
223
+ lastActivityAt: string;
224
+ }
@@ -295,6 +295,11 @@ engine.route('/knowledge-import', createKnowledgeImportRoutes(knowledgeImport));
295
295
  engine.route('/skill-updates', createSkillUpdaterRoutes(skillUpdater));
296
296
  engine.route('/oauth', createOAuthConnectRoutes(vault, lifecycle));
297
297
 
298
+ // Database Access system
299
+ import { DatabaseConnectionManager, createDatabaseAccessRoutes } from '../database-access/index.js';
300
+ const databaseManager = new DatabaseConnectionManager({ vault });
301
+ engine.route('/database', createDatabaseAccessRoutes(databaseManager));
302
+
298
303
  // ─── Hierarchy / Management API ─────────────────────────
299
304
  engine.get('/hierarchy/org-chart', async (c) => {
300
305
  if (!hierarchyManager) return c.json({ error: 'Hierarchy not initialized' }, 503);
@@ -752,6 +757,7 @@ export async function setEngineDb(
752
757
  storageManager.setDb(db),
753
758
  policyImporter.setDb(db),
754
759
  (async () => { (taskQueue as any).db = (db as any)?.db || db; await taskQueue.init(); })(),
760
+ databaseManager.setDb((db as any)?.db || db),
755
761
  ]);
756
762
  // Initialize hierarchy manager + start background task monitor
757
763
  hierarchyManager = new AgentHierarchyManager(db);
@@ -1026,4 +1032,4 @@ export function setRuntime(runtime: any): void {
1026
1032
  }
1027
1033
 
1028
1034
  export { engine as engineRoutes };
1029
- export { permissionEngine, configGen, deployer, approvals, lifecycle, knowledgeBase, tenants, activity, dlp, commBus, guardrails, journal, compliance, communityRegistry, workforce, policyEngine, memoryManager, onboarding, vault, storageManager, policyImporter, knowledgeContribution, skillUpdater, agentStatus, hierarchyManager };
1035
+ export { permissionEngine, configGen, deployer, approvals, lifecycle, knowledgeBase, tenants, activity, dlp, commBus, guardrails, journal, compliance, communityRegistry, workforce, policyEngine, memoryManager, onboarding, vault, storageManager, policyImporter, knowledgeContribution, skillUpdater, agentStatus, hierarchyManager, databaseManager };