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,162 @@
1
+ """
2
+ Low-level HTTP client for Trello API.
3
+
4
+ This module is responsible for:
5
+ - authentication (API key and token)
6
+ - building URLs
7
+ - making HTTP requests
8
+ - basic error handling
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from dataclasses import dataclass
15
+ from typing import Any, Dict, Optional
16
+
17
+ import requests
18
+
19
+ from ....core.errors import IntegrationError
20
+ from ....core.logging import get_logger
21
+
22
+
23
+ logger = get_logger("sami.integrations.trello.http")
24
+
25
+
26
+ @dataclass
27
+ class TrelloHttpClient:
28
+ """
29
+ Simple HTTP client for Trello's REST API.
30
+
31
+ Trello API documentation: https://developer.atlassian.com/cloud/trello/guides/rest-api/api-introduction/
32
+ """
33
+
34
+ api_key: str
35
+ api_token: str
36
+ timeout_seconds: int = 30
37
+ verify_ssl: bool = True
38
+
39
+ def _auth_params(self) -> Dict[str, str]:
40
+ """Get authentication query parameters."""
41
+ return {
42
+ "key": self.api_key,
43
+ "token": self.api_token,
44
+ }
45
+
46
+ def _build_url(self, endpoint: str) -> str:
47
+ """
48
+ Build a full URL from an endpoint.
49
+
50
+ Args:
51
+ endpoint: API endpoint path (e.g., "/1/boards/{boardId}/cards")
52
+
53
+ Returns:
54
+ Full URL string
55
+ """
56
+ base_url = "https://api.trello.com"
57
+ endpoint = endpoint.lstrip("/")
58
+ return f"{base_url}/{endpoint}"
59
+
60
+ def _handle_trello_error(self, response: requests.Response) -> None:
61
+ """
62
+ Raise IntegrationError if the response indicates an error.
63
+
64
+ Args:
65
+ response: HTTP response object
66
+
67
+ Raises:
68
+ IntegrationError: If the response indicates an error
69
+ """
70
+ if response.status_code < 400:
71
+ return
72
+
73
+ try:
74
+ error_data = response.json()
75
+ message = error_data.get("message", f"HTTP {response.status_code}")
76
+ except Exception:
77
+ message = f"HTTP {response.status_code}: {response.text[:200]}"
78
+
79
+ raise IntegrationError(f"Trello API error: {message}")
80
+
81
+ def request(
82
+ self,
83
+ method: str,
84
+ endpoint: str,
85
+ json_data: Optional[Dict[str, Any]] = None,
86
+ params: Optional[Dict[str, Any]] = None,
87
+ ) -> Dict[str, Any]:
88
+ """
89
+ Make an HTTP request to Trello API.
90
+
91
+ Args:
92
+ method: HTTP method (GET, POST, PUT, PATCH, DELETE)
93
+ endpoint: API endpoint path
94
+ json_data: JSON payload (for POST, PUT, PATCH)
95
+ params: Query parameters (merged with auth params)
96
+
97
+ Returns:
98
+ Response JSON as dictionary
99
+
100
+ Raises:
101
+ IntegrationError: If the request fails
102
+ """
103
+ url = self._build_url(endpoint)
104
+ headers = {
105
+ "Content-Type": "application/json",
106
+ "Accept": "application/json",
107
+ }
108
+
109
+ # Merge auth params with provided params
110
+ all_params = self._auth_params().copy()
111
+ if params:
112
+ all_params.update(params)
113
+
114
+ try:
115
+ logger.debug(f"Trello {method} {url}")
116
+ if all_params:
117
+ logger.debug(f" Query params: {all_params}")
118
+ if json_data:
119
+ logger.debug(f" JSON payload: {json.dumps(json_data)[:200]}...")
120
+
121
+ response = requests.request(
122
+ method=method,
123
+ url=url,
124
+ headers=headers,
125
+ json=json_data,
126
+ params=all_params,
127
+ timeout=self.timeout_seconds,
128
+ verify=self.verify_ssl,
129
+ )
130
+
131
+ logger.debug(f"Trello response status: {response.status_code}")
132
+ if response.status_code >= 400:
133
+ logger.error(f"Trello API error - Status: {response.status_code}, URL: {url}, Response: {response.text[:500]}")
134
+
135
+ self._handle_trello_error(response)
136
+
137
+ if response.status_code == 204: # No Content
138
+ return {}
139
+
140
+ return response.json()
141
+
142
+ except requests.exceptions.Timeout as e:
143
+ raise IntegrationError(f"Trello API request timeout: {e}") from e
144
+ except requests.exceptions.RequestException as e:
145
+ raise IntegrationError(f"Trello API request failed: {e}") from e
146
+
147
+ def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
148
+ """GET request."""
149
+ return self.request("GET", endpoint, params=params)
150
+
151
+ def post(self, endpoint: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
152
+ """POST request."""
153
+ return self.request("POST", endpoint, json_data=json_data, params=params)
154
+
155
+ def put(self, endpoint: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
156
+ """PUT request."""
157
+ return self.request("PUT", endpoint, json_data=json_data, params=params)
158
+
159
+ def delete(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
160
+ """DELETE request."""
161
+ return self.request("DELETE", endpoint, params=params)
162
+
@@ -0,0 +1,12 @@
1
+ """
2
+ Knowledge base integrations for SamiGPT.
3
+
4
+ Currently this provides a filesystem-backed KB client that reads client
5
+ infrastructure descriptions from ``client_env/*`` in the project root.
6
+ """
7
+
8
+ from .fs_kb_client import FileSystemKBClient
9
+
10
+ __all__ = ["FileSystemKBClient"]
11
+
12
+
@@ -0,0 +1,313 @@
1
+ """
2
+ Filesystem-backed KB client.
3
+
4
+ This client reads client infrastructure knowledge from JSON files under
5
+ ``client_env/*`` in the project root and exposes it via the KBClient API.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ from typing import List, Optional
13
+
14
+ from ...core.errors import IntegrationError
15
+ from ...api.kb import (
16
+ KBClient,
17
+ KBClientInfra,
18
+ KBSubnet,
19
+ KBServer,
20
+ KBUser,
21
+ KBDeviceSchema,
22
+ KBUserSchema,
23
+ KBEnvRules,
24
+ )
25
+
26
+
27
+ class FileSystemKBClient(KBClient):
28
+ """
29
+ Knowledge base client that reads from local JSON files.
30
+
31
+ Folder layout (relative to project root):
32
+ - client_env/
33
+ - all_clients/ # generic templates (not treated as a client)
34
+ - env_rules.json # shared rules across all clients
35
+ - <client_name>_client/
36
+ - internal_subnets.json
37
+ - internal_servers.json
38
+ - internal_users.json
39
+ - naming_schemas.json
40
+ """
41
+
42
+ def __init__(self, client_env_dir: Optional[str] = None) -> None:
43
+ # Infer project root from this file path
44
+ if client_env_dir is None:
45
+ project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
46
+ client_env_dir = os.path.join(project_root, "client_env")
47
+
48
+ self.client_env_dir = client_env_dir
49
+
50
+ # ------------------------------------------------------------------
51
+ # KBClient interface
52
+ # ------------------------------------------------------------------
53
+ def list_clients(self) -> List[str]:
54
+ """List available client environment folders."""
55
+ if not os.path.isdir(self.client_env_dir):
56
+ return []
57
+
58
+ clients: List[str] = []
59
+ for entry in os.listdir(self.client_env_dir):
60
+ full_path = os.path.join(self.client_env_dir, entry)
61
+ if not os.path.isdir(full_path):
62
+ continue
63
+ if entry == "all_clients":
64
+ continue
65
+ # Conventionally treat folders ending with _client as full environments
66
+ if entry.endswith("_client"):
67
+ clients.append(entry)
68
+ clients.sort()
69
+ return clients
70
+
71
+ def get_client_infra(self, client_name: str) -> KBClientInfra:
72
+ """Load and aggregate infrastructure knowledge for a specific client."""
73
+ # Allow passing bare client name without _client suffix
74
+ if not client_name.endswith("_client"):
75
+ candidate = f"{client_name}_client"
76
+ else:
77
+ candidate = client_name
78
+
79
+ client_dir = os.path.join(self.client_env_dir, candidate)
80
+ if not os.path.isdir(client_dir):
81
+ raise IntegrationError(
82
+ f"Client environment not found: {client_name} "
83
+ f"(looked in {client_dir})"
84
+ )
85
+
86
+ subnets = self._load_subnets(client_dir)
87
+ servers = self._load_servers(client_dir)
88
+ users = self._load_users(client_dir)
89
+ device_schemas, user_schemas = self._load_naming_schemas(client_dir)
90
+ env_rules = self._load_env_rules()
91
+
92
+ summary = self._build_summary(
93
+ client_name=candidate,
94
+ subnets=subnets,
95
+ servers=servers,
96
+ users=users,
97
+ device_schemas=device_schemas,
98
+ user_schemas=user_schemas,
99
+ )
100
+
101
+ return KBClientInfra(
102
+ client_name=candidate,
103
+ subnets=subnets,
104
+ servers=servers,
105
+ users=users,
106
+ device_schemas=device_schemas,
107
+ user_schemas=user_schemas,
108
+ env_rules=env_rules,
109
+ summary=summary,
110
+ )
111
+
112
+ # ------------------------------------------------------------------
113
+ # Helpers for loading JSON and building DTOs
114
+ # ------------------------------------------------------------------
115
+ def _load_json(self, path: str) -> Optional[dict]:
116
+ if not os.path.exists(path):
117
+ return None
118
+ try:
119
+ with open(path, "r", encoding="utf-8") as f:
120
+ return json.load(f)
121
+ except Exception as e:
122
+ raise IntegrationError(f"Failed to load JSON from {path}: {e}") from e
123
+
124
+ def _load_subnets(self, client_dir: str) -> List[KBSubnet]:
125
+ data = self._load_json(os.path.join(client_dir, "internal_subnets.json")) or {}
126
+ raw_subnets = data.get("subnets", [])
127
+ subnets: List[KBSubnet] = []
128
+ for s in raw_subnets:
129
+ subnets.append(
130
+ KBSubnet(
131
+ name=s.get("name", ""),
132
+ cidr=s.get("cidr", ""),
133
+ network_type=s.get("network_type"),
134
+ access_method=s.get("access_method"),
135
+ description=s.get("description"),
136
+ tags=s.get("tags"),
137
+ )
138
+ )
139
+ return subnets
140
+
141
+ def _load_servers(self, client_dir: str) -> List[KBServer]:
142
+ data = self._load_json(os.path.join(client_dir, "internal_servers.json")) or {}
143
+ raw_servers = data.get("servers", [])
144
+ servers: List[KBServer] = []
145
+ for s in raw_servers:
146
+ servers.append(
147
+ KBServer(
148
+ hostname=s.get("hostname", ""),
149
+ ip_address=s.get("ip_address"),
150
+ role=s.get("role"),
151
+ environment=s.get("environment"),
152
+ os=s.get("os"),
153
+ description=s.get("description"),
154
+ criticality=s.get("criticality"),
155
+ tags=s.get("tags"),
156
+ )
157
+ )
158
+ return servers
159
+
160
+ def _load_users(self, client_dir: str) -> List[KBUser]:
161
+ data = self._load_json(os.path.join(client_dir, "internal_users.json")) or {}
162
+ raw_users = data.get("users", [])
163
+ users: List[KBUser] = []
164
+ for u in raw_users:
165
+ users.append(
166
+ KBUser(
167
+ username=u.get("username", ""),
168
+ display_name=u.get("display_name"),
169
+ account_type=u.get("account_type"),
170
+ department=u.get("department"),
171
+ privilege_level=u.get("privilege_level"),
172
+ description=u.get("description"),
173
+ tags=u.get("tags"),
174
+ )
175
+ )
176
+ return users
177
+
178
+ def _load_naming_schemas(
179
+ self, client_dir: str
180
+ ) -> tuple[List[KBDeviceSchema], List[KBUserSchema]]:
181
+ data = self._load_json(os.path.join(client_dir, "naming_schemas.json")) or {}
182
+ raw_device_schemas = data.get("device_schemas", [])
183
+ raw_user_schemas = data.get("user_schemas", [])
184
+
185
+ device_schemas: List[KBDeviceSchema] = []
186
+ for d in raw_device_schemas:
187
+ # Skip entries missing required fields
188
+ if not d.get("pattern") or not d.get("device_type"):
189
+ continue
190
+ device_schemas.append(
191
+ KBDeviceSchema(
192
+ pattern=d.get("pattern", ""),
193
+ pattern_style=d.get("pattern_style", "regex"),
194
+ device_type=d.get("device_type", ""),
195
+ example=d.get("example"),
196
+ description=d.get("description"),
197
+ tags=d.get("tags"),
198
+ )
199
+ )
200
+
201
+ user_schemas: List[KBUserSchema] = []
202
+ for u in raw_user_schemas:
203
+ if not u.get("pattern") or not u.get("user_type"):
204
+ continue
205
+ user_schemas.append(
206
+ KBUserSchema(
207
+ pattern=u.get("pattern", ""),
208
+ pattern_style=u.get("pattern_style", "regex"),
209
+ user_type=u.get("user_type", ""),
210
+ example=u.get("example"),
211
+ description=u.get("description"),
212
+ tags=u.get("tags"),
213
+ )
214
+ )
215
+
216
+ return device_schemas, user_schemas
217
+
218
+ def _load_env_rules(self) -> Optional[KBEnvRules]:
219
+ data = self._load_json(os.path.join(self.client_env_dir, "env_rules.json"))
220
+ if not data:
221
+ return None
222
+ return KBEnvRules(
223
+ version=data.get("version"),
224
+ environment_types=data.get("environment_types"),
225
+ network_classification=data.get("network_classification"),
226
+ user_categories=data.get("user_categories"),
227
+ general_rules=data.get("general_rules"),
228
+ )
229
+
230
+ # ------------------------------------------------------------------
231
+ # Summary builder
232
+ # ------------------------------------------------------------------
233
+ def _build_summary(
234
+ self,
235
+ client_name: str,
236
+ subnets: List[KBSubnet],
237
+ servers: List[KBServer],
238
+ users: List[KBUser],
239
+ device_schemas: List[KBDeviceSchema],
240
+ user_schemas: List[KBUserSchema],
241
+ ) -> str:
242
+ """Build a concise, human-readable summary of the client environment."""
243
+
244
+ total_subnets = len(subnets)
245
+ total_servers = len(servers)
246
+ total_users = len(users)
247
+
248
+ # Count by simple categories
249
+ internal_subnets = [
250
+ s for s in subnets if (s.network_type or "").lower() in {"internal", "lan"}
251
+ ]
252
+ vpn_subnets = [
253
+ s for s in subnets
254
+ if "vpn" in (s.tags or []) or "vpn" in (s.description or "").lower()
255
+ ]
256
+ guest_subnets = [
257
+ s for s in subnets
258
+ if (s.network_type or "").lower() == "guest"
259
+ or "guest" in (s.tags or [])
260
+ ]
261
+ dmz_subnets = [
262
+ s for s in subnets
263
+ if (s.network_type or "").lower() == "dmz"
264
+ or "dmz" in (s.tags or [])
265
+ ]
266
+
267
+ admin_users = [
268
+ u for u in users
269
+ if (u.account_type or "").lower() == "administrator"
270
+ or (u.privilege_level or "").lower() in {"admin", "high"}
271
+ ]
272
+ contractor_users = [
273
+ u for u in users if (u.account_type or "").lower() == "contractor"
274
+ ]
275
+ service_accounts = [
276
+ u for u in users if (u.account_type or "").lower() in {"service", "service_account"}
277
+ ]
278
+
279
+ lines: List[str] = []
280
+ lines.append(f"Client '{client_name}' infrastructure overview:")
281
+ lines.append(
282
+ f"- {total_subnets} subnets "
283
+ f"({len(internal_subnets)} internal, "
284
+ f"{len(vpn_subnets)} VPN, "
285
+ f"{len(dmz_subnets)} DMZ, "
286
+ f"{len(guest_subnets)} guest)"
287
+ )
288
+ lines.append(
289
+ f"- {total_servers} servers (examples: "
290
+ + ", ".join(s.hostname for s in servers[:3])
291
+ + (", ..." if total_servers > 3 else ")")
292
+ )
293
+ lines.append(
294
+ f"- {total_users} user accounts "
295
+ f"({len(admin_users)} admin, "
296
+ f"{len(service_accounts)} service, "
297
+ f"{len(contractor_users)} contractor)"
298
+ )
299
+
300
+ if device_schemas:
301
+ device_types = sorted({d.device_type for d in device_schemas})
302
+ lines.append(
303
+ "- Device naming schemas define types: " + ", ".join(device_types)
304
+ )
305
+ if user_schemas:
306
+ user_types = sorted({u.user_type for u in user_schemas})
307
+ lines.append(
308
+ "- User naming schemas define categories: " + ", ".join(user_types)
309
+ )
310
+
311
+ return "\n".join(lines)
312
+
313
+
@@ -0,0 +1,6 @@
1
+ """SIEM integrations for SamiGPT."""
2
+
3
+ from .elastic.elastic_client import ElasticSIEMClient
4
+
5
+ __all__ = ["ElasticSIEMClient"]
6
+
@@ -0,0 +1,6 @@
1
+ """Elastic SIEM integration."""
2
+
3
+ from .elastic_client import ElasticSIEMClient
4
+
5
+ __all__ = ["ElasticSIEMClient"]
6
+