memorisdk 1.0.2__py3-none-any.whl → 2.0.0__py3-none-any.whl

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.

Potentially problematic release.


This version of memorisdk might be problematic. Click here for more details.

Files changed (46) hide show
  1. memori/__init__.py +24 -8
  2. memori/agents/conscious_agent.py +252 -414
  3. memori/agents/memory_agent.py +487 -224
  4. memori/agents/retrieval_agent.py +416 -60
  5. memori/config/memory_manager.py +323 -0
  6. memori/core/conversation.py +393 -0
  7. memori/core/database.py +386 -371
  8. memori/core/memory.py +1638 -531
  9. memori/core/providers.py +217 -0
  10. memori/database/adapters/__init__.py +10 -0
  11. memori/database/adapters/mysql_adapter.py +331 -0
  12. memori/database/adapters/postgresql_adapter.py +291 -0
  13. memori/database/adapters/sqlite_adapter.py +229 -0
  14. memori/database/auto_creator.py +320 -0
  15. memori/database/connection_utils.py +207 -0
  16. memori/database/connectors/base_connector.py +283 -0
  17. memori/database/connectors/mysql_connector.py +240 -18
  18. memori/database/connectors/postgres_connector.py +277 -4
  19. memori/database/connectors/sqlite_connector.py +178 -3
  20. memori/database/models.py +400 -0
  21. memori/database/queries/base_queries.py +1 -1
  22. memori/database/queries/memory_queries.py +91 -2
  23. memori/database/query_translator.py +222 -0
  24. memori/database/schema_generators/__init__.py +7 -0
  25. memori/database/schema_generators/mysql_schema_generator.py +215 -0
  26. memori/database/search/__init__.py +8 -0
  27. memori/database/search/mysql_search_adapter.py +255 -0
  28. memori/database/search/sqlite_search_adapter.py +180 -0
  29. memori/database/search_service.py +548 -0
  30. memori/database/sqlalchemy_manager.py +839 -0
  31. memori/integrations/__init__.py +36 -11
  32. memori/integrations/litellm_integration.py +340 -6
  33. memori/integrations/openai_integration.py +506 -240
  34. memori/utils/input_validator.py +395 -0
  35. memori/utils/pydantic_models.py +138 -36
  36. memori/utils/query_builder.py +530 -0
  37. memori/utils/security_audit.py +594 -0
  38. memori/utils/security_integration.py +339 -0
  39. memori/utils/transaction_manager.py +547 -0
  40. {memorisdk-1.0.2.dist-info → memorisdk-2.0.0.dist-info}/METADATA +44 -17
  41. memorisdk-2.0.0.dist-info/RECORD +67 -0
  42. memorisdk-1.0.2.dist-info/RECORD +0 -44
  43. memorisdk-1.0.2.dist-info/entry_points.txt +0 -2
  44. {memorisdk-1.0.2.dist-info → memorisdk-2.0.0.dist-info}/WHEEL +0 -0
  45. {memorisdk-1.0.2.dist-info → memorisdk-2.0.0.dist-info}/licenses/LICENSE +0 -0
  46. {memorisdk-1.0.2.dist-info → memorisdk-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,594 @@
1
+ """
2
+ Security audit service for Memori database operations
3
+ Provides comprehensive security validation and monitoring
4
+ """
5
+
6
+ import re
7
+ import time
8
+ from dataclasses import dataclass
9
+ from enum import Enum
10
+ from typing import Any, Dict, List, Optional, Tuple
11
+
12
+ from .exceptions import SecurityError
13
+ from .input_validator import InputValidator
14
+
15
+
16
+ class SecurityLevel(str, Enum):
17
+ """Security severity levels"""
18
+
19
+ CRITICAL = "critical"
20
+ HIGH = "high"
21
+ MEDIUM = "medium"
22
+ LOW = "low"
23
+ INFO = "info"
24
+
25
+
26
+ class VulnerabilityType(str, Enum):
27
+ """Types of security vulnerabilities"""
28
+
29
+ SQL_INJECTION = "sql_injection"
30
+ XSS = "xss"
31
+ COMMAND_INJECTION = "command_injection"
32
+ PATH_TRAVERSAL = "path_traversal"
33
+ PRIVILEGE_ESCALATION = "privilege_escalation"
34
+ DATA_EXPOSURE = "data_exposure"
35
+ WEAK_VALIDATION = "weak_validation"
36
+
37
+
38
+ @dataclass
39
+ class SecurityFinding:
40
+ """Represents a security finding"""
41
+
42
+ vulnerability_type: VulnerabilityType
43
+ severity: SecurityLevel
44
+ description: str
45
+ location: str
46
+ recommendation: str
47
+ evidence: Optional[str] = None
48
+ remediation_code: Optional[str] = None
49
+
50
+
51
+ @dataclass
52
+ class SecurityAuditReport:
53
+ """Security audit report"""
54
+
55
+ findings: List[SecurityFinding]
56
+ total_queries_audited: int
57
+ critical_count: int
58
+ high_count: int
59
+ medium_count: int
60
+ low_count: int
61
+ audit_timestamp: float
62
+ overall_risk_score: float
63
+
64
+
65
+ class DatabaseSecurityAuditor:
66
+ """Comprehensive security auditor for database operations"""
67
+
68
+ # Advanced SQL injection patterns
69
+ SQL_INJECTION_PATTERNS = [
70
+ # Union-based injection
71
+ r"\bunion\s+select\b",
72
+ r"\bunion\s+all\s+select\b",
73
+ # Boolean-based injection
74
+ r"\b(and|or)\s+\d+\s*=\s*\d+",
75
+ r"\b(and|or)\s+.+\s*(like|=)\s*.+",
76
+ # Time-based injection
77
+ r"\bwaitfor\s+delay\b",
78
+ r"\bsleep\s*\(",
79
+ r"\bbenchmark\s*\(",
80
+ r"\bpg_sleep\s*\(",
81
+ # Comment-based injection
82
+ r"--\s*[^\r\n]*",
83
+ r"/\*.*?\*/",
84
+ r"\#[^\r\n]*",
85
+ # Stacked queries
86
+ r";\s*(select|insert|update|delete|drop|create|alter)",
87
+ # Information gathering
88
+ r"\binformation_schema\b",
89
+ r"\bsys\.|sysobjects|syscolumns",
90
+ r"\bmysql\.user\b",
91
+ r"\bpg_tables\b",
92
+ # Dangerous functions
93
+ r"\bxp_cmdshell\b",
94
+ r"\bsp_executesql\b",
95
+ r"\bexec\s*\(",
96
+ r"\beval\s*\(",
97
+ # File operations
98
+ r"\binto\s+outfile\b",
99
+ r"\binto\s+dumpfile\b",
100
+ r"\bload_file\s*\(",
101
+ r"\bcopy\s+.*\bfrom\b",
102
+ ]
103
+
104
+ # XSS patterns
105
+ XSS_PATTERNS = [
106
+ r"<script[^>]*>.*?</script>",
107
+ r"<iframe[^>]*>.*?</iframe>",
108
+ r"<object[^>]*>.*?</object>",
109
+ r"<embed[^>]*>",
110
+ r"<applet[^>]*>.*?</applet>",
111
+ r"javascript\s*:",
112
+ r"on\w+\s*=",
113
+ r"expression\s*\(",
114
+ r"url\s*\(",
115
+ r"@import",
116
+ ]
117
+
118
+ # Command injection patterns
119
+ COMMAND_INJECTION_PATTERNS = [
120
+ r"[;&|`$]",
121
+ r"\$\([^)]+\)",
122
+ r"`[^`]+`",
123
+ r"\|\s*(cat|ls|pwd|whoami|id|uname)",
124
+ r"(^|\s)(wget|curl|nc|netcat|telnet|ssh)\s",
125
+ r"\b(rm|mv|cp|chmod|chown|kill)\s",
126
+ ]
127
+
128
+ # Path traversal patterns
129
+ PATH_TRAVERSAL_PATTERNS = [
130
+ r"\.\./",
131
+ r"\.\.\\",
132
+ r"%2e%2e%2f",
133
+ r"%2e%2e\\",
134
+ r"..%252f",
135
+ r"..%255c",
136
+ ]
137
+
138
+ def __init__(self):
139
+ self.findings = []
140
+ self.queries_audited = 0
141
+ self.blocked_operations = set()
142
+
143
+ def audit_query(
144
+ self,
145
+ query: str,
146
+ params: Optional[List[Any]] = None,
147
+ context: Optional[str] = None,
148
+ ) -> List[SecurityFinding]:
149
+ """Audit a single database query for security vulnerabilities"""
150
+ findings = []
151
+ self.queries_audited += 1
152
+
153
+ # Audit SQL injection
154
+ sql_findings = self._audit_sql_injection(query, params, context)
155
+ findings.extend(sql_findings)
156
+
157
+ # Audit parameter validation
158
+ param_findings = self._audit_parameter_validation(query, params, context)
159
+ findings.extend(param_findings)
160
+
161
+ # Audit privilege operations
162
+ privilege_findings = self._audit_privilege_operations(query, context)
163
+ findings.extend(privilege_findings)
164
+
165
+ # Audit data exposure
166
+ exposure_findings = self._audit_data_exposure(query, context)
167
+ findings.extend(exposure_findings)
168
+
169
+ return findings
170
+
171
+ def _audit_sql_injection(
172
+ self, query: str, params: Optional[List[Any]], context: Optional[str]
173
+ ) -> List[SecurityFinding]:
174
+ """Audit for SQL injection vulnerabilities"""
175
+ findings = []
176
+ query_lower = query.lower()
177
+
178
+ # Check for dangerous patterns
179
+ for pattern in self.SQL_INJECTION_PATTERNS:
180
+ matches = re.finditer(pattern, query_lower, re.IGNORECASE | re.MULTILINE)
181
+ for match in matches:
182
+ findings.append(
183
+ SecurityFinding(
184
+ vulnerability_type=VulnerabilityType.SQL_INJECTION,
185
+ severity=SecurityLevel.CRITICAL,
186
+ description=f"Potential SQL injection pattern detected: {pattern}",
187
+ location=context or "unknown",
188
+ recommendation="Use parameterized queries and input validation",
189
+ evidence=f"Match: '{match.group()}' at position {match.start()}",
190
+ remediation_code="Use ? or %s placeholders with parameter binding",
191
+ )
192
+ )
193
+
194
+ # Check for string concatenation in queries
195
+ if any(char in query for char in ["+", "||"]) and "VALUES" in query.upper():
196
+ findings.append(
197
+ SecurityFinding(
198
+ vulnerability_type=VulnerabilityType.SQL_INJECTION,
199
+ severity=SecurityLevel.HIGH,
200
+ description="String concatenation detected in SQL query",
201
+ location=context or "unknown",
202
+ recommendation="Use parameterized queries instead of string concatenation",
203
+ evidence="Query contains string concatenation operators",
204
+ )
205
+ )
206
+
207
+ # Check if parameters are properly used
208
+ if params is None and any(var in query_lower for var in ["%s", "?"]):
209
+ findings.append(
210
+ SecurityFinding(
211
+ vulnerability_type=VulnerabilityType.WEAK_VALIDATION,
212
+ severity=SecurityLevel.MEDIUM,
213
+ description="Query has parameter placeholders but no parameters provided",
214
+ location=context or "unknown",
215
+ recommendation="Ensure all parameter placeholders have corresponding values",
216
+ )
217
+ )
218
+
219
+ return findings
220
+
221
+ def _audit_parameter_validation(
222
+ self, query: str, params: Optional[List[Any]], context: Optional[str]
223
+ ) -> List[SecurityFinding]:
224
+ """Audit parameter validation"""
225
+ findings = []
226
+
227
+ if params:
228
+ for i, param in enumerate(params):
229
+ # Check for potential XSS in parameters
230
+ if isinstance(param, str):
231
+ for pattern in self.XSS_PATTERNS:
232
+ if re.search(pattern, param, re.IGNORECASE):
233
+ findings.append(
234
+ SecurityFinding(
235
+ vulnerability_type=VulnerabilityType.XSS,
236
+ severity=SecurityLevel.HIGH,
237
+ description=f"XSS pattern in parameter {i}",
238
+ location=context or "unknown",
239
+ recommendation="Sanitize and validate input parameters",
240
+ evidence=f"Parameter value: {param[:100]}...",
241
+ )
242
+ )
243
+
244
+ # Check for command injection
245
+ for pattern in self.COMMAND_INJECTION_PATTERNS:
246
+ if re.search(pattern, param):
247
+ findings.append(
248
+ SecurityFinding(
249
+ vulnerability_type=VulnerabilityType.COMMAND_INJECTION,
250
+ severity=SecurityLevel.CRITICAL,
251
+ description=f"Command injection pattern in parameter {i}",
252
+ location=context or "unknown",
253
+ recommendation="Validate and sanitize all user input",
254
+ evidence=f"Parameter value: {param[:100]}...",
255
+ )
256
+ )
257
+
258
+ # Check for path traversal
259
+ for pattern in self.PATH_TRAVERSAL_PATTERNS:
260
+ if re.search(pattern, param):
261
+ findings.append(
262
+ SecurityFinding(
263
+ vulnerability_type=VulnerabilityType.PATH_TRAVERSAL,
264
+ severity=SecurityLevel.HIGH,
265
+ description=f"Path traversal pattern in parameter {i}",
266
+ location=context or "unknown",
267
+ recommendation="Validate file paths and restrict access",
268
+ evidence=f"Parameter value: {param[:100]}...",
269
+ )
270
+ )
271
+
272
+ # Check parameter size
273
+ if isinstance(param, str) and len(param) > 10000:
274
+ findings.append(
275
+ SecurityFinding(
276
+ vulnerability_type=VulnerabilityType.DATA_EXPOSURE,
277
+ severity=SecurityLevel.MEDIUM,
278
+ description=f"Unusually large parameter {i} ({len(param)} chars)",
279
+ location=context or "unknown",
280
+ recommendation="Implement input length limits",
281
+ evidence=f"Parameter length: {len(param)} characters",
282
+ )
283
+ )
284
+
285
+ return findings
286
+
287
+ def _audit_privilege_operations(
288
+ self, query: str, context: Optional[str]
289
+ ) -> List[SecurityFinding]:
290
+ """Audit for privilege escalation attempts"""
291
+ findings = []
292
+ query_upper = query.upper().strip()
293
+
294
+ # Dangerous DDL operations
295
+ dangerous_ddl = ["DROP", "CREATE USER", "ALTER USER", "GRANT", "REVOKE"]
296
+ for operation in dangerous_ddl:
297
+ if query_upper.startswith(operation):
298
+ findings.append(
299
+ SecurityFinding(
300
+ vulnerability_type=VulnerabilityType.PRIVILEGE_ESCALATION,
301
+ severity=SecurityLevel.CRITICAL,
302
+ description=f"Dangerous DDL operation: {operation}",
303
+ location=context or "unknown",
304
+ recommendation="Restrict DDL operations to administrative contexts",
305
+ evidence=f"Query starts with: {operation}",
306
+ )
307
+ )
308
+
309
+ # System function calls
310
+ system_functions = [
311
+ "SYSTEM",
312
+ "SHELL",
313
+ "EXEC",
314
+ "EXECUTE",
315
+ "XP_CMDSHELL",
316
+ "SP_EXECUTESQL",
317
+ "OPENROWSET",
318
+ "OPENDATASOURCE",
319
+ ]
320
+
321
+ for func in system_functions:
322
+ if func in query_upper:
323
+ findings.append(
324
+ SecurityFinding(
325
+ vulnerability_type=VulnerabilityType.PRIVILEGE_ESCALATION,
326
+ severity=SecurityLevel.CRITICAL,
327
+ description=f"System function call detected: {func}",
328
+ location=context or "unknown",
329
+ recommendation="Avoid system function calls in application queries",
330
+ evidence=f"Function: {func}",
331
+ )
332
+ )
333
+
334
+ return findings
335
+
336
+ def _audit_data_exposure(
337
+ self, query: str, context: Optional[str]
338
+ ) -> List[SecurityFinding]:
339
+ """Audit for potential data exposure issues"""
340
+ findings = []
341
+ query_upper = query.upper()
342
+
343
+ # SELECT * queries (potential over-exposure)
344
+ if re.search(r"SELECT\s+\*", query_upper):
345
+ findings.append(
346
+ SecurityFinding(
347
+ vulnerability_type=VulnerabilityType.DATA_EXPOSURE,
348
+ severity=SecurityLevel.MEDIUM,
349
+ description="SELECT * query may expose unnecessary data",
350
+ location=context or "unknown",
351
+ recommendation="Specify exact columns needed instead of using *",
352
+ evidence="Query uses SELECT *",
353
+ )
354
+ )
355
+
356
+ # Missing WHERE clauses in UPDATE/DELETE
357
+ if query_upper.startswith(("UPDATE", "DELETE")) and "WHERE" not in query_upper:
358
+ findings.append(
359
+ SecurityFinding(
360
+ vulnerability_type=VulnerabilityType.DATA_EXPOSURE,
361
+ severity=SecurityLevel.CRITICAL,
362
+ description="UPDATE/DELETE without WHERE clause",
363
+ location=context or "unknown",
364
+ recommendation="Always include WHERE clause in UPDATE/DELETE operations",
365
+ evidence="Missing WHERE clause",
366
+ )
367
+ )
368
+
369
+ # Potential sensitive data in queries
370
+ sensitive_keywords = [
371
+ "PASSWORD",
372
+ "TOKEN",
373
+ "SECRET",
374
+ "KEY",
375
+ "PRIVATE",
376
+ "SSN",
377
+ "SOCIAL_SECURITY",
378
+ "CREDIT_CARD",
379
+ "CVV",
380
+ ]
381
+
382
+ for keyword in sensitive_keywords:
383
+ if keyword in query_upper:
384
+ findings.append(
385
+ SecurityFinding(
386
+ vulnerability_type=VulnerabilityType.DATA_EXPOSURE,
387
+ severity=SecurityLevel.HIGH,
388
+ description=f"Query references potentially sensitive data: {keyword}",
389
+ location=context or "unknown",
390
+ recommendation="Ensure sensitive data is properly protected and encrypted",
391
+ evidence=f"Keyword: {keyword}",
392
+ )
393
+ )
394
+
395
+ return findings
396
+
397
+ def validate_query_safety(
398
+ self,
399
+ query: str,
400
+ params: Optional[List[Any]] = None,
401
+ context: Optional[str] = None,
402
+ strict_mode: bool = True,
403
+ ) -> Tuple[bool, List[SecurityFinding]]:
404
+ """Validate if a query is safe to execute"""
405
+ findings = self.audit_query(query, params, context)
406
+
407
+ # Determine if query should be blocked
408
+ has_critical = any(f.severity == SecurityLevel.CRITICAL for f in findings)
409
+ has_high = any(f.severity == SecurityLevel.HIGH for f in findings)
410
+
411
+ is_safe = True
412
+ if strict_mode:
413
+ is_safe = not (has_critical or has_high)
414
+ else:
415
+ is_safe = not has_critical
416
+
417
+ return is_safe, findings
418
+
419
+ def generate_audit_report(self) -> SecurityAuditReport:
420
+ """Generate comprehensive security audit report"""
421
+ critical_count = sum(
422
+ 1 for f in self.findings if f.severity == SecurityLevel.CRITICAL
423
+ )
424
+ high_count = sum(1 for f in self.findings if f.severity == SecurityLevel.HIGH)
425
+ medium_count = sum(
426
+ 1 for f in self.findings if f.severity == SecurityLevel.MEDIUM
427
+ )
428
+ low_count = sum(1 for f in self.findings if f.severity == SecurityLevel.LOW)
429
+
430
+ # Calculate risk score (0-100)
431
+ risk_score = min(
432
+ 100,
433
+ (critical_count * 25 + high_count * 10 + medium_count * 5 + low_count * 1),
434
+ )
435
+
436
+ return SecurityAuditReport(
437
+ findings=self.findings.copy(),
438
+ total_queries_audited=self.queries_audited,
439
+ critical_count=critical_count,
440
+ high_count=high_count,
441
+ medium_count=medium_count,
442
+ low_count=low_count,
443
+ audit_timestamp=time.time(),
444
+ overall_risk_score=risk_score,
445
+ )
446
+
447
+ def get_remediation_suggestions(self) -> Dict[VulnerabilityType, List[str]]:
448
+ """Get remediation suggestions grouped by vulnerability type"""
449
+ suggestions = {
450
+ VulnerabilityType.SQL_INJECTION: [
451
+ "Use parameterized queries with ? or %s placeholders",
452
+ "Validate and sanitize all user inputs",
453
+ "Implement input length limits",
454
+ "Use ORM frameworks when possible",
455
+ "Escape special SQL characters",
456
+ "Implement allowlist validation for dynamic queries",
457
+ ],
458
+ VulnerabilityType.XSS: [
459
+ "HTML encode all user input before display",
460
+ "Use Content Security Policy (CSP) headers",
461
+ "Validate input against expected patterns",
462
+ "Sanitize data before database storage",
463
+ "Implement output encoding",
464
+ ],
465
+ VulnerabilityType.COMMAND_INJECTION: [
466
+ "Never pass user input directly to system commands",
467
+ "Use allowlist validation for system operations",
468
+ "Implement proper input validation",
469
+ "Use safe APIs instead of shell commands",
470
+ "Run applications with minimal privileges",
471
+ ],
472
+ VulnerabilityType.PATH_TRAVERSAL: [
473
+ "Validate and sanitize all file paths",
474
+ "Use absolute paths with validation",
475
+ "Implement chroot or similar containment",
476
+ "Restrict file system access permissions",
477
+ "Use safe file handling libraries",
478
+ ],
479
+ VulnerabilityType.PRIVILEGE_ESCALATION: [
480
+ "Implement proper access controls",
481
+ "Use principle of least privilege",
482
+ "Validate user permissions before operations",
483
+ "Audit administrative operations",
484
+ "Separate administrative and user interfaces",
485
+ ],
486
+ VulnerabilityType.DATA_EXPOSURE: [
487
+ "Use specific column names instead of SELECT *",
488
+ "Implement data classification and handling policies",
489
+ "Use encryption for sensitive data",
490
+ "Implement proper access logging",
491
+ "Regular security audits of data access patterns",
492
+ ],
493
+ }
494
+ return suggestions
495
+
496
+
497
+ class SecureQueryBuilder:
498
+ """Helper class for building secure queries"""
499
+
500
+ def __init__(self, auditor: DatabaseSecurityAuditor):
501
+ self.auditor = auditor
502
+
503
+ def build_safe_select(
504
+ self,
505
+ table: str,
506
+ columns: List[str],
507
+ where_conditions: Dict[str, Any],
508
+ limit: Optional[int] = None,
509
+ ) -> Tuple[str, List[Any]]:
510
+ """Build a safe SELECT query"""
511
+ # Validate inputs
512
+ table = InputValidator.sanitize_sql_identifier(table)
513
+ validated_columns = [
514
+ InputValidator.sanitize_sql_identifier(col) for col in columns
515
+ ]
516
+
517
+ # Build query
518
+ columns_str = ", ".join(validated_columns)
519
+ query = f"SELECT {columns_str} FROM {table}"
520
+ params = []
521
+
522
+ if where_conditions:
523
+ where_parts = []
524
+ for column, value in where_conditions.items():
525
+ safe_column = InputValidator.sanitize_sql_identifier(column)
526
+ where_parts.append(f"{safe_column} = ?")
527
+ params.append(value)
528
+ query += f" WHERE {' AND '.join(where_parts)}"
529
+
530
+ if limit:
531
+ query += " LIMIT ?"
532
+ params.append(int(limit))
533
+
534
+ # Audit the query
535
+ is_safe, findings = self.auditor.validate_query_safety(
536
+ query, params, "build_safe_select"
537
+ )
538
+ if not is_safe:
539
+ raise SecurityError(f"Generated query failed security audit: {findings}")
540
+
541
+ return query, params
542
+
543
+ def build_safe_insert(
544
+ self, table: str, data: Dict[str, Any]
545
+ ) -> Tuple[str, List[Any]]:
546
+ """Build a safe INSERT query"""
547
+ # Validate inputs
548
+ table = InputValidator.sanitize_sql_identifier(table)
549
+ validated_data = {}
550
+
551
+ for column, value in data.items():
552
+ safe_column = InputValidator.sanitize_sql_identifier(column)
553
+ validated_data[safe_column] = value
554
+
555
+ columns = list(validated_data.keys())
556
+ values = list(validated_data.values())
557
+
558
+ # Build query
559
+ columns_str = ", ".join(columns)
560
+ placeholders = ", ".join(["?"] * len(values))
561
+ query = f"INSERT INTO {table} ({columns_str}) VALUES ({placeholders})"
562
+
563
+ # Audit the query
564
+ is_safe, findings = self.auditor.validate_query_safety(
565
+ query, values, "build_safe_insert"
566
+ )
567
+ if not is_safe:
568
+ raise SecurityError(f"Generated query failed security audit: {findings}")
569
+
570
+ return query, values
571
+
572
+
573
+ # Global security auditor instance
574
+ _global_auditor = DatabaseSecurityAuditor()
575
+
576
+
577
+ def get_security_auditor() -> DatabaseSecurityAuditor:
578
+ """Get the global security auditor instance"""
579
+ return _global_auditor
580
+
581
+
582
+ def audit_query(
583
+ query: str, params: Optional[List[Any]] = None, context: Optional[str] = None
584
+ ) -> List[SecurityFinding]:
585
+ """Convenience function to audit a single query"""
586
+ return _global_auditor.audit_query(query, params, context)
587
+
588
+
589
+ def validate_query_safety(
590
+ query: str, params: Optional[List[Any]] = None, context: Optional[str] = None
591
+ ) -> bool:
592
+ """Convenience function to validate query safety"""
593
+ is_safe, _ = _global_auditor.validate_query_safety(query, params, context)
594
+ return is_safe