The Same Data, Two Windows, Opposite Answers
Here is an alert. An account recorded two failed authentication attempts in twenty four hours, from two separate addresses, using a legacy protocol.
What is your assessment?
Most analysts would say a stale password. A phone, a copier, a mail client somewhere with credentials that no longer work, retrying occasionally. Two attempts is nothing. It is the sort of thing you note and move on from.
That was our rule's assessment too. It graded the case medium and the narrative said, in as many words, that a small number of source addresses more often indicates a device holding an outdated password than an attack.
The Same Account, Seven Days
Fifteen attempts. Fourteen distinct addresses.
That is not a device. A device has an address and keeps it. Fourteen addresses producing one attempt each is rotation, and rotation is deliberate: it is how a credential attack stays below per-address rate limits and per-account lockout thresholds.
The account was under a distributed password attack. The rule had described it as probably a misconfigured phone, in an alert narrative written to be persuasive.
Nothing Was Broken
The query was correct. The grading logic was correct. The narrative was well-written and matched the data it was given.
The window was wrong, and everything downstream inherited the error.
This is worth sitting with, because it is not a bug anybody would find by reading the code. The rule did exactly what it was built to do. It was built to look at the wrong span of time.
Analysis Window And Alerting Window Are Different
The confusion comes from using one window for two jobs.
The alerting window answers: has something happened that I should report now? It should be short, because latency matters.
The analysis window answers: what does this mean? It should be as long as the behaviour it describes, which for a slow campaign is weeks.
Collapsing them into one number forces a choice between fast alerting and correct interpretation. Separating them costs one additional aggregation.
history AS (
SELECT account, min(ts) AS first_seen,
count() AS events_30d,
uniqExact(source) AS sources_30d
FROM events
WHERE ts > now() - INTERVAL 30 DAY
GROUP BY account
)
Join that against the recent window. Alert on the recent. Grade on the history. Put both figures in the narrative so the reader can see the difference for themselves.
The Corrected Narrative
Once the rule read the longer history, the text changed to something that actually helped:
Across the seven day history this account has been reached from fourteen
distinct addresses for fifteen attempts, which is roughly one attempt per
address. That is deliberate rotation to remain below per-address rate limits
and per-account lockout thresholds, not a misconfigured client repeating a
stored password. Note that the volume within any single day may look small;
the pattern is only visible across the whole period, which is why the seven
day figures are given here.
The last sentence is the important one. It tells the analyst why the number in front of them looks unremarkable, which stops them from reaching the conclusion the rule reached before it was fixed.
What To Check
For every rule you run that aggregates, ask what the shortest campaign is that would be invisible in its window. If the answer is anything a real adversary would run, the window is too short.
Slow is not the same as harmless. Frequently it is the opposite, because slow is what somebody chooses when they expect to be watched.