detect.ss: faithful label-level DGA dedup via dga-label kernel

ober

d75d25b3d3ee12101f10690ff01f35a90b851e82

diff --git a/jsecmon/detect.ss b/jsecmon/detect.ss
index 8dd064b..b4a1a29 100644
--- a/jsecmon/detect.ss
+++ b/jsecmon/detect.ss
@@ -29,7 +29,7 @@
                   make-date make-time)
           (except (jerboa prelude) meta atom?)
           (only (jsecmon kernels)
-                lolbin-score-cmdline lolbin-severity dga-score-domain))
+                lolbin-score-cmdline lolbin-severity dga-score-domain dga-label))
 
   (def lolbin-threshold 50)
   (def dga-threshold 60)
@@ -82,9 +82,11 @@
       events))
 
   ;; ── rule: DGA / high-entropy DNS query ──────────────────────────────────────
-  ;; One alert per suspect domain. secmon dedups on (host, process_name, winning
-  ;; label); we key on the full query_name (the kernel returns the max score, not
-  ;; which label won) — equivalent for distinct domains, finer-grained otherwise.
+  ;; One alert per suspect domain, deduped on (host, process_name, winning label)
+  ;; like secmon's HashMap::or_insert — so a host querying many subdomains of one
+  ;; DGA label (the leftmost label `dga-label` returns) alerts once. The faithful
+  ;; storage-Rule port with the full reason breakdown is (jsecmon dga); this is
+  ;; the live event-pipeline shape feeding (jsecmon analytics).
   (def (detect-dga-domain events)
     (let ((seen (make-hash-table))
           (out '()))
@@ -92,20 +94,21 @@
         (lambda (ev)
           (when (string=? (a-str ev "event_type") "dns_query")
             (let ((qname (a-str (ev-data ev) "query_name")))
-              (when (and (not (string=? qname ""))
-                         (>= (dga-score-domain qname) dga-threshold))
-                (let* ((host (a-str ev "host"))
-                       (pname (or (ev-pname ev) ""))
-                       (k (string-append host "\x1f;" pname "\x1f;" qname)))
-                  (unless (hash-get seen k)
-                    (let ((a (make-anomaly "dga_domain" host "high"
-                               (a-num ev "timestamp_ms" 0)
-                               (details-hash "process_name" (if (string=? pname "") "?" pname)
-                                             "pid" (hash-get ev "pid")
-                                             "query_name" qname
-                                             "score" (dga-score-domain qname)))))
-                      (hash-put! seen k a)
-                      (set! out (cons a out)))))))))
+              (unless (string=? qname "")
+                (let ((score (dga-score-domain qname)))
+                  (when (>= score dga-threshold)
+                    (let* ((host (a-str ev "host"))
+                           (pname (or (ev-pname ev) ""))
+                           (k (string-append host "\x1f;" pname "\x1f;" (dga-label qname))))
+                      (unless (hash-get seen k)
+                        (let ((a (make-anomaly "dga_domain" host "high"
+                                   (a-num ev "timestamp_ms" 0)
+                                   (details-hash "process_name" (if (string=? pname "") "?" pname)
+                                                 "pid" (hash-get ev "pid")
+                                                 "query_name" qname
+                                                 "score" score))))
+                          (hash-put! seen k a)
+                          (set! out (cons a out)))))))))))
         events)
       (reverse out)))