docs: add standardized comments to all services (Python, Go, Bash)

- Add docs/commenting-standard.md defining per-language comment standards
  (Go godoc, Python PEP-257, C Doxygen, Bash header blocks, SQL banners)

- services/dashboard: 100% docstring coverage (100/100 functions)
  - All FastAPI route handlers, helpers, classes, and models documented
  - Language: French (project convention)

- services/bot-detector: 100% docstring coverage (53/53 symbols)
  - bot_detector.py: 14 functions + module docstring
  - anubis/fetch_rules.py: 9 functions

- shared/python/ja4_common: full docstrings on ClickHouseClient (7 methods)
  and ClickHouseSettings class

- services/correlator: 24 godoc comments added across 6 Go files
  - correlation_service.go: 10 private helpers
  - unixsocket/source.go: 6 parsing/socket helpers
  - correlated_log.go: 4 field extraction helpers
  - orchestrator.go, logger.go, main.go: 4 comments

- services/correlator/scripts/audit-architecture.sh: standardized header block

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
toto
2026-04-07 21:32:29 +02:00
parent 12d60975da
commit 3dfeba860b
22 changed files with 388 additions and 10 deletions

View File

@ -1 +1 @@
# Backend package
"""Package principal du backend FastAPI bot-detector."""

View File

@ -5,6 +5,7 @@ from pydantic_settings import BaseSettings
class Settings(BaseSettings):
"""Paramètres de configuration de l'application chargés depuis l'environnement."""
# ClickHouse
CLICKHOUSE_HOST: str = "clickhouse"
CLICKHOUSE_PORT: int = 8123
@ -22,6 +23,7 @@ class Settings(BaseSettings):
CORS_ORIGINS: list = ["http://localhost:3000", "http://127.0.0.1:3000"]
class Config:
"""Configuration Pydantic pour le chargement du fichier .env."""
env_file = ".env"
case_sensitive = True

View File

@ -8,6 +8,7 @@ from enum import Enum
class ThreatLevel(str, Enum):
"""Niveaux de menace supportés par le modèle de détection."""
CRITICAL = "CRITICAL"
HIGH = "HIGH"
MEDIUM = "MEDIUM"
@ -19,6 +20,7 @@ class ThreatLevel(str, Enum):
# ─────────────────────────────────────────────────────────────────────────────
class MetricsSummary(BaseModel):
"""Résumé agrégé des métriques sur les dernières 24 heures."""
total_detections: int
critical_count: int
high_count: int
@ -30,6 +32,7 @@ class MetricsSummary(BaseModel):
class TimeSeriesPoint(BaseModel):
"""Point de série temporelle par heure pour les métriques."""
hour: datetime
total: int
critical: int
@ -39,6 +42,7 @@ class TimeSeriesPoint(BaseModel):
class MetricsResponse(BaseModel):
"""Réponse complète des métriques du dashboard avec série temporelle."""
summary: MetricsSummary
timeseries: List[TimeSeriesPoint]
threat_distribution: Dict[str, int]
@ -49,6 +53,7 @@ class MetricsResponse(BaseModel):
# ─────────────────────────────────────────────────────────────────────────────
class Detection(BaseModel):
"""Représentation d'une détection d'anomalie émise par le modèle ML."""
detected_at: datetime
src_ip: str
ja4: str
@ -82,6 +87,7 @@ class Detection(BaseModel):
class DetectionsListResponse(BaseModel):
"""Liste paginée de détections d'anomalies."""
items: List[Detection]
total: int
page: int
@ -94,6 +100,7 @@ class DetectionsListResponse(BaseModel):
# ─────────────────────────────────────────────────────────────────────────────
class AttributeValue(BaseModel):
"""Valeur d'attribut avec comptage, pourcentage et métadonnées temporelles."""
value: str
count: int
percentage: float
@ -105,6 +112,7 @@ class AttributeValue(BaseModel):
class VariabilityAttributes(BaseModel):
"""Ensemble des attributs de variabilité comportementale pour une entité."""
user_agents: List[AttributeValue] = Field(default_factory=list)
ja4: List[AttributeValue] = Field(default_factory=list)
countries: List[AttributeValue] = Field(default_factory=list)
@ -115,11 +123,13 @@ class VariabilityAttributes(BaseModel):
class Insight(BaseModel):
"""Message d'analyse contextuelle (alerte, information ou succès)."""
type: str # "warning", "info", "success"
message: str
class VariabilityResponse(BaseModel):
"""Réponse d'analyse de variabilité pour un attribut donné."""
type: str
value: str
total_detections: int
@ -134,11 +144,13 @@ class VariabilityResponse(BaseModel):
# ─────────────────────────────────────────────────────────────────────────────
class AttributeListItem(BaseModel):
"""Élément de la liste des valeurs uniques d'un attribut avec son comptage."""
value: str
count: int
class AttributeListResponse(BaseModel):
"""Réponse de la liste des valeurs uniques pour un type d'attribut."""
type: str
items: List[AttributeListItem]
total: int
@ -149,6 +161,7 @@ class AttributeListResponse(BaseModel):
# ─────────────────────────────────────────────────────────────────────────────
class UserAgentValue(BaseModel):
"""Valeur de User-Agent avec comptage et plage temporelle d'observation."""
value: str
count: int
percentage: float
@ -157,6 +170,7 @@ class UserAgentValue(BaseModel):
class UserAgentsResponse(BaseModel):
"""Réponse de la liste des User-Agents associés à une entité."""
type: str
value: str
user_agents: List[UserAgentValue]
@ -169,12 +183,14 @@ class UserAgentsResponse(BaseModel):
# ─────────────────────────────────────────────────────────────────────────────
class ClassificationLabel(str, Enum):
"""Étiquettes de classification SOC pour les IPs et fingerprints JA4."""
LEGITIMATE = "legitimate"
SUSPICIOUS = "suspicious"
MALICIOUS = "malicious"
class ClassificationBase(BaseModel):
"""Modèle de base partagé pour les classifications SOC."""
ip: Optional[str] = None
ja4: Optional[str] = None
label: ClassificationLabel
@ -198,6 +214,7 @@ class Classification(ClassificationBase):
class ClassificationsListResponse(BaseModel):
"""Liste paginée des classifications SOC enregistrées."""
items: List[Classification]
total: int

View File

@ -1 +1 @@
# Routes package
"""Package des routes FastAPI de l'API bot-detector."""

View File

@ -374,6 +374,7 @@ async def analyze_user_agents(ip: str):
# Classification des UAs
def classify_ua(ua: str) -> str:
"""Classe un User-Agent en 'bot', 'script', 'browser' ou 'unknown'."""
ua_lower = ua.lower()
if any(bot in ua_lower for bot in ['bot', 'crawler', 'spider', 'curl', 'wget', 'python', 'requests', 'scrapy']):
return 'bot'

View File

@ -10,6 +10,7 @@ router = APIRouter(prefix="/api/botnets", tags=["botnets"])
def _botnet_class(unique_countries: int) -> str:
"""Classifie un JA4 selon sa dispersion géographique."""
if unique_countries > 100:
return "global_botnet"
if unique_countries > 20:

View File

@ -222,6 +222,7 @@ def _run_clustering_job(k: int, hours: int, sensitivity: float = 1.0) -> None:
continue
def avg_f(key: str, crows: list[dict] = cluster_rows[j]) -> float:
"""Calcule la moyenne flottante d'un champ numérique sur les lignes du cluster."""
return float(np.mean([float(r.get(key) or 0) for r in crows]))
mean_ttl = avg_f("ttl")
@ -245,6 +246,7 @@ def _run_clustering_job(k: int, hours: int, sensitivity: float = 1.0) -> None:
orgs = [str(r.get("asn_org") or "") for r in cluster_rows[j] if r.get("asn_org")]
def topk(lst: list[str], n: int = 5) -> list[str]:
"""Retourne les n valeurs les plus fréquentes d'une liste (valeurs vides exclues)."""
return [v for v, _ in Counter(lst).most_common(n) if v]
radar = [

View File

@ -489,6 +489,7 @@ async def get_ua_analysis(
def _build_ua_risk_flags(ua: str, ua_type: str, unique_ja4s: int, ip_count: int) -> list:
"""Construit la liste des indicateurs de risque pour un User-Agent."""
flags = []
if ua_type == "bot":
flags.append("ua_bot_signature")

View File

@ -144,6 +144,7 @@ async def get_metrics_baseline():
row = r.result_rows[0] if r.result_rows else None
def pct_change(today: int, yesterday: int) -> float:
"""Calcule la variation en pourcentage entre aujourd'hui et hier. Retourne 100 si hier=0 et aujourd'hui>0."""
if yesterday == 0:
return 100.0 if today > 0 else 0.0
return round((today - yesterday) / yesterday * 100, 1)

View File

@ -11,6 +11,7 @@ router = APIRouter(prefix="/api/ml", tags=["ml_features"])
def _attack_type(fuzzing_index: float, hit_velocity: float,
is_fake_nav: int, ua_ch_mismatch: int) -> str:
"""Déduit le type d'attaque depuis les métriques comportementales."""
if fuzzing_index > 50:
return "brute_force"
if hit_velocity > 1.0:
@ -113,6 +114,7 @@ async def get_ip_radar(ip: str):
row = result.result_rows[0]
def _f(v) -> float:
"""Convertit une valeur nullable en float (None ou falsy → 0.0)."""
return float(v or 0)
return {