Scoring & Alerting#
Starting with v2.2, Melissae replaces the continuous weighted-signal scoring engine with a rule-based alerting engine. Detection logic lives in declarative YAML rules under rules/, and each observed IP’s verdict is computed by summing, for every matching rule, a base severity plus a logarithmic boost on the number of alerts emitted by that rule over a rolling 90-day window, capped at 100. The logarithmic growth keeps the Suspicious band reachable and prevents a single repeating low-severity rule from collapsing instantly into Malicious.
Scale & Verdicts#
Scores still use a 0–100 scale. Verdict thresholds are unchanged:
Range |
Verdict |
Description |
|---|---|---|
0–29 |
Benign |
Observed in the logs but never matched a rule, or matched only low-value signals (passive scans, single connections). Includes a dedicated |
30–69 |
Suspicious |
Active scanning, failed auth, reconnaissance |
70–100 |
Malicious |
Compromise, post-exploitation, ICS tampering, confirmed CVE exploitation |
Rule Format#
Each rule is a YAML file under rules/ (one file per rule, named MLSxxx.yml):
id: MLS008
name: SSH brute-force attempt
description: >
Multiple SSH authentication failures from the same IP within a short
window. Indicates credential-stuffing or brute-force activity.
severity: high
enabled: true
schedule: "*/1 * * * *"
lookback: 1m
mql: 'protocol:ssh AND action:Failed'
group_by: ip
threshold: 3
score: 40
tags: [brute-force, credential-access]
mitre: [T1110]
Field |
Meaning |
|---|---|
|
Stable rule identifier (e.g. |
|
Human-readable label and longer explanation surfaced in the dashboard. |
|
One of |
|
Boolean; disabled rules are loaded but never evaluated. |
|
Cron expression controlling how often the rule is re-evaluated by |
|
Time window scanned at each run ( |
|
Detection query in Melissae Query Language (the same DSL as the dashboard search bar). |
|
Aggregation key (typically |
|
Minimum number of matching events in the window required to emit an alert. |
|
Points contributed to the per-IP verdict when the rule fires. |
|
Free-form tags and MITRE ATT&CK technique IDs for classification. |
Built-in Rules#
Twelve rules ship with v2.6, covering the signals previously hard-coded in the scoring engine:
ID |
Detection |
Severity |
Score |
Notes |
|---|---|---|---|---|
MLS001 |
Telnet CVE-2026-24061 auth-bypass ( |
critical |
85 |
Targeted exploitation of the Telnet auth-bypass honeypot |
MLS002 |
FTP brute-force ( |
high |
30 |
≥5 failures / 5 min |
MLS003 |
Malicious FTP file activity (LIST / PUT / GET / DELETE / RMDIR) |
critical |
70 |
Hands-on activity post-auth |
MLS004 |
Successful FTP login ( |
critical |
70 |
Any success on the honeypot |
MLS005 |
HTTP request burst / web scanning (static assets filtered out) |
low |
10 |
≥15 dynamic requests / 3 min |
MLS006 |
HTTP probing of sensitive paths ( |
medium |
15 |
≥3 hits / 3 min |
MLS007 |
Modbus write operation — ICS tampering |
high |
45 |
Any write attempt |
MLS008 |
SSH brute-force ( |
high |
40 |
≥3 failures / 1 min |
MLS009 |
Post-compromise SSH command ( |
critical |
70 |
Hands-on-keyboard activity |
MLS010 |
Successful SSH login ( |
critical |
70 |
Any success on the honeypot |
MLS011 |
Successful Telnet login ( |
critical |
60 |
Deprecated protocol, IoT-botnet signal |
MLS012 |
Nmap scan ( |
low |
5 |
User-agent based recon detection |
The complete set of rules — including exact MQL queries, thresholds and scores — is the source of truth in the rules/ directory and is also exposed by the API at GET /api/rules.
How Scoring Works#
rule_engine.pyloads every YAML file inrules/(configurable viaMELISSAE_RULES_DIR).For each enabled rule whose
scheduleis due, it pulls the logs of the lastlookbackwindow, runs themqlquery against them and groups the matches bygroup_by.Every group with at least
thresholdmatches produces an alert in MongoDB (alertscollection) carryingrule_id,severity,score,ip, time range and matching log references.threatIntel.pyaggregates alerts (rolling 90-day window) per IP into thethreatscollection. Each matching rule contributes\[\mathrm{contrib}(r) = r.\mathrm{score} + 5 \cdot \log_2(\max(1,\, r.\mathrm{count}))\]and the final verdict score is
\[\mathrm{score}(\mathrm{ip}) = \min\!\left(100,\; \sum_{r \in \text{matched rules}} \mathrm{contrib}(r)\right)\]where
r.countis the number of alerts emitted by rulerfor that IP in the window. The verdict label follows the table above.The same job also performs a passive aggregation over the
logscollection: any IP observed in the logs that has not raised a single alert receives athreatsdocument with score0, verdictbenignandpassive: true. This ensures the dashboard reflects every IP the hive has actually seen, not only the ones that fired a rule.The dashboard consumes
threats(Threat Intelligence, Map) andalerts(Alerts page) to drive its views.
This design makes detection logic transparent and auditable: every score increment is traceable to a specific rule, and operators can enable/disable, retune or extend rules without touching engine code.
Melissae Query Language (MQL)#
MQL is the small DSL used both by the dashboard search bar and by the mql field of each rule. Supported features:
Field-scoped terms —
protocol:ssh,action:"Login failed",ip:1.2.3.4,cve:CVE-2026-24061,user:root,user-agent:nmap,path:/admin,hour:14,date:2026-05-09,agent:my-agent.Free-text terms — bare words match against any field.
Boolean operators —
AND/OR/NOT(alsoand/or/!), with parenthesized grouping.Quoted values —
"Login failed"to match phrases containing spaces.
Examples#
protocol:ssh AND action:Failed
protocol:ftp AND action:"Login failed"
protocol:modbus AND action:write
cve:CVE-2026-24061
protocol:http AND nmap
ip:192.168.1.10 AND NOT action:successful