iflow-mcp-m507_ai-soc-agent 1.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.
Files changed (85) hide show
  1. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/METADATA +410 -0
  2. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/RECORD +85 -0
  3. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/WHEEL +5 -0
  4. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/entry_points.txt +2 -0
  5. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/licenses/LICENSE +21 -0
  6. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/top_level.txt +1 -0
  7. src/__init__.py +8 -0
  8. src/ai_controller/README.md +139 -0
  9. src/ai_controller/__init__.py +12 -0
  10. src/ai_controller/agent_executor.py +596 -0
  11. src/ai_controller/cli/__init__.py +2 -0
  12. src/ai_controller/cli/main.py +243 -0
  13. src/ai_controller/session_manager.py +409 -0
  14. src/ai_controller/web/__init__.py +2 -0
  15. src/ai_controller/web/server.py +1181 -0
  16. src/ai_controller/web/static/css/README.md +102 -0
  17. src/api/__init__.py +13 -0
  18. src/api/case_management.py +271 -0
  19. src/api/edr.py +187 -0
  20. src/api/kb.py +136 -0
  21. src/api/siem.py +308 -0
  22. src/core/__init__.py +10 -0
  23. src/core/config.py +242 -0
  24. src/core/config_storage.py +684 -0
  25. src/core/dto.py +50 -0
  26. src/core/errors.py +36 -0
  27. src/core/logging.py +128 -0
  28. src/integrations/__init__.py +8 -0
  29. src/integrations/case_management/__init__.py +5 -0
  30. src/integrations/case_management/iris/__init__.py +11 -0
  31. src/integrations/case_management/iris/iris_client.py +885 -0
  32. src/integrations/case_management/iris/iris_http.py +274 -0
  33. src/integrations/case_management/iris/iris_mapper.py +263 -0
  34. src/integrations/case_management/iris/iris_models.py +128 -0
  35. src/integrations/case_management/thehive/__init__.py +8 -0
  36. src/integrations/case_management/thehive/thehive_client.py +193 -0
  37. src/integrations/case_management/thehive/thehive_http.py +147 -0
  38. src/integrations/case_management/thehive/thehive_mapper.py +190 -0
  39. src/integrations/case_management/thehive/thehive_models.py +125 -0
  40. src/integrations/cti/__init__.py +6 -0
  41. src/integrations/cti/local_tip/__init__.py +10 -0
  42. src/integrations/cti/local_tip/local_tip_client.py +90 -0
  43. src/integrations/cti/local_tip/local_tip_http.py +110 -0
  44. src/integrations/cti/opencti/__init__.py +10 -0
  45. src/integrations/cti/opencti/opencti_client.py +101 -0
  46. src/integrations/cti/opencti/opencti_http.py +418 -0
  47. src/integrations/edr/__init__.py +6 -0
  48. src/integrations/edr/elastic_defend/__init__.py +6 -0
  49. src/integrations/edr/elastic_defend/elastic_defend_client.py +351 -0
  50. src/integrations/edr/elastic_defend/elastic_defend_http.py +162 -0
  51. src/integrations/eng/__init__.py +10 -0
  52. src/integrations/eng/clickup/__init__.py +8 -0
  53. src/integrations/eng/clickup/clickup_client.py +513 -0
  54. src/integrations/eng/clickup/clickup_http.py +156 -0
  55. src/integrations/eng/github/__init__.py +8 -0
  56. src/integrations/eng/github/github_client.py +169 -0
  57. src/integrations/eng/github/github_http.py +158 -0
  58. src/integrations/eng/trello/__init__.py +8 -0
  59. src/integrations/eng/trello/trello_client.py +207 -0
  60. src/integrations/eng/trello/trello_http.py +162 -0
  61. src/integrations/kb/__init__.py +12 -0
  62. src/integrations/kb/fs_kb_client.py +313 -0
  63. src/integrations/siem/__init__.py +6 -0
  64. src/integrations/siem/elastic/__init__.py +6 -0
  65. src/integrations/siem/elastic/elastic_client.py +3319 -0
  66. src/integrations/siem/elastic/elastic_http.py +165 -0
  67. src/mcp/README.md +183 -0
  68. src/mcp/TOOLS.md +2827 -0
  69. src/mcp/__init__.py +13 -0
  70. src/mcp/__main__.py +18 -0
  71. src/mcp/agent_profiles.py +408 -0
  72. src/mcp/flow_agent_profiles.py +424 -0
  73. src/mcp/mcp_server.py +4086 -0
  74. src/mcp/rules_engine.py +487 -0
  75. src/mcp/runbook_manager.py +264 -0
  76. src/orchestrator/__init__.py +11 -0
  77. src/orchestrator/incident_workflow.py +244 -0
  78. src/orchestrator/tools_case.py +1085 -0
  79. src/orchestrator/tools_cti.py +359 -0
  80. src/orchestrator/tools_edr.py +315 -0
  81. src/orchestrator/tools_eng.py +378 -0
  82. src/orchestrator/tools_kb.py +156 -0
  83. src/orchestrator/tools_siem.py +1709 -0
  84. src/web/__init__.py +8 -0
  85. src/web/config_server.py +511 -0
@@ -0,0 +1,418 @@
1
+ """
2
+ Low-level HTTP client for OpenCTI (Open Cyber Threat Intelligence Platform).
3
+
4
+ This module handles GraphQL requests to the OpenCTI API for hash lookups.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from typing import Any, Dict, Optional
11
+
12
+ import requests
13
+
14
+ from ....core.errors import IntegrationError
15
+ from ....core.logging import get_logger
16
+
17
+
18
+ logger = get_logger("sami.integrations.cti.opencti.http")
19
+
20
+
21
+ class OpenCTIHttpClient:
22
+ """
23
+ HTTP client for OpenCTI GraphQL API.
24
+
25
+ Handles hash lookups via GraphQL queries.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ base_url: str,
31
+ api_key: str,
32
+ timeout_seconds: int = 30,
33
+ verify_ssl: bool = True,
34
+ ) -> None:
35
+ """
36
+ Initialize the OpenCTI HTTP client.
37
+
38
+ Args:
39
+ base_url: Base URL of the OpenCTI API (e.g., "https://opencti.example.com")
40
+ api_key: API key/token for authentication
41
+ timeout_seconds: Request timeout in seconds
42
+ verify_ssl: Whether to verify SSL certificates
43
+ """
44
+ self.base_url = base_url.rstrip("/")
45
+ self.api_key = api_key
46
+ self.timeout_seconds = timeout_seconds
47
+ self.verify_ssl = verify_ssl
48
+ # Try /api/graphql first, fallback to /graphql
49
+ self.graphql_endpoint = f"{self.base_url}/api/graphql"
50
+ self.graphql_endpoint_fallback = f"{self.base_url}/graphql"
51
+
52
+ logger.info(
53
+ f"OpenCTI HTTP client initialized: base_url={self.base_url}, "
54
+ f"primary_endpoint={self.graphql_endpoint}, "
55
+ f"fallback_endpoint={self.graphql_endpoint_fallback}, "
56
+ f"timeout={timeout_seconds}s, verify_ssl={verify_ssl}"
57
+ )
58
+
59
+ def _headers(self) -> Dict[str, str]:
60
+ """Build request headers."""
61
+ return {
62
+ "Content-Type": "application/json",
63
+ "Accept": "application/json",
64
+ "Authorization": f"Bearer {self.api_key}",
65
+ }
66
+
67
+ def _get_hash_type(self, hash_value: str) -> str:
68
+ """
69
+ Determine hash type based on length.
70
+
71
+ Args:
72
+ hash_value: The hash value
73
+
74
+ Returns:
75
+ Hash algorithm name (MD5, SHA1, SHA256, SHA512)
76
+ """
77
+ hash_length = len(hash_value.strip())
78
+ if hash_length == 32:
79
+ return "MD5"
80
+ elif hash_length == 40:
81
+ return "SHA1"
82
+ elif hash_length == 64:
83
+ return "SHA256"
84
+ elif hash_length == 128:
85
+ return "SHA512"
86
+ else:
87
+ # Default to SHA256 for unknown lengths
88
+ return "SHA256"
89
+
90
+ def _get_observable_type(self, hash_type: str) -> str:
91
+ """
92
+ Get OpenCTI observable type for hash.
93
+
94
+ Args:
95
+ hash_type: Hash algorithm name (MD5, SHA1, SHA256, SHA512)
96
+
97
+ Returns:
98
+ OpenCTI observable type (e.g., "File-SHA256")
99
+ """
100
+ type_map = {
101
+ "MD5": "File-MD5",
102
+ "SHA1": "File-SHA1",
103
+ "SHA256": "File-SHA256",
104
+ "SHA512": "File-SHA512",
105
+ }
106
+ return type_map.get(hash_type, "File-SHA256")
107
+
108
+ def lookup_hash(self, hash_value: str) -> Optional[Dict[str, Any]]:
109
+ """
110
+ Look up a hash via the GraphQL API.
111
+
112
+ Args:
113
+ hash_value: The hash value to look up (MD5, SHA1, SHA256, SHA512)
114
+
115
+ Returns:
116
+ Dictionary containing hash information and related indicators, or None if not found
117
+
118
+ Raises:
119
+ IntegrationError: If the API request fails
120
+ """
121
+ hash_type = self._get_hash_type(hash_value)
122
+ observable_type = self._get_observable_type(hash_type)
123
+ hash_value_clean = hash_value.strip()
124
+
125
+ logger.info(
126
+ f"Starting OpenCTI hash lookup: hash={hash_value_clean[:16]}... "
127
+ f"(type={hash_type}, observable_type={observable_type})"
128
+ )
129
+
130
+ # OpenCTI GraphQL query using stixCyberObservables
131
+ # This is the correct way to query observables in OpenCTI
132
+ query = """
133
+ query HashLookup($filters: [StixCyberObservablesFiltering!], $first: Int) {
134
+ stixCyberObservables(filters: $filters, first: $first) {
135
+ edges {
136
+ node {
137
+ id
138
+ observable_value
139
+ entity_type
140
+ indicators {
141
+ edges {
142
+ node {
143
+ id
144
+ pattern
145
+ pattern_type
146
+ valid_from
147
+ valid_until
148
+ x_opencti_score
149
+ x_opencti_detection
150
+ created_at
151
+ updated_at
152
+ labels {
153
+ edges {
154
+ node {
155
+ id
156
+ value
157
+ }
158
+ }
159
+ }
160
+ killChainPhases {
161
+ edges {
162
+ node {
163
+ id
164
+ kill_chain_name
165
+ phase_name
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ """
177
+
178
+ # Build filters for OpenCTI
179
+ # Filter by observable type (e.g., "File-SHA256") and hash value
180
+ filters = [
181
+ {
182
+ "key": "entity_type",
183
+ "values": [observable_type]
184
+ },
185
+ {
186
+ "key": "observable_value",
187
+ "values": [hash_value_clean]
188
+ }
189
+ ]
190
+
191
+ variables = {
192
+ "filters": filters,
193
+ "first": 10 # Limit to first 10 results
194
+ }
195
+
196
+ payload = {
197
+ "query": query,
198
+ "variables": variables
199
+ }
200
+
201
+ try:
202
+ logger.debug(
203
+ f"OpenCTI request details: endpoint={self.graphql_endpoint}, "
204
+ f"hash_type={hash_type}, observable_type={observable_type}, "
205
+ f"filters={filters}, timeout={self.timeout_seconds}s"
206
+ )
207
+
208
+ # Try primary endpoint first
209
+ logger.debug(f"Sending POST request to primary endpoint: {self.graphql_endpoint}")
210
+ response = requests.post(
211
+ self.graphql_endpoint,
212
+ json=payload,
213
+ headers=self._headers(),
214
+ timeout=self.timeout_seconds,
215
+ verify=self.verify_ssl,
216
+ )
217
+
218
+ logger.debug(f"Primary endpoint response: status_code={response.status_code}")
219
+
220
+ # If 404 on primary endpoint, try fallback
221
+ if response.status_code == 404:
222
+ logger.warning(
223
+ f"Primary endpoint {self.graphql_endpoint} returned 404 (Not Found). "
224
+ f"Attempting fallback endpoint: {self.graphql_endpoint_fallback}"
225
+ )
226
+ response = requests.post(
227
+ self.graphql_endpoint_fallback,
228
+ json=payload,
229
+ headers=self._headers(),
230
+ timeout=self.timeout_seconds,
231
+ verify=self.verify_ssl,
232
+ )
233
+ logger.debug(f"Fallback endpoint response: status_code={response.status_code}")
234
+
235
+ # Update endpoint for future requests if fallback works
236
+ if response.status_code != 404:
237
+ logger.info(
238
+ f"Fallback endpoint successful. Updating primary endpoint to: "
239
+ f"{self.graphql_endpoint_fallback}"
240
+ )
241
+ self.graphql_endpoint = self.graphql_endpoint_fallback
242
+ else:
243
+ logger.error(
244
+ f"Both endpoints returned 404: primary={self.graphql_endpoint}, "
245
+ f"fallback={self.graphql_endpoint_fallback}"
246
+ )
247
+
248
+ # Raise for any HTTP errors (including 404 if both endpoints fail)
249
+ response.raise_for_status()
250
+
251
+ logger.debug(f"HTTP request successful: status_code={response.status_code}")
252
+ result = response.json()
253
+
254
+ # Log response structure for debugging (without sensitive data)
255
+ logger.debug(
256
+ f"GraphQL response received: has_data={'data' in result}, "
257
+ f"has_errors={'errors' in result}, response_keys={list(result.keys())}"
258
+ )
259
+
260
+ # Check for GraphQL errors
261
+ if "errors" in result:
262
+ error_messages = [err.get("message", "Unknown error") for err in result["errors"]]
263
+ error_msg = "; ".join(error_messages)
264
+ logger.error(
265
+ f"GraphQL query returned errors for hash {hash_value_clean[:16]}...: "
266
+ f"{error_msg}. Full errors: {json.dumps(result['errors'], indent=2)}"
267
+ )
268
+ raise IntegrationError(f"GraphQL query failed: {error_msg}")
269
+
270
+ # Extract hash data from GraphQL response
271
+ data = result.get("data", {})
272
+ observables = data.get("stixCyberObservables", {})
273
+ edges = observables.get("edges", [])
274
+
275
+ logger.debug(
276
+ f"Parsed GraphQL response: observables_found={len(edges)}, "
277
+ f"observable_keys={list(observables.keys()) if observables else 'N/A'}"
278
+ )
279
+
280
+ if not edges:
281
+ logger.info(
282
+ f"Hash {hash_value_clean[:16]}... (type={hash_type}) not found in OpenCTI. "
283
+ f"No observables matching the criteria."
284
+ )
285
+ return None
286
+
287
+ # Get the first observable result
288
+ observable_node = edges[0]["node"]
289
+ observable_id = observable_node.get("id", "unknown")
290
+ observable_value = observable_node.get("observable_value", hash_value_clean)
291
+
292
+ logger.info(
293
+ f"Found observable in OpenCTI: id={observable_id}, "
294
+ f"value={observable_value[:16]}..., type={observable_type}"
295
+ )
296
+
297
+ # Build result dictionary
298
+ hash_result = {
299
+ "value": observable_value,
300
+ "algorithm": hash_type, # Use detected hash type
301
+ "id": observable_id,
302
+ "indicators": [],
303
+ }
304
+
305
+ # Extract indicators
306
+ indicators = observable_node.get("indicators", {}).get("edges", [])
307
+ logger.debug(f"Found {len(indicators)} indicator(s) associated with observable")
308
+
309
+ for idx, indicator_edge in enumerate(indicators):
310
+ indicator_node = indicator_edge["node"]
311
+ indicator_id = indicator_node.get("id", "unknown")
312
+ indicator_score = indicator_node.get("x_opencti_score")
313
+ indicator_detection = indicator_node.get("x_opencti_detection")
314
+
315
+ logger.debug(
316
+ f"Processing indicator {idx + 1}/{len(indicators)}: "
317
+ f"id={indicator_id}, score={indicator_score}, "
318
+ f"detection={indicator_detection}"
319
+ )
320
+
321
+ indicator = {
322
+ "id": indicator_id,
323
+ "pattern": indicator_node.get("pattern"),
324
+ "pattern_type": indicator_node.get("pattern_type"),
325
+ "valid_from": indicator_node.get("valid_from"),
326
+ "valid_until": indicator_node.get("valid_until"),
327
+ "score": indicator_score,
328
+ "detection": indicator_detection,
329
+ "created_at": indicator_node.get("created_at"),
330
+ "updated_at": indicator_node.get("updated_at"),
331
+ "labels": [],
332
+ "kill_chain_phases": [],
333
+ }
334
+
335
+ # Extract labels
336
+ labels = indicator_node.get("labels", {}).get("edges", [])
337
+ for label_edge in labels:
338
+ label_value = label_edge["node"].get("value")
339
+ indicator["labels"].append(label_value)
340
+
341
+ if labels:
342
+ logger.debug(f"Indicator {indicator_id} has {len(labels)} label(s): {[l['node'].get('value') for l in labels]}")
343
+
344
+ # Extract kill chain phases
345
+ kill_chain = indicator_node.get("killChainPhases", {}).get("edges", [])
346
+ for phase_edge in kill_chain:
347
+ phase_node = phase_edge["node"]
348
+ indicator["kill_chain_phases"].append({
349
+ "kill_chain_name": phase_node.get("kill_chain_name"),
350
+ "phase_name": phase_node.get("phase_name"),
351
+ })
352
+
353
+ if kill_chain:
354
+ logger.debug(
355
+ f"Indicator {indicator_id} has {len(kill_chain)} kill chain phase(s): "
356
+ f"{[p['node'].get('phase_name') for p in kill_chain]}"
357
+ )
358
+
359
+ hash_result["indicators"].append(indicator)
360
+
361
+ logger.info(
362
+ f"OpenCTI hash lookup successful: hash={hash_value_clean[:16]}..., "
363
+ f"observable_id={observable_id}, indicators_count={len(hash_result['indicators'])}"
364
+ )
365
+ return hash_result
366
+
367
+ except requests.exceptions.Timeout as e:
368
+ logger.error(
369
+ f"Timeout looking up hash {hash_value_clean[:16]}... in OpenCTI: "
370
+ f"endpoint={self.graphql_endpoint}, timeout={self.timeout_seconds}s, error={e}"
371
+ )
372
+ raise IntegrationError(f"Timeout looking up hash: {e}") from e
373
+ except requests.exceptions.RequestException as e:
374
+ logger.error(
375
+ f"API request failed for hash {hash_value_clean[:16]}... in OpenCTI: "
376
+ f"endpoint={self.graphql_endpoint}, error_type={type(e).__name__}, error={e}"
377
+ )
378
+
379
+ # Try to extract error details from response
380
+ error_detail = None
381
+ status_code = None
382
+ if hasattr(e, "response") and e.response is not None:
383
+ status_code = e.response.status_code
384
+ logger.debug(
385
+ f"Error response details: status_code={status_code}, "
386
+ f"headers={dict(e.response.headers) if e.response.headers else 'N/A'}"
387
+ )
388
+
389
+ try:
390
+ error_detail = e.response.json()
391
+ logger.debug(f"Error response JSON: {json.dumps(error_detail, indent=2)}")
392
+
393
+ if "errors" in error_detail:
394
+ error_messages = [err.get("message", "Unknown error") for err in error_detail["errors"]]
395
+ error_detail = "; ".join(error_messages)
396
+ elif "detail" in error_detail:
397
+ error_detail = error_detail["detail"]
398
+ except Exception as parse_error:
399
+ logger.debug(f"Could not parse error response as JSON: {parse_error}")
400
+ if e.response.text:
401
+ error_detail = e.response.text[:200]
402
+ logger.debug(f"Error response text (first 200 chars): {error_detail}")
403
+
404
+ error_msg = f"API request failed: {e}"
405
+ if status_code:
406
+ error_msg += f" (status_code={status_code})"
407
+ if error_detail:
408
+ error_msg += f" - {error_detail}"
409
+
410
+ logger.error(f"OpenCTI hash lookup failed: {error_msg}")
411
+ raise IntegrationError(error_msg) from e
412
+ except Exception as e:
413
+ logger.exception(
414
+ f"Unexpected error during OpenCTI hash lookup for {hash_value_clean[:16]}...: "
415
+ f"error_type={type(e).__name__}, error={e}"
416
+ )
417
+ raise IntegrationError(f"Unexpected error during hash lookup: {e}") from e
418
+
@@ -0,0 +1,6 @@
1
+ """EDR integrations for SamiGPT."""
2
+
3
+ from .elastic_defend.elastic_defend_client import ElasticDefendEDRClient
4
+
5
+ __all__ = ["ElasticDefendEDRClient"]
6
+
@@ -0,0 +1,6 @@
1
+ """Elastic Defend EDR integration."""
2
+
3
+ from .elastic_defend_client import ElasticDefendEDRClient
4
+
5
+ __all__ = ["ElasticDefendEDRClient"]
6
+