Skip to content

Commit 38dc75b

Browse files
committed
feat: Integrated Gemini API to generate dynamic AI explanations for security alerts.
1 parent 1e1201c commit 38dc75b

File tree

1 file changed

+37
-5
lines changed

1 file changed

+37
-5
lines changed

worker/worker.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from datetime import datetime
1313
from typing import Optional
1414
import redis
15+
import requests
1516
from behavior import BehaviorEngine
1617
from semantic import OpenRouterSimilarityDetector
1718
from fusion import FusionEngine
@@ -22,6 +23,7 @@
2223
EVENTS_CHANNEL = "events"
2324
ALERT_THRESHOLD = 0.7
2425
PERFORMANCE_TARGET_MS = 500
26+
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
2527

2628

2729
class ShadowGuardWorker:
@@ -144,18 +146,48 @@ def _format_alert(self, result: dict, log_data: dict, processing_time_ms: float)
144146
"""
145147
return alert
146148

149+
def _generate_ai_explanation(self, domain: str, url: str, category: str) -> str:
150+
"""Generate a concise AI explanation using Gemini API."""
151+
if not GEMINI_API_KEY:
152+
return f"Detected access to {category} service"
153+
154+
try:
155+
prompt = (
156+
f"You are a security analyst. Explain why accessing {domain} ({url}) "
157+
f"is a security risk in 1 short sentence. Category: {category}."
158+
)
159+
160+
response = requests.post(
161+
f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}",
162+
json={"contents": [{"parts": [{"text": prompt}]}]},
163+
timeout=5
164+
)
165+
166+
if response.status_code == 200:
167+
text = response.json()["candidates"][0]["content"]["parts"][0]["text"]
168+
return text.strip()
169+
else:
170+
print(f"[WARN] Gemini API returned {response.status_code}: {response.text}")
171+
except Exception as e:
172+
print(f"[WARN] Gemini API failed: {e}")
173+
174+
return f"Detected access to {category} service"
175+
147176
def _save_alert_to_redis(self, result: dict):
148177
"""Save alert to Redis for backend consumption."""
149178
try:
150-
# Handle override vs normal analysis
179+
# Determine category
151180
if result.get("override"):
152-
# Blacklisted/Whitelisted domain - use override data
153181
category = "Blacklisted" if result["final_risk_score"] > 0.5 else "Whitelisted"
154-
ai_message = result.get("override_reason", "No explanation")
155182
else:
156-
# Normal analysis - use semantic data
157183
category = result.get("semantic_analysis", {}).get("top_category", "unknown")
158-
ai_message = result.get("semantic_analysis", {}).get("explanation", "No analysis available")
184+
185+
# Always generate AI explanation
186+
ai_message = self._generate_ai_explanation(
187+
domain=result["domain"],
188+
url=result.get("url", ""),
189+
category=category
190+
)
159191

160192
# Format alert for frontend
161193
alert = {

0 commit comments

Comments
 (0)