diff --git a/.gitignore b/.gitignore index e69de29..2bb5881 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +__pycache__/ +*.pyc +logs/ diff --git a/logs/events_log.jsonl b/logs/events_log.jsonl index 78df9ab..08a5d10 100644 --- a/logs/events_log.jsonl +++ b/logs/events_log.jsonl @@ -1 +1,46 @@ {"timestamp": "2025-11-05T04:49:15.063275+00:00", "source": "camera_node_1", "type": "intrusion", "details": {"zone": "loading_bay", "confidence": 0.87}} +{"timestamp": "2025-11-05T15:27:23.114703+00:00", "source": "camA", "type": "intrusion", "details": {"zone": "lobby"}} +{"timestamp": "2025-11-05T15:27:35.974145+00:00", "source": "camA", "type": "intrusion", "details": {"zone": "lobby"}} +{"timestamp": "2025-11-05T15:27:44.733306+00:00", "source": "camA", "type": "intrusion", "details": {"zone": "lobby"}} +{"timestamp": "2025-11-05T15:28:22.105045+00:00", "source": "camA", "type": "intrusion", "details": {"zone": "lobby"}} +{"timestamp": "2025-11-05T15:28:40.150320+00:00", "source": "camA", "type": "intrusion", "details": {"zone": "lobby"}} +{"timestamp": "2025-11-05T15:28:49.425947+00:00", "source": "camA", "type": "intrusion", "details": {"zone": "lobby"}} +{"timestamp": "2025-11-05T15:29:36.701314+00:00", "source": "camA", "type": "intrusion", "details": {"zone": "lobby"}} +{"timestamp": "2025-11-05T15:48:20.214370+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "MainGate", "confidence": 0.83}} +{"timestamp": "2025-11-05T15:48:24.273159+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "MainGate", "confidence": 0.99}} +{"timestamp": "2025-11-05T15:48:29.280312+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "MainGate", "confidence": 0.78}} +{"timestamp": "2025-11-05T15:48:32.298760+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "ServerRoom", "confidence": 0.96}} +{"timestamp": "2025-11-05T15:48:34.305271+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "MainGate", "confidence": 0.81}} +{"timestamp": "2025-11-05T15:48:36.318437+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "Warehouse", "confidence": 0.79}} +{"timestamp": "2025-11-05T15:48:39.328778+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "Warehouse", "confidence": 0.88}} +{"timestamp": "2025-11-05T15:48:42.336792+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "ServerRoom", "confidence": 0.96}} +{"timestamp": "2025-11-05T15:48:47.343460+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "MainGate", "confidence": 0.82}} +{"timestamp": "2025-11-05T15:48:49.350584+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "Warehouse", "confidence": 0.74}} +{"timestamp": "2025-11-05T15:48:53.355437+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "MainGate", "confidence": 0.9}} +{"timestamp": "2025-11-05T15:48:57.364895+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "ServerRoom", "confidence": 0.97}} +{"timestamp": "2025-11-05T15:49:00.368249+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "Lobby", "confidence": 0.97}} +{"timestamp": "2025-11-05T15:49:02.373533+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "ServerRoom", "confidence": 0.85}} +{"timestamp": "2025-11-05T15:49:04.381406+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "Lobby", "confidence": 0.99}} +{"timestamp": "2025-11-05T15:49:09.387040+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "MainGate", "confidence": 0.98}} +{"timestamp": "2025-11-05T15:49:13.392251+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "ServerRoom", "confidence": 0.84}} +{"timestamp": "2025-11-05T15:49:18.398145+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "Warehouse", "confidence": 0.82}} +{"timestamp": "2025-11-05T15:53:29.591057+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "ServerRoom", "confidence": 0.8}} +{"timestamp": "2025-11-05T15:53:32.597284+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "MainGate", "confidence": 0.86}} +{"timestamp": "2025-11-05T15:53:35.602629+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "MainGate", "confidence": 0.93}} +{"timestamp": "2025-11-05T15:53:40.612955+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "MainGate", "confidence": 0.86}} +{"timestamp": "2025-11-05T15:53:42.617961+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "Lobby", "confidence": 0.84}} +{"timestamp": "2025-11-05T15:53:47.627867+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "MainGate", "confidence": 0.89}} +{"timestamp": "2025-11-05T15:53:49.654096+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "ServerRoom", "confidence": 0.8}} +{"timestamp": "2025-11-05T15:53:54.663972+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "ServerRoom", "confidence": 0.92}} +{"timestamp": "2025-11-05T15:53:59.672196+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "Lobby", "confidence": 0.92}} +{"timestamp": "2025-11-05T15:54:04.685684+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "Warehouse", "confidence": 0.76}} +{"timestamp": "2025-11-05T15:54:07.695522+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "ServerRoom", "confidence": 0.89}} +{"timestamp": "2025-11-05T15:54:11.713202+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "Warehouse", "confidence": 0.9}} +{"timestamp": "2025-11-05T15:54:14.716615+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "ServerRoom", "confidence": 0.77}} +{"timestamp": "2025-11-05T15:54:16.725430+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "MainGate", "confidence": 0.87}} +{"timestamp": "2025-11-05T15:54:21.732386+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "Warehouse", "confidence": 0.81}} +{"timestamp": "2025-11-05T15:54:25.734890+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "Warehouse", "confidence": 0.71}} +{"timestamp": "2025-11-05T15:54:27.741517+00:00", "source": "simSensor-01", "type": "motion", "details": {"zone": "MainGate", "confidence": 0.88}} +{"timestamp": "2025-11-05T15:54:31.746401+00:00", "source": "simSensor-01", "type": "door_open", "details": {"zone": "MainGate", "confidence": 0.93}} +{"timestamp": "2025-11-05T15:54:34.749109+00:00", "source": "simSensor-01", "type": "access_denied", "details": {"zone": "ServerRoom", "confidence": 0.86}} +{"timestamp": "2025-11-05T15:54:39.753955+00:00", "source": "simSensor-01", "type": "intrusion", "details": {"zone": "Warehouse", "confidence": 0.85}} diff --git a/sim/sensor_simulator.py b/sim/sensor_simulator.py new file mode 100644 index 0000000..59ed083 --- /dev/null +++ b/sim/sensor_simulator.py @@ -0,0 +1,43 @@ +import requests +import json +import random +import time +import os +from dotenv import load_dotenv + +# -------- Load env vars -------- +load_dotenv() +API_KEY = os.getenv("API_KEY") +BRIDGE_URL = "http://127.0.0.1:8000/api/event" + +EVENT_TYPES = ["motion", "intrusion", "access_denied", "door_open"] +ZONES = ["Lobby", "Warehouse", "ServerRoom", "MainGate"] + +def send_event(event_type): + payload = { + "source": "simSensor-01", + "type": event_type, + "details": { + "zone": random.choice(ZONES), + "confidence": round(random.uniform(0.7, 0.99), 2) + } + } + headers = { + "Authorization": f"Bearer {API_KEY}", + "Content-Type": "application/json" + } + + try: + response = requests.post(BRIDGE_URL, headers=headers, data=json.dumps(payload)) + print(f"[SENT] {event_type} ({response.status_code})") + except Exception as e: + print(f"[ERROR] Could not reach Bridge: {e}") + +print("[INFO] SynAccel Sensor Simulator started. Press Ctrl+C to stop.") +try: + while True: + event = random.choice(EVENT_TYPES) + send_event(event) + time.sleep(random.randint(2, 5)) +except KeyboardInterrupt: + print("\n[INFO] Sensor simulation stopped.") diff --git a/src/bridge_core/__pycache__/main.cpython-311.pyc b/src/bridge_core/__pycache__/main.cpython-311.pyc index faab4bb..620660b 100644 Binary files a/src/bridge_core/__pycache__/main.cpython-311.pyc and b/src/bridge_core/__pycache__/main.cpython-311.pyc differ diff --git a/src/bridge_core/main.py b/src/bridge_core/main.py index 9993e06..1ae0b49 100644 --- a/src/bridge_core/main.py +++ b/src/bridge_core/main.py @@ -7,6 +7,8 @@ from datetime import datetime, timezone import json from pathlib import Path +from src.intelligence.correlation_engine import correlate_events + # ---------- Load environment variables ---------- load_dotenv() @@ -46,6 +48,9 @@ class Event(BaseModel): def index(): return {"message": "SynAccel-Bridge API is running"} +recent_events = [] + + @app.post("/api/event") async def receive_event(event: Event, authorized: bool = Depends(verify_api_key)): """Receive and process a security or sensor event.""" @@ -59,13 +64,25 @@ async def receive_event(event: Event, authorized: bool = Depends(verify_api_key) logs_dir = Path("logs") logs_dir.mkdir(exist_ok=True) - log_file = logs_dir / "events_log.jsonl" with open(log_file, "a") as f: f.write(json.dumps(log_entry) + "\n") - - return { - "received": True, - "message": "Event logged successfully", - "data": event.dict() - } + + recent_events.append(log_entry) + if len(recent_events) > 20: + recent_events.pop(0) + + # --- 3. Run correlation analysis + alerts = correlate_events(recent_events) + + if alerts: + alerts_file = logs_dir / "alerts_log.jsonl" + with open(alerts_file, "a") as f: + for alert in alerts: + f.write(json.dumps(alert) + "\n") + for a in alerts: + print(f"[⚠] {a['severity'].upper()} | {a['message']}") + + # Normal response + print(f"[✔] Logged {event.type} from {event.source}") + return {"received": True, "alerts_triggered": len(alerts), "data": event.dict()} diff --git a/src/intelligence/correlation_engine.py b/src/intelligence/correlation_engine.py new file mode 100644 index 0000000..04c6207 --- /dev/null +++ b/src/intelligence/correlation_engine.py @@ -0,0 +1,62 @@ +from datetime import datetime, timezone, timedelta + +# A dictionary to remember when each source last triggered an alert +last_alert_time = {} + +def correlate_events(events): + """ + Analyze recent events for abnormal patterns. + Returns a list of generated alerts. + """ + alerts = [] + now = datetime.now(timezone.utc) + + # --- Parameters --- + INTRUSION_THRESHOLD = 3 # how many intrusions before alert + COOLDOWN_PERIOD = timedelta(seconds=60) # wait 60s before re-alerting same source + WINDOW = timedelta(seconds=30) # only look at events within last 30 s + + # --- Filter only recent events --- + recent_window = [ + e for e in events + if now - datetime.fromisoformat(e["timestamp"]) <= WINDOW + ] + + # --- Example 1: repeated intrusions from same source --- + intrusion_counts = {} + for e in recent_window: + if e["type"] == "intrusion": + intrusion_counts[e["source"]] = intrusion_counts.get(e["source"], 0) + 1 + + for src, count in intrusion_counts.items(): + if count >= INTRUSION_THRESHOLD: + # cooldown check + if src not in last_alert_time or now - last_alert_time[src] > COOLDOWN_PERIOD: + alerts.append({ + "timestamp": now.isoformat(), + "source": src, + "type": "intrusion_spike", + "severity": "high", + "message": f"{count} intrusion events from {src} within short timeframe" + }) + last_alert_time[src] = now # remember time of alert + + # --- Example 2: motion followed by access_denied from same source --- + for i in range(len(recent_window) - 1): + if ( + recent_window[i]["type"] == "motion" + and recent_window[i + 1]["type"] == "access_denied" + and recent_window[i]["source"] == recent_window[i + 1]["source"] + ): + src = recent_window[i]["source"] + if src not in last_alert_time or now - last_alert_time[src] > COOLDOWN_PERIOD: + alerts.append({ + "timestamp": now.isoformat(), + "source": src, + "type": "sequence_alert", + "severity": "medium", + "message": f"Motion followed by access_denied on {src}" + }) + last_alert_time[src] = now + + return alerts