Skip to content

Commit 2a5389f

Browse files
author
Ope Olatunji
committed
Fix db_list_tables blocked by own sanitizer + add security_overrides column
Two fixes: 1. db_list_tables and db_describe_table were blocked by the query sanitizer because they use information_schema queries, which matched the BLOCKED_PATTERNS list. Added _trusted flag to DatabaseQuery so internal system tools bypass the sanitizer while user queries still get checked. 2. Agent security settings save failed with "column security_overrides does not exist" — the column was never added via migration. Added ALTER TABLE migration for Postgres and SQLite. Updated updateAgent() and mapAgent() in all 6 adapters (postgres, sqlite, mysql, turso, mongodb, dynamodb).
1 parent 07d4abc commit 2a5389f

File tree

11 files changed

+33
-15
lines changed

11 files changed

+33
-15
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@agenticmail/enterprise",
3-
"version": "0.5.508",
3+
"version": "0.5.510",
44
"description": "AgenticMail Enterprise — cloud-hosted AI agent identity, email, auth & compliance for organizations",
55
"type": "module",
66
"bin": {

src/database-access/agent-tools.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export function createDatabaseTools(manager: DatabaseConnectionManager, agentId:
136136
}
137137

138138
try {
139-
const result = await manager.executeQuery({ connectionId: input.connectionId, agentId, operation: 'read', sql });
139+
const result = await manager.executeQuery({ connectionId: input.connectionId, agentId, operation: 'read', sql, _trusted: true });
140140
if (!result.success) return errorResult(result.error || 'Query failed');
141141
return jsonResult({ columns: result.rows, table: input.table });
142142
} catch (e: any) {
@@ -180,7 +180,7 @@ export function createDatabaseTools(manager: DatabaseConnectionManager, agentId:
180180
}
181181

182182
try {
183-
const result = await manager.executeQuery({ connectionId: input.connectionId, agentId, operation: 'read', sql });
183+
const result = await manager.executeQuery({ connectionId: input.connectionId, agentId, operation: 'read', sql, _trusted: true });
184184
if (!result.success) return errorResult(result.error || 'Query failed');
185185
return jsonResult({ tables: result.rows });
186186
} catch (e: any) {

src/database-access/connection-manager.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -468,14 +468,21 @@ export class DatabaseConnectionManager {
468468
return { success: false, error: 'No SQL query provided', executionTimeMs: 0, queryId };
469469
}
470470

471-
const sanitizeResult = sanitizeQuery(query.sql, access.permissions, config, access);
472-
if (!sanitizeResult.allowed) {
473-
await this.logAudit(query, config, access, 'read', 0, false, sanitizeResult.reason || 'Query blocked', startMs, queryId);
474-
return { success: false, error: sanitizeResult.reason, executionTimeMs: Date.now() - startMs, queryId };
471+
let finalSql = query.sql;
472+
let sanitizeResult: { allowed: boolean; operation: string; reason?: string; sanitizedQuery?: string } | undefined;
473+
474+
if (query._trusted) {
475+
// Internal system tools (db_list_tables, db_describe_table) bypass sanitizer
476+
finalSql = query.sql;
477+
} else {
478+
sanitizeResult = sanitizeQuery(query.sql, access.permissions, config, access);
479+
if (!sanitizeResult.allowed) {
480+
await this.logAudit(query, config, access, 'read', 0, false, sanitizeResult.reason || 'Query blocked', startMs, queryId);
481+
return { success: false, error: sanitizeResult.reason, executionTimeMs: Date.now() - startMs, queryId };
482+
}
483+
finalSql = sanitizeResult.sanitizedQuery || query.sql;
475484
}
476485

477-
const finalSql = sanitizeResult.sanitizedQuery || query.sql;
478-
479486
// 4. Check concurrent query limit
480487
const _maxConcurrent = access.queryLimits?.maxConcurrentQueries ?? config.queryLimits?.maxConcurrentQueries ?? 5;
481488
// (In production, track active queries per connection — simplified here)

src/database-access/types.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,16 +167,19 @@ export interface DatabaseQuery {
167167
operation: 'read' | 'write' | 'delete' | 'schema' | 'execute';
168168
sql?: string;
169169
params?: any[];
170-
170+
171171
// For NoSQL
172172
collection?: string;
173173
filter?: Record<string, any>;
174174
update?: Record<string, any>;
175175
document?: Record<string, any>;
176-
176+
177177
// For Redis
178178
command?: string;
179179
args?: string[];
180+
181+
/** Skip query sanitizer — only for internal system tools (db_list_tables, db_describe_table) */
182+
_trusted?: boolean;
180183
}
181184

182185
export interface QueryResult {

src/db/dynamodb.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ export class DynamoAdapter extends DatabaseAdapter {
210210
const current = await this.getItem(pk('AGENT'), id);
211211
if (!current) throw new Error('Agent not found');
212212
const merged = { ...current, updatedAt: new Date().toISOString() };
213-
for (const key of ['name', 'email', 'role', 'status', 'metadata']) {
213+
for (const key of ['name', 'email', 'role', 'status', 'metadata', 'securityOverrides']) {
214214
if ((updates as any)[key] !== undefined) merged[key] = (updates as any)[key];
215215
}
216216
if (updates.name) { merged.GSI1SK = updates.name; }

src/db/mongodb.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export class MongoAdapter extends DatabaseAdapter {
129129

130130
async updateAgent(id: string, updates: Partial<Agent>): Promise<Agent> {
131131
const set: any = { updatedAt: new Date() };
132-
for (const key of ['name', 'email', 'role', 'status', 'metadata']) {
132+
for (const key of ['name', 'email', 'role', 'status', 'metadata', 'securityOverrides']) {
133133
if ((updates as any)[key] !== undefined) set[key] = (updates as any)[key];
134134
}
135135
await this.col('agents').updateOne({ _id: id }, { $set: set });

src/db/mysql.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ export class MysqlAdapter extends DatabaseAdapter {
226226
if ((updates as any)[key] !== undefined) { fields.push(`${col} = ?`); vals.push((updates as any)[key]); }
227227
}
228228
if (updates.metadata) { fields.push('metadata = ?'); vals.push(JSON.stringify(updates.metadata)); }
229+
if ((updates as any).securityOverrides !== undefined) { fields.push('security_overrides = ?'); vals.push(JSON.stringify((updates as any).securityOverrides)); }
229230
if (fields.length === 0) return (await this.getAgent(id))!;
230231
fields.push('updated_at = NOW()');
231232
vals.push(id);
@@ -475,6 +476,7 @@ export class MysqlAdapter extends DatabaseAdapter {
475476
return {
476477
id: r.id, name: r.name, email: r.email, role: r.role, status: r.status,
477478
metadata: typeof r.metadata === 'string' ? JSON.parse(r.metadata) : (r.metadata || {}),
479+
securityOverrides: r.security_overrides ? (typeof r.security_overrides === 'string' ? JSON.parse(r.security_overrides) : r.security_overrides) : undefined,
478480
createdAt: new Date(r.created_at), updatedAt: new Date(r.updated_at), createdBy: r.created_by,
479481
};
480482
}

src/db/postgres.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ export class PostgresAdapter extends DatabaseAdapter {
247247
ALTER TABLE users ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT TRUE;
248248
ALTER TABLE users ADD COLUMN IF NOT EXISTS client_org_id TEXT;
249249
ALTER TABLE agents ADD COLUMN IF NOT EXISTS billing_rate NUMERIC(10,2) DEFAULT 0;
250+
ALTER TABLE agents ADD COLUMN IF NOT EXISTS security_overrides JSONB;
250251
`).catch(() => {});
251252
// ─── Client Organizations ────────────────────────────
252253
await client.query(`

src/db/sqlite.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export class SqliteAdapter extends DatabaseAdapter {
6565
try { this.db.exec(`ALTER TABLE users ADD COLUMN is_active INTEGER DEFAULT 1`); } catch { /* exists */ }
6666
try { this.db.exec(`ALTER TABLE users ADD COLUMN client_org_id TEXT`); } catch { /* exists */ }
6767
try { this.db.exec(`ALTER TABLE agents ADD COLUMN billing_rate REAL DEFAULT 0`); } catch { /* exists */ }
68+
try { this.db.exec(`ALTER TABLE agents ADD COLUMN security_overrides TEXT`); } catch { /* exists */ }
6869
// ─── Client Organizations ────────────────────────────
6970
this.db.exec(`
7071
CREATE TABLE IF NOT EXISTS client_organizations (
@@ -212,6 +213,7 @@ export class SqliteAdapter extends DatabaseAdapter {
212213
if ((updates as any)[key] !== undefined) { fields.push(`${col} = ?`); vals.push((updates as any)[key]); }
213214
}
214215
if (updates.metadata) { fields.push('metadata = ?'); vals.push(JSON.stringify(updates.metadata)); }
216+
if ((updates as any).securityOverrides !== undefined) { fields.push('security_overrides = ?'); vals.push(JSON.stringify((updates as any).securityOverrides)); }
215217
fields.push("updated_at = datetime('now')");
216218
vals.push(id);
217219
this.db.prepare(`UPDATE agents SET ${fields.join(', ')} WHERE id = ?`).run(...vals);
@@ -432,6 +434,7 @@ export class SqliteAdapter extends DatabaseAdapter {
432434
return {
433435
id: r.id, name: r.name, email: r.email, role: r.role, status: r.status,
434436
metadata: typeof r.metadata === 'string' ? JSON.parse(r.metadata) : r.metadata,
437+
securityOverrides: r.security_overrides ? (typeof r.security_overrides === 'string' ? JSON.parse(r.security_overrides) : r.security_overrides) : undefined,
435438
createdAt: new Date(r.created_at), updatedAt: new Date(r.updated_at), createdBy: r.created_by,
436439
client_org_id: r.client_org_id || null,
437440
};

0 commit comments

Comments
 (0)