memorisdk 1.0.1__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.
- memori/__init__.py +24 -8
- memori/agents/conscious_agent.py +252 -414
- memori/agents/memory_agent.py +487 -224
- memori/agents/retrieval_agent.py +416 -60
- memori/config/memory_manager.py +323 -0
- memori/core/conversation.py +393 -0
- memori/core/database.py +386 -371
- memori/core/memory.py +1676 -534
- memori/core/providers.py +217 -0
- memori/database/adapters/__init__.py +10 -0
- memori/database/adapters/mysql_adapter.py +331 -0
- memori/database/adapters/postgresql_adapter.py +291 -0
- memori/database/adapters/sqlite_adapter.py +229 -0
- memori/database/auto_creator.py +320 -0
- memori/database/connection_utils.py +207 -0
- memori/database/connectors/base_connector.py +283 -0
- memori/database/connectors/mysql_connector.py +240 -18
- memori/database/connectors/postgres_connector.py +277 -4
- memori/database/connectors/sqlite_connector.py +178 -3
- memori/database/models.py +400 -0
- memori/database/queries/base_queries.py +1 -1
- memori/database/queries/memory_queries.py +91 -2
- memori/database/query_translator.py +222 -0
- memori/database/schema_generators/__init__.py +7 -0
- memori/database/schema_generators/mysql_schema_generator.py +215 -0
- memori/database/search/__init__.py +8 -0
- memori/database/search/mysql_search_adapter.py +255 -0
- memori/database/search/sqlite_search_adapter.py +180 -0
- memori/database/search_service.py +548 -0
- memori/database/sqlalchemy_manager.py +839 -0
- memori/integrations/__init__.py +36 -11
- memori/integrations/litellm_integration.py +340 -6
- memori/integrations/openai_integration.py +506 -240
- memori/utils/input_validator.py +395 -0
- memori/utils/pydantic_models.py +138 -36
- memori/utils/query_builder.py +530 -0
- memori/utils/security_audit.py +594 -0
- memori/utils/security_integration.py +339 -0
- memori/utils/transaction_manager.py +547 -0
- {memorisdk-1.0.1.dist-info → memorisdk-2.0.0.dist-info}/METADATA +144 -34
- memorisdk-2.0.0.dist-info/RECORD +67 -0
- memorisdk-1.0.1.dist-info/RECORD +0 -44
- memorisdk-1.0.1.dist-info/entry_points.txt +0 -2
- {memorisdk-1.0.1.dist-info → memorisdk-2.0.0.dist-info}/WHEEL +0 -0
- {memorisdk-1.0.1.dist-info → memorisdk-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {memorisdk-1.0.1.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
|