fix: remplace les scores -1.00/0.00 sentinel par des badges textuels

Les entrées ANUBIS_DENY et KNOWN_BOT ont des scores hardcodés (-1.0 et 0.0)
qui ne sont pas calculés par l'IsolationForest mais servent de sentinels.
Les afficher comme un score numérique est trompeur.

ScoreBadge accepte maintenant threatLevel, botName et anubisAction:
- ANUBIS_DENY / anubis_bot_action='DENY' → badge 'RÈGLE' (rouge)
- KNOWN_BOT / bot_name non-vide       → badge 'BOT'   (vert)
- Tout autre cas                       → score IF numérique [-1, 0]

DetectionRow étendu avec threat_level? et bot_name? pour accéder
aux champs retournés par l'API dans le rendu de la colonne Score.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
SOC Analyst
2026-03-19 18:15:14 +01:00
parent 533072a157
commit ee54034ffd

View File

@ -23,6 +23,8 @@ interface DetectionRow {
client_headers?: string; client_headers?: string;
model_name: string; model_name: string;
anomaly_score: number; anomaly_score: number;
threat_level?: string;
bot_name?: string;
hits?: number; hits?: number;
hit_velocity?: number; hit_velocity?: number;
asn_org?: string; asn_org?: string;
@ -281,7 +283,14 @@ export function DetectionsList() {
label: col.label, label: col.label,
sortable: true, sortable: true,
align: 'right' as const, align: 'right' as const,
render: (_, row) => <ScoreBadge score={row.anomaly_score} />, render: (_, row) => (
<ScoreBadge
score={row.anomaly_score}
threatLevel={row.threat_level}
botName={row.bot_name}
anubisAction={row.anubis_bot_action}
/>
),
}; };
case 'hits': case 'hits':
return { return {
@ -539,7 +548,38 @@ function ModelBadge({ model }: { model: string }) {
} }
// Composant ScoreBadge // Composant ScoreBadge
function ScoreBadge({ score }: { score: number }) { // Les scores non-IF (ANUBIS_DENY, KNOWN_BOT) sont stockés comme sentinels
// (-1.0 et 0.0) et doivent être affichés comme des badges textuels,
// pas comme des scores numériques calculés par l'IsolationForest.
function ScoreBadge({
score,
threatLevel,
botName,
anubisAction,
}: {
score: number;
threatLevel?: string;
botName?: string;
anubisAction?: string;
}) {
// ANUBIS_DENY : menace identifiée par règle, pas par IF
if (threatLevel === 'ANUBIS_DENY' || anubisAction === 'DENY') {
return (
<span className="inline-flex items-center text-xs px-1.5 py-0.5 rounded border bg-red-500/15 text-red-400 border-red-500/30 font-medium">
RÈGLE
</span>
);
}
// KNOWN_BOT : bot légitime identifié par dictionnaire ou Anubis ALLOW
if (threatLevel === 'KNOWN_BOT' || (botName && botName !== '')) {
return (
<span className="inline-flex items-center text-xs px-1.5 py-0.5 rounded border bg-green-500/15 text-green-400 border-green-500/30 font-medium">
BOT
</span>
);
}
// Score IF réel
let color = 'text-threat-low'; let color = 'text-threat-low';
if (score < -0.3) color = 'text-threat-critical'; if (score < -0.3) color = 'text-threat-critical';
else if (score < -0.15) color = 'text-threat-high'; else if (score < -0.15) color = 'text-threat-high';