godcrm/backend/utils/__tests__/sqlSanitizer.test.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

176 lines
5.6 KiB
JavaScript

// backend/utils/__tests__/sqlSanitizer.test.js
// SEC-001: SQL Sanitizer Tests - ADR-015
import { describe, it, expect } from 'vitest';
import {
validateTableName,
escapeIdentifier,
buildWhereClause,
buildInClause,
ALLOWED_TABLES
} from '../sqlSanitizer.js';
describe('SQL Sanitizer', () => {
describe('validateTableName', () => {
it('should accept valid table names', () => {
const validTables = [
'users',
'spaces',
'projects',
'dashboards',
'universal_tables',
'table_columns',
'table_rows',
'widgets',
'api_keys',
'webhooks',
'webhook_logs',
'files',
'folders',
'chat_threads',
'chat_participants',
'chat_messages',
'audit_log',
'system_settings',
'user_settings',
'data_sources',
'sync_logs',
'schema_layouts',
'monitoring_runs',
'monitoring_events'
];
for (const table of validTables) {
expect(() => validateTableName(table)).not.toThrow();
expect(validateTableName(table)).toBe(table);
}
});
it('should reject invalid table names', () => {
const invalidTables = [
'nonexistent_table',
'users; DROP TABLE users;--',
"users' OR '1'='1",
'users UNION SELECT * FROM passwords',
'../../../etc/passwd',
'',
null,
undefined,
123,
'SELECT * FROM users',
'users--',
'users/*',
'table"name'
];
for (const table of invalidTables) {
expect(() => validateTableName(table)).toThrow(/Invalid table name/);
}
});
});
describe('escapeIdentifier', () => {
it('should escape valid column/table names', () => {
expect(escapeIdentifier('column_name')).toBe('"column_name"');
expect(escapeIdentifier('TableName')).toBe('"TableName"');
expect(escapeIdentifier('_private')).toBe('"_private"');
expect(escapeIdentifier('column123')).toBe('"column123"');
});
it('should escape double quotes in identifiers', () => {
expect(escapeIdentifier('col"name')).toBe('"col""name"');
expect(escapeIdentifier('a"b"c')).toBe('"a""b""c"');
});
it('should reject dangerous identifiers', () => {
expect(() => escapeIdentifier('col; DROP TABLE')).toThrow();
expect(() => escapeIdentifier("col' OR '1'='1")).toThrow();
expect(() => escapeIdentifier('col--comment')).toThrow();
expect(() => escapeIdentifier('123start')).toThrow();
expect(() => escapeIdentifier('')).toThrow();
expect(() => escapeIdentifier(null)).toThrow();
expect(() => escapeIdentifier(undefined)).toThrow();
expect(() => escapeIdentifier('col/*comment*/')).toThrow();
});
});
describe('buildWhereClause', () => {
it('should build parameterized WHERE clause', () => {
const filters = { status: 'active', type: 'user' };
const { clause, params } = buildWhereClause(filters, ['status', 'type']);
expect(clause).toBe('WHERE "status" = ? AND "type" = ?');
expect(params).toEqual(['active', 'user']);
});
it('should handle empty filters', () => {
const { clause, params } = buildWhereClause({}, ['status']);
expect(clause).toBe('');
expect(params).toEqual([]);
});
it('should ignore non-whitelisted fields', () => {
const filters = { status: 'active', injected: 'DROP TABLE users' };
const { clause, params } = buildWhereClause(filters, ['status']);
expect(clause).toBe('WHERE "status" = ?');
expect(params).toEqual(['active']);
expect(params).not.toContain('DROP TABLE users');
});
it('should handle null and undefined values', () => {
const filters = { status: 'active', type: null, name: undefined };
const { clause, params } = buildWhereClause(filters, ['status', 'type', 'name']);
expect(clause).toBe('WHERE "status" = ?');
expect(params).toEqual(['active']);
});
it('should handle null/undefined filters object', () => {
expect(buildWhereClause(null, ['status'])).toEqual({ clause: '', params: [] });
expect(buildWhereClause(undefined, ['status'])).toEqual({ clause: '', params: [] });
});
});
describe('buildInClause', () => {
it('should build IN clause with placeholders', () => {
const { clause, params } = buildInClause('id', [1, 2, 3]);
expect(clause).toBe('"id" IN (?, ?, ?)');
expect(params).toEqual([1, 2, 3]);
});
it('should handle single value', () => {
const { clause, params } = buildInClause('status', ['active']);
expect(clause).toBe('"status" IN (?)');
expect(params).toEqual(['active']);
});
it('should reject empty array', () => {
expect(() => buildInClause('id', [])).toThrow('Values must be a non-empty array');
});
it('should reject non-array values', () => {
expect(() => buildInClause('id', null)).toThrow();
expect(() => buildInClause('id', 'value')).toThrow();
});
});
describe('ALLOWED_TABLES constant', () => {
it('should be a Set', () => {
expect(ALLOWED_TABLES).toBeInstanceOf(Set);
});
it('should contain core tables', () => {
expect(ALLOWED_TABLES.has('users')).toBe(true);
expect(ALLOWED_TABLES.has('spaces')).toBe(true);
expect(ALLOWED_TABLES.has('projects')).toBe(true);
});
it('should not contain dangerous values', () => {
expect(ALLOWED_TABLES.has('DROP')).toBe(false);
expect(ALLOWED_TABLES.has('; --')).toBe(false);
});
});
});