Skip to content

Commit fa0ddec

Browse files
committed
Stabilize UTC timestamps and dev tooling
1 parent 7212f0b commit fa0ddec

9 files changed

Lines changed: 536 additions & 303 deletions

File tree

backend/app/main.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import csv
44
import io
55
import logging
6+
import os
7+
import sys
68
import time
79
from contextlib import asynccontextmanager
810
from datetime import datetime, timezone
@@ -103,8 +105,11 @@
103105
saved_filter_service = SavedFilterService()
104106
logger = logging.getLogger("netbotpro.api")
105107
if not logger.handlers:
108+
default_level = "CRITICAL" if "unittest" in sys.modules else "INFO"
109+
log_level = os.environ.get("NETBOT_LOG_LEVEL", default_level).upper()
106110
logging.basicConfig(
107-
level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s %(message)s"
111+
level=getattr(logging, log_level, logging.INFO),
112+
format="%(asctime)s %(levelname)s %(name)s %(message)s",
108113
)
109114
MAX_PCAP_UPLOAD_BYTES = 50 * 1024 * 1024
110115
ALLOWED_PCAP_SUFFIXES = {".pcap", ".pcapng"}

backend/app/services/sniffer_service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import os
55
import queue
66
import threading
7-
from datetime import datetime
7+
from datetime import datetime, timezone
88
from typing import Any
99

1010
from backend.app.bootstrap import ensure_project_root_on_path
@@ -189,7 +189,7 @@ def _on_packet(self, meta: dict[str, Any]) -> None:
189189

190190
def _process_packet(self, meta: dict[str, Any]) -> None:
191191
packet = dict(meta)
192-
packet.setdefault("ts", datetime.utcnow().isoformat() + "Z")
192+
packet.setdefault("ts", datetime.now(timezone.utc).isoformat())
193193
packet.setdefault("id", self._next_packet_id())
194194
policy = current_capture_policy()
195195
self._apply_payload_policy(packet, policy.to_public_dict())

core/ids_rules_engine.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# ids_rules_engine.py
22
# -*- coding: utf-8 -*-
3-
import os
43
import json
4+
import os
55
from collections import defaultdict
6-
from datetime import datetime, timedelta
7-
from typing import Dict, Any, List, Tuple
6+
from datetime import datetime, timedelta, timezone
7+
from typing import Any, Dict, List, Tuple
88

99

1010
class RuleEngine:
@@ -66,7 +66,7 @@ def analyze(self, meta: Dict[str, Any]) -> Dict[str, Any] | None:
6666

6767
src = meta.get("src")
6868
dport = int(meta.get("dport") or 0)
69-
now = datetime.utcnow()
69+
now = datetime.now(timezone.utc)
7070

7171
for idx, rule in enumerate(self.rules):
7272
if not rule or not rule.get("enabled", True):

core/ids_signature.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212

1313
from __future__ import annotations
1414

15-
from collections import defaultdict
16-
from datetime import datetime, timedelta
17-
from typing import Dict, Any, List, Tuple, Optional
1815
import json
1916
import os
17+
from collections import defaultdict
18+
from datetime import datetime, timedelta, timezone
19+
from typing import Any, Dict, List, Optional, Tuple
2020

2121

2222
class SignatureIDS:
@@ -28,7 +28,9 @@ class SignatureIDS:
2828

2929
def __init__(self) -> None:
3030
# (src, dst) -> list[(ts, dport)]
31-
self.port_scan_map: Dict[Tuple[str, str], List[Tuple[datetime, int]]] = defaultdict(list)
31+
self.port_scan_map: Dict[Tuple[str, str], List[Tuple[datetime, int]]] = (
32+
defaultdict(list)
33+
)
3234
# (src, dst) -> list[ts]
3335
self.syn_counter: Dict[Tuple[str, str], List[datetime]] = defaultdict(list)
3436
# src -> list[ts]
@@ -47,7 +49,9 @@ def __init__(self) -> None:
4749
# Custom rules for Rule Editor
4850
self.custom_rules: List[Dict[str, Any]] = []
4951
# (rule_idx, src, dport) -> list[datetime]
50-
self.custom_state: Dict[Tuple[int, str, int], List[datetime]] = defaultdict(list)
52+
self.custom_state: Dict[Tuple[int, str, int], List[datetime]] = defaultdict(
53+
list
54+
)
5155

5256
base_dir = os.path.abspath(os.path.dirname(__file__))
5357
self.rules_path: str = os.path.join(base_dir, "rules.json")
@@ -149,15 +153,17 @@ def analyze_packet(self, meta: Dict[str, Any]) -> Optional[Dict[str, Any]]:
149153
Returns:
150154
alert dict or None
151155
"""
152-
now = datetime.utcnow()
156+
now = datetime.now(timezone.utc)
153157
self._cleanup(now)
154158

155159
src = meta.get("src")
156160
dst = meta.get("dst")
157161
proto = meta.get("proto")
158162
dport = meta.get("dport")
159-
flags = (meta.get("flags") or "")
160-
length = int(meta.get("length") or 0) # فعلاً استفاده نمی‌کنیم ولی برای آینده خوبه
163+
flags = meta.get("flags") or ""
164+
length = int(
165+
meta.get("length") or 0
166+
) # فعلاً استفاده نمی‌کنیم ولی برای آینده خوبه
161167

162168
alert: Optional[Dict[str, Any]] = None
163169

@@ -176,7 +182,13 @@ def analyze_packet(self, meta: Dict[str, Any]) -> Optional[Dict[str, Any]]:
176182
}
177183

178184
# ---------------- Port Scan detection ----------------
179-
if alert is None and src and dst and dport is not None and proto in ("TCP", "UDP"):
185+
if (
186+
alert is None
187+
and src
188+
and dst
189+
and dport is not None
190+
and proto in ("TCP", "UDP")
191+
):
180192
key = (src, dst)
181193
arr2 = self.port_scan_map[key]
182194
arr2.append((now, int(dport)))

core/incident_engine.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import hashlib
1616
from dataclasses import dataclass
17-
from datetime import datetime, timedelta
17+
from datetime import datetime, timedelta, timezone
1818
from typing import Any, Dict, Optional, Tuple
1919

2020

@@ -47,21 +47,25 @@ def __init__(self, window_sec: int = 120) -> None:
4747
self.window = timedelta(seconds=int(window_sec))
4848
self._incidents: Dict[Tuple[str, str, str, str, str], Incident] = {}
4949

50-
def _make_key(self, meta: Dict[str, Any], alert: Dict[str, Any]) -> Tuple[str, str, str, str, str]:
50+
def _make_key(
51+
self, meta: Dict[str, Any], alert: Dict[str, Any]
52+
) -> Tuple[str, str, str, str, str]:
5153
src = _safe_str(meta.get("src"))
5254
dst = _safe_str(meta.get("dst"))
5355
atk = _safe_str(alert.get("attack_type") or alert.get("attack") or "Alert")
5456
dport = _safe_str(meta.get("dport"))
5557
eng = _safe_str(alert.get("engine") or "IDS")
5658
return (src, dst, atk, dport, eng)
5759

58-
def _make_id(self, key: Tuple[str, str, str, str, str], first_seen: datetime) -> str:
60+
def _make_id(
61+
self, key: Tuple[str, str, str, str, str], first_seen: datetime
62+
) -> str:
5963
base = "|".join(key) + "|" + first_seen.strftime("%Y%m%d%H%M%S")
6064
h = hashlib.sha1(base.encode("utf-8", errors="ignore")).hexdigest()
6165
return h[:12]
6266

6367
def enrich_alert(self, meta: Dict[str, Any], alert: Dict[str, Any]) -> None:
64-
now = datetime.utcnow()
68+
now = datetime.now(timezone.utc)
6569
key = self._make_key(meta, alert)
6670

6771
# Parse normalized score if present

0 commit comments

Comments
 (0)