DEV Community

Cover image for Blue Watch, Day 4-6: From Alerts to a Story
Luna Meadows
Luna Meadows

Posted on

Blue Watch, Day 4-6: From Alerts to a Story

If you've been following along, Blue Watch is my mini-SIEM: parse logs, detect suspicious patterns, generate alerts, all config-first. Day 1-3 got me a parser, a rules file, and a detector that fires four correct alerts on my test attack scenario with zero false positives.

Four alerts is nice. But four alerts sitting in a list still isn't an incident. That's what the last three days were about: turning raw alerts into a score, and a score into a story.

*The problem: one attacker, four disguises
*

My test scenario has a single attacker brute-forcing admin, spinning up a backdoor user shelly, and reading /etc/shadow. But the alerts that come out of the detector don't know they're all the same person. One alert is tagged by IP. Another is tagged by username. Without something to unify them, my "incident" looks like four unrelated blips instead of one attacker's full campaign.

That's the job of scorer.py.

build_ip_to_user_map() and get_entity()

First step: figure out which IPs and users actually belong together. build_ip_to_user_map() walks the event stream and links every IP to the users who logged in from it.

Then get_entity() decides who an alert really "belongs to," in priority order:

first_user if the alert carries one (this covers privilege-escalation alerts, where the original attacker identity matters more than whoever they became)
user, if present
otherwise, the IP mapped back to a user via the map from step one

That fallback chain is the whole trick. It's the difference between four disconnected alerts and one entity with four alerts against its name.

Scoring severity into a single number

Once every alert has an owner, score_alerts() sums up severity points per entity:

low: 20
medium: 50
high: 70
critical: 100

Then score_to_level() converts the total back into a human label: LOW, MEDIUM, HIGH, or CRITICAL. Simple, but it's the layer that turns "a pile of alerts" into "a number a human can triage against."

The output layer

Two ways to see the result:

save_alerts_json() writes a structured output/alerts.json — machine-readable, ready to feed into whatever comes next.
print_report_table() renders the same data as a clean terminal table, for when I just want to glance at it.

Run against the test scenario, everything unifies the way it should: admin → 290 points → CRITICAL, with all four alerts correctly attributed to one entity. Exactly what four separate, disguised alerts should have added up to.

Day 6: the timeline

Scoring answers "how bad is this?" The last piece answers "what actually happened, in order?"

timeline.py is deliberately small:

build_timeline() sorts every event chronologically. The nice trick here: ISO 8601 timestamps sort correctly as plain strings, so there's no datetime parsing needed — just a string sort.
print_timeline() renders the play-by-play with generic field display, meaning it doesn't need special-case logic for each event type. Any event shape that comes out of the parser just... prints.

The payoff is the full 12-event story, in order, with luna's ordinary login sitting exactly where it should — mid-sequence, clearly harmless noise next to the actual attack. That's the detail I was hoping for: a good detector doesn't just catch the bad stuff, it also stays quiet about the normal stuff sitting right next to it.

Top comments (1)

Collapse
 
topstar_ai profile image
Luis Cruz

I was particularly interested in the build_ip_to_user_map() and get_entity() functions, as they seem to be the key to unifying alerts from the same attacker. The fallback chain in get_entity() is a clever solution to handle different scenarios, such as privilege-escalation alerts. I've worked on similar projects where entity resolution was crucial, and I've found that using a combination of IP and user mapping can be very effective. Have you considered adding any additional factors to the entity resolution process, such as device or location information, to further improve the accuracy of the alerts?