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
src/web/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """
2
+ Web interface components for SamiGPT.
3
+
4
+ This package contains:
5
+ - ``mcp_server.py``: MCP server that exposes investigation skills as tools.
6
+ - ``rules_engine.py``: Rules/workflow engine for automated investigations.
7
+ """
8
+
@@ -0,0 +1,511 @@
1
+ """
2
+ Web server for SamiGPT configuration management.
3
+
4
+ This module provides a FastAPI server that exposes:
5
+ - REST API for managing configurations
6
+ - Web UI for configuring TheHive, Elastic (SIEM), and EDR integrations
7
+
8
+ The interface is protected with a secret/password that must be set via environment variable.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import hashlib
14
+ import hmac
15
+ import os
16
+ import secrets
17
+ from pathlib import Path
18
+ from typing import Any, Dict, Optional
19
+
20
+ from fastapi import FastAPI, HTTPException, Request, Response, status
21
+ from fastapi.responses import HTMLResponse, JSONResponse
22
+ from fastapi.staticfiles import StaticFiles
23
+ from pydantic import BaseModel
24
+ from starlette.middleware.base import BaseHTTPMiddleware
25
+
26
+ from ..core.config_storage import (
27
+ CONFIG_FILE,
28
+ ENV_FILE,
29
+ get_config_dict,
30
+ load_config_from_file,
31
+ update_config_dict,
32
+ )
33
+ from ..core.errors import ConfigError
34
+
35
+ # Get admin secret from environment or use default
36
+ ADMIN_SECRET = os.getenv("SAMIGPT_ADMIN_SECRET", "admin")
37
+ SESSION_SECRET = os.getenv("SAMIGPT_SESSION_SECRET", secrets.token_urlsafe(32))
38
+
39
+ # In-memory session store (for simple implementation)
40
+ # In production, consider using Redis or database-backed sessions
41
+ _sessions: Dict[str, Dict[str, Any]] = {}
42
+
43
+
44
+ class SimpleSessionMiddleware(BaseHTTPMiddleware):
45
+ """
46
+ Simple session middleware that doesn't require itsdangerous.
47
+ Uses signed cookies with HMAC for security.
48
+ """
49
+
50
+ def __init__(self, app, secret_key: str):
51
+ super().__init__(app)
52
+ self.secret_key = secret_key.encode()
53
+
54
+ async def dispatch(self, request: Request, call_next):
55
+ # Get session ID from cookie
56
+ session_id = request.cookies.get("session_id")
57
+ session_data = {}
58
+
59
+ if session_id and session_id in _sessions:
60
+ # Verify signature
61
+ expected_sig = self._sign(session_id)
62
+ provided_sig = request.cookies.get("session_sig", "")
63
+ if hmac.compare_digest(expected_sig, provided_sig):
64
+ session_data = _sessions[session_id].copy()
65
+
66
+ # Attach session to request state
67
+ request.state.session = session_data
68
+
69
+ # Process request
70
+ response = await call_next(request)
71
+
72
+ # Save session if modified
73
+ if hasattr(request.state, "session_modified") and request.state.session_modified:
74
+ if not session_id or session_id not in _sessions:
75
+ session_id = secrets.token_urlsafe(32)
76
+ _sessions[session_id] = {}
77
+
78
+ _sessions[session_id].update(request.state.session)
79
+
80
+ # Set cookie with signature
81
+ sig = self._sign(session_id)
82
+ response.set_cookie(
83
+ key="session_id",
84
+ value=session_id,
85
+ httponly=True,
86
+ samesite="lax",
87
+ max_age=86400, # 24 hours
88
+ )
89
+ response.set_cookie(
90
+ key="session_sig",
91
+ value=sig,
92
+ httponly=True,
93
+ samesite="lax",
94
+ max_age=86400,
95
+ )
96
+
97
+ return response
98
+
99
+ def _sign(self, data: str) -> str:
100
+ """Create HMAC signature for session ID."""
101
+ return hmac.new(self.secret_key, data.encode(), hashlib.sha256).hexdigest()
102
+
103
+
104
+ # Create FastAPI app
105
+ app = FastAPI(
106
+ title="SamiGPT Configuration Manager",
107
+ description="Web interface for configuring SamiGPT integrations",
108
+ version="1.0.0",
109
+ )
110
+
111
+ # Add simple session middleware (no itsdangerous dependency)
112
+ app.add_middleware(SimpleSessionMiddleware, secret_key=SESSION_SECRET)
113
+
114
+ # Determine paths
115
+ WEB_DIR = Path(__file__).parent
116
+ STATIC_DIR = WEB_DIR / "static"
117
+ TEMPLATES_DIR = WEB_DIR / "templates"
118
+
119
+ # Create directories if they don't exist
120
+ STATIC_DIR.mkdir(exist_ok=True)
121
+ TEMPLATES_DIR.mkdir(exist_ok=True)
122
+
123
+ # Mount static files
124
+ if STATIC_DIR.exists():
125
+ app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
126
+
127
+
128
+ # Helper function to check authentication
129
+ def is_authenticated(request: Request) -> bool:
130
+ """Check if the request is authenticated."""
131
+ session_data = getattr(request.state, "session", {})
132
+ return session_data.get("authenticated", False) == True
133
+
134
+
135
+ def require_auth(request: Request) -> None:
136
+ """Require authentication or raise 401."""
137
+ if not is_authenticated(request):
138
+ raise HTTPException(
139
+ status_code=status.HTTP_401_UNAUTHORIZED,
140
+ detail="Authentication required",
141
+ )
142
+
143
+
144
+ # Pydantic models for API requests
145
+ class LoginRequest(BaseModel):
146
+ secret: str
147
+
148
+
149
+ class TheHiveConfigUpdate(BaseModel):
150
+ base_url: Optional[str] = None
151
+ api_key: Optional[str] = None
152
+ timeout_seconds: Optional[int] = 30
153
+ enabled: bool = True
154
+
155
+
156
+ class IrisConfigUpdate(BaseModel):
157
+ base_url: Optional[str] = None
158
+ api_key: Optional[str] = None
159
+ timeout_seconds: Optional[int] = 30
160
+ enabled: bool = True
161
+
162
+
163
+ class ElasticConfigUpdate(BaseModel):
164
+ base_url: Optional[str] = None
165
+ api_key: Optional[str] = None
166
+ username: Optional[str] = None
167
+ password: Optional[str] = None
168
+ timeout_seconds: Optional[int] = 30
169
+ verify_ssl: Optional[bool] = True
170
+ enabled: bool = True
171
+
172
+
173
+ class EDRConfigUpdate(BaseModel):
174
+ edr_type: Optional[str] = "velociraptor"
175
+ base_url: Optional[str] = None
176
+ api_key: Optional[str] = None
177
+ timeout_seconds: Optional[int] = 30
178
+ enabled: bool = True
179
+
180
+
181
+ class LoggingConfigUpdate(BaseModel):
182
+ log_dir: Optional[str] = "logs"
183
+ log_level: Optional[str] = "INFO"
184
+
185
+
186
+ class ConfigUpdate(BaseModel):
187
+ thehive: Optional[TheHiveConfigUpdate] = None
188
+ iris: Optional[IrisConfigUpdate] = None
189
+ elastic: Optional[ElasticConfigUpdate] = None
190
+ edr: Optional[EDRConfigUpdate] = None
191
+ logging: Optional[LoggingConfigUpdate] = None
192
+
193
+
194
+ @app.get("/", response_class=HTMLResponse)
195
+ async def root(request: Request):
196
+ """Serve the login page or main configuration page if authenticated."""
197
+ session_data = getattr(request.state, "session", {})
198
+ if not session_data.get("authenticated", False):
199
+ login_path = TEMPLATES_DIR / "login.html"
200
+ if login_path.exists():
201
+ with open(login_path, "r") as f:
202
+ return HTMLResponse(content=f.read())
203
+ return HTMLResponse(
204
+ content="""
205
+ <!DOCTYPE html>
206
+ <html>
207
+ <head><title>Login - SamiGPT</title></head>
208
+ <body>
209
+ <h1>Login Required</h1>
210
+ <p>Please set SAMIGPT_ADMIN_SECRET environment variable and login page will appear.</p>
211
+ </body>
212
+ </html>
213
+ """
214
+ )
215
+
216
+ # Authenticated - serve config page
217
+ html_path = TEMPLATES_DIR / "index.html"
218
+ if html_path.exists():
219
+ with open(html_path, "r") as f:
220
+ return HTMLResponse(content=f.read())
221
+ return HTMLResponse(
222
+ content="<h1>SamiGPT Configuration Manager</h1><p>index.html not found</p>"
223
+ )
224
+
225
+
226
+ @app.post("/api/auth/login")
227
+ async def login(request: Request, login_data: LoginRequest):
228
+ """Authenticate with the admin secret."""
229
+ # Compare using constant-time comparison to prevent timing attacks
230
+ if secrets.compare_digest(login_data.secret, ADMIN_SECRET):
231
+ request.state.session = request.state.session if hasattr(request.state, "session") else {}
232
+ request.state.session["authenticated"] = True
233
+ request.state.session_modified = True
234
+ return JSONResponse(content={"success": True, "message": "Authentication successful"})
235
+ else:
236
+ raise HTTPException(
237
+ status_code=status.HTTP_401_UNAUTHORIZED,
238
+ detail="Invalid secret",
239
+ )
240
+
241
+
242
+ @app.post("/api/auth/logout")
243
+ async def logout(request: Request, response: Response):
244
+ """Logout and clear session."""
245
+ session_id = request.cookies.get("session_id")
246
+ if session_id and session_id in _sessions:
247
+ del _sessions[session_id]
248
+
249
+ # Clear cookies
250
+ response.delete_cookie("session_id")
251
+ response.delete_cookie("session_sig")
252
+
253
+ return JSONResponse(content={"success": True, "message": "Logged out successfully"})
254
+
255
+
256
+ @app.get("/api/auth/status")
257
+ async def auth_status(request: Request):
258
+ """Check authentication status."""
259
+ session_data = getattr(request.state, "session", {})
260
+ authenticated = session_data.get("authenticated", False) == True
261
+ return JSONResponse(
262
+ content={"authenticated": authenticated}
263
+ )
264
+
265
+
266
+ @app.get("/api/config", response_model=Dict[str, Any])
267
+ async def get_config(request: Request):
268
+ """Get current configuration."""
269
+ require_auth(request)
270
+ try:
271
+ config_dict = get_config_dict()
272
+ # Remove sensitive data from response
273
+ if "thehive" in config_dict and config_dict["thehive"]:
274
+ config_dict["thehive"]["api_key"] = "***" if config_dict["thehive"].get("api_key") else None
275
+ if "elastic" in config_dict and config_dict["elastic"]:
276
+ config_dict["elastic"]["api_key"] = "***" if config_dict["elastic"].get("api_key") else None
277
+ config_dict["elastic"]["password"] = "***" if config_dict["elastic"].get("password") else None
278
+ if "edr" in config_dict and config_dict["edr"]:
279
+ config_dict["edr"]["api_key"] = "***" if config_dict["edr"].get("api_key") else None
280
+
281
+ # Add file location information
282
+ from pathlib import Path
283
+ config_dict["_meta"] = {
284
+ "config_file": str(Path(CONFIG_FILE).absolute()),
285
+ "env_file": str(Path(ENV_FILE).absolute()),
286
+ "config_file_exists": Path(CONFIG_FILE).exists(),
287
+ "env_file_exists": Path(ENV_FILE).exists(),
288
+ }
289
+ return JSONResponse(content=config_dict)
290
+ except ConfigError as e:
291
+ raise HTTPException(status_code=500, detail=str(e))
292
+
293
+
294
+ @app.post("/api/config", response_model=Dict[str, Any])
295
+ async def update_config(request: Request, config_update: ConfigUpdate):
296
+ """Update configuration."""
297
+ require_auth(request)
298
+ try:
299
+ # Build update dict
300
+ updates: Dict[str, Any] = {}
301
+
302
+ if config_update.thehive is not None:
303
+ if config_update.thehive.enabled:
304
+ if not config_update.thehive.base_url or not config_update.thehive.api_key:
305
+ raise HTTPException(
306
+ status_code=400,
307
+ detail="TheHive base_url and api_key are required when enabled",
308
+ )
309
+ updates["thehive"] = {
310
+ "base_url": config_update.thehive.base_url,
311
+ "api_key": config_update.thehive.api_key,
312
+ "timeout_seconds": config_update.thehive.timeout_seconds or 30,
313
+ }
314
+ else:
315
+ updates["thehive"] = None
316
+
317
+ if config_update.iris is not None:
318
+ if config_update.iris.enabled:
319
+ if not config_update.iris.base_url or not config_update.iris.api_key:
320
+ raise HTTPException(
321
+ status_code=400,
322
+ detail="IRIS base_url and api_key are required when enabled",
323
+ )
324
+ updates["iris"] = {
325
+ "base_url": config_update.iris.base_url,
326
+ "api_key": config_update.iris.api_key,
327
+ "timeout_seconds": config_update.iris.timeout_seconds or 30,
328
+ }
329
+ else:
330
+ updates["iris"] = None
331
+
332
+ if config_update.elastic is not None:
333
+ if config_update.elastic.enabled:
334
+ if not config_update.elastic.base_url:
335
+ raise HTTPException(
336
+ status_code=400,
337
+ detail="Elastic base_url is required when enabled",
338
+ )
339
+ updates["elastic"] = {
340
+ "base_url": config_update.elastic.base_url,
341
+ "api_key": config_update.elastic.api_key,
342
+ "username": config_update.elastic.username,
343
+ "password": config_update.elastic.password,
344
+ "timeout_seconds": config_update.elastic.timeout_seconds or 30,
345
+ "verify_ssl": config_update.elastic.verify_ssl,
346
+ }
347
+ else:
348
+ updates["elastic"] = None
349
+
350
+ if config_update.edr is not None:
351
+ if config_update.edr.enabled:
352
+ if not config_update.edr.base_url or not config_update.edr.api_key:
353
+ raise HTTPException(
354
+ status_code=400,
355
+ detail="EDR base_url and api_key are required when enabled",
356
+ )
357
+ updates["edr"] = {
358
+ "edr_type": config_update.edr.edr_type or "velociraptor",
359
+ "base_url": config_update.edr.base_url,
360
+ "api_key": config_update.edr.api_key,
361
+ "timeout_seconds": config_update.edr.timeout_seconds or 30,
362
+ }
363
+ else:
364
+ updates["edr"] = None
365
+
366
+ if config_update.logging is not None:
367
+ updates["logging"] = {
368
+ "log_dir": config_update.logging.log_dir or "logs",
369
+ "log_level": config_update.logging.log_level or "INFO",
370
+ }
371
+
372
+ # Update and save config
373
+ updated_config = update_config_dict(updates)
374
+ config_dict = get_config_dict()
375
+
376
+ return JSONResponse(content={"success": True, "config": config_dict})
377
+ except ConfigError as e:
378
+ raise HTTPException(status_code=500, detail=str(e))
379
+
380
+
381
+ @app.get("/api/config/test/thehive")
382
+ async def test_thehive(request: Request):
383
+ """Test TheHive connection."""
384
+ require_auth(request)
385
+ try:
386
+ config = load_config_from_file()
387
+ if not config.thehive:
388
+ raise HTTPException(status_code=400, detail="TheHive not configured")
389
+
390
+ from ..integrations.case_management.thehive.thehive_client import (
391
+ TheHiveCaseManagementClient,
392
+ )
393
+
394
+ client = TheHiveCaseManagementClient.from_config(config)
395
+ is_connected = client.ping()
396
+
397
+ return JSONResponse(
398
+ content={
399
+ "success": is_connected,
400
+ "message": "Connected successfully" if is_connected else "Connection failed",
401
+ }
402
+ )
403
+ except Exception as e:
404
+ return JSONResponse(
405
+ content={"success": False, "message": str(e)}, status_code=500
406
+ )
407
+
408
+
409
+ @app.get("/api/config/test/iris")
410
+ async def test_iris(request: Request):
411
+ """Test IRIS connection."""
412
+ require_auth(request)
413
+ try:
414
+ config = load_config_from_file()
415
+ if not config.iris:
416
+ raise HTTPException(status_code=400, detail="IRIS not configured")
417
+
418
+ from ..integrations.case_management.iris.iris_client import (
419
+ IRISCaseManagementClient,
420
+ )
421
+
422
+ client = IRISCaseManagementClient.from_config(config)
423
+ is_connected = client.ping()
424
+
425
+ return JSONResponse(
426
+ content={
427
+ "success": is_connected,
428
+ "message": "Connected successfully" if is_connected else "Connection failed",
429
+ }
430
+ )
431
+ except Exception as e:
432
+ return JSONResponse(
433
+ content={"success": False, "message": str(e)}, status_code=500
434
+ )
435
+
436
+
437
+ @app.get("/api/config/test/elastic")
438
+ async def test_elastic(request: Request):
439
+ """Test Elastic connection."""
440
+ require_auth(request)
441
+ try:
442
+ config = load_config_from_file()
443
+ if not config.elastic:
444
+ raise HTTPException(status_code=400, detail="Elastic not configured")
445
+
446
+ # TODO: Implement Elastic client test when integration is available
447
+ return JSONResponse(
448
+ content={
449
+ "success": False,
450
+ "message": "Elastic integration not yet implemented",
451
+ }
452
+ )
453
+ except Exception as e:
454
+ return JSONResponse(
455
+ content={"success": False, "message": str(e)}, status_code=500
456
+ )
457
+
458
+
459
+ @app.get("/api/config/test/edr")
460
+ async def test_edr(request: Request):
461
+ """Test EDR connection."""
462
+ require_auth(request)
463
+ try:
464
+ config = load_config_from_file()
465
+ if not config.edr:
466
+ raise HTTPException(status_code=400, detail="EDR not configured")
467
+
468
+ # TODO: Implement EDR client test when integration is available
469
+ return JSONResponse(
470
+ content={
471
+ "success": False,
472
+ "message": "EDR integration not yet implemented",
473
+ }
474
+ )
475
+ except Exception as e:
476
+ return JSONResponse(
477
+ content={"success": False, "message": str(e)}, status_code=500
478
+ )
479
+
480
+
481
+ @app.post("/api/config/reload")
482
+ async def reload_config(request: Request):
483
+ """Reload configuration from files (sync from .env or config.json)."""
484
+ require_auth(request)
485
+ try:
486
+ # Force reload from files (priority: .env > config.json)
487
+ config = load_config_from_file()
488
+ # Save to both files to sync them
489
+ from ..core.config_storage import save_config_to_file
490
+ save_config_to_file(config, save_both=True)
491
+
492
+ config_dict = get_config_dict()
493
+ return JSONResponse(
494
+ content={
495
+ "success": True,
496
+ "message": "Configuration reloaded from files",
497
+ "config": config_dict,
498
+ }
499
+ )
500
+ except ConfigError as e:
501
+ raise HTTPException(status_code=500, detail=str(e))
502
+
503
+
504
+ if __name__ == "__main__":
505
+ import uvicorn
506
+
507
+ print(f"Starting SamiGPT Configuration Manager...")
508
+ print(f"Admin secret is set via SAMIGPT_ADMIN_SECRET environment variable")
509
+ if ADMIN_SECRET == "admin":
510
+ print("WARNING: Using default admin secret! Set SAMIGPT_ADMIN_SECRET environment variable.")
511
+ uvicorn.run(app, host="0.0.0.0", port=8080)