<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Luna Meadows</title>
    <description>The latest articles on DEV Community by Luna Meadows (@luna_meadows).</description>
    <link>https://dev.to/luna_meadows</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4037025%2F0c052ada-e949-4059-ae1b-2d2b7b11a984.jpg</url>
      <title>DEV Community: Luna Meadows</title>
      <link>https://dev.to/luna_meadows</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/luna_meadows"/>
    <language>en</language>
    <item>
      <title>Glasshouse — Building a Home SOC Lab From Scratch (and Attacking It Myself)</title>
      <dc:creator>Luna Meadows</dc:creator>
      <pubDate>Thu, 06 Aug 2026 09:15:58 +0000</pubDate>
      <link>https://dev.to/luna_meadows/glasshouse-building-a-home-soc-lab-from-scratch-and-attacking-it-myself-244g</link>
      <guid>https://dev.to/luna_meadows/glasshouse-building-a-home-soc-lab-from-scratch-and-attacking-it-myself-244g</guid>
      <description>&lt;p&gt;I'm a third-year Cybersecurity student, and I'm aiming at Blue Team work — SOC Analyst first, then DFIR, then eventually threat hunting and detection engineering. The problem with that career path when you're still in college is obvious: nobody hands a sophomore a real SIEM and a real incident to work. So I built one.&lt;/p&gt;

&lt;p&gt;This post is a walkthrough of &lt;strong&gt;Glasshouse&lt;/strong&gt;, a home SOC lab I built to simulate exactly what a junior SOC analyst does on the job: collect logs, get attacked, catch it, and write it up. I'm not going to pretend everything went smoothly, because it didn't — and honestly, the parts that broke taught me more than the parts that worked first try.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Three machines, bridged onto the same network so traffic actually has to travel between them instead of staying inside one hypervisor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows 10 VM (victim)&lt;/strong&gt; — 4GB RAM, 60GB disk, running Sysmon with the SwiftOnSecurity config. Sysmon is the piece that makes Windows logging actually useful for detection — process creation with full command lines, registry modifications, network connections, the works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kali (attacker)&lt;/strong&gt; — running on a separate physical laptop rather than as another VM on the same box, connected over the same home WiFi via bridged networking. I wanted the attack traffic to be real network traffic, not loopback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ubuntu Server VM (the brain)&lt;/strong&gt; — self-hosted &lt;strong&gt;Wazuh&lt;/strong&gt;, running the manager, indexer, and dashboard all on one box.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Windows machine ships its Sysmon and Windows Event logs to the Wazuh agent, which forwards them to the manager, which indexes and correlates them, which shows up on the dashboard. Simple pipeline in theory. Not simple in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Everything that broke (and why I'm glad it did)
&lt;/h3&gt;

&lt;p&gt;If I only listed the finished architecture, this section wouldn't exist — and it's the most useful part of the project. Along the way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VirtualBox corrupted itself badly enough that I switched to VMware entirely&lt;/li&gt;
&lt;li&gt;I locked myself out of the Ubuntu box and had to recover the password&lt;/li&gt;
&lt;li&gt;The Ubuntu disk was undersized for Wazuh's indexer and needed an LVM resize&lt;/li&gt;
&lt;li&gt;A Wazuh install got into a broken state and had to be wiped and reinstalled clean&lt;/li&gt;
&lt;li&gt;Moving Kali to a separate physical laptop meant re-IPing the whole lab network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the one that mattered most: after everything was "installed," Wazuh wasn't actually collecting Sysmon events at all. The dashboard was just quiet. I dug into &lt;code&gt;ossec.conf&lt;/code&gt; on the Windows agent and found the Sysmon event channel simply wasn't configured to be collected — an easy thing to miss, an easy thing to fix once you know to look, and exactly the kind of "why is nothing showing up" troubleshooting that real SOC work is full of.&lt;/p&gt;

&lt;p&gt;Once that was fixed, I confirmed the full pipeline end-to-end: &lt;strong&gt;Sysmon → Agent → Wazuh Manager → Dashboard.&lt;/strong&gt; Logs flowing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attacking my own lab
&lt;/h2&gt;

&lt;p&gt;Detection you haven't tested is just a hope. So I ran five techniques from the Atomic Red Team library against my own Windows box, mapped to MITRE ATT&amp;amp;CK, and checked whether Wazuh actually caught each one — not just "did an alert fire" but "does the telemetry tell a real story."&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;T1059.001&lt;/strong&gt; — Encoded PowerShell&lt;/td&gt;
&lt;td&gt;Base64 &lt;code&gt;-EncodedCommand&lt;/code&gt; execution&lt;/td&gt;
&lt;td&gt;Caught, decoded, and documented&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;T1003.002&lt;/strong&gt; — Credential dumping&lt;/td&gt;
&lt;td&gt;Exporting SAM/System/Security registry hives&lt;/td&gt;
&lt;td&gt;Caught — 2 of 3 hives exported; the Security hive was blocked by permissions, which is actually the realistic outcome&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;T1547.001&lt;/strong&gt; — Registry persistence&lt;/td&gt;
&lt;td&gt;RunOnce key persistence&lt;/td&gt;
&lt;td&gt;Caught by both Sysmon's own tagging and Wazuh's rule engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;T1055&lt;/strong&gt; — Process injection&lt;/td&gt;
&lt;td&gt;Classic injection technique&lt;/td&gt;
&lt;td&gt;Never even ran — Windows Defender blocked it outright&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;T1083&lt;/strong&gt; — Discovery&lt;/td&gt;
&lt;td&gt;Recursive &lt;code&gt;dir /s&lt;/code&gt; enumeration&lt;/td&gt;
&lt;td&gt;Caught via the enumeration pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That T1055 result is worth pausing on. It's tempting to want every technique to "succeed" so you have a detection to show off, but Defender blocking it before Wazuh even got a chance is a legitimate defense-in-depth story — the lab isn't just testing my SIEM, it's testing the whole stack.&lt;/p&gt;

&lt;p&gt;The standout result, though, was a &lt;strong&gt;Hydra brute-force attack&lt;/strong&gt; launched from Kali against the Windows box. Individual failed logons on their own aren't interesting — but Wazuh correlated them into a single "Multiple Windows Logon Failures" alert (T1110), and that correlation then triggered Windows' own account lockout mechanism (T1531). Watching an attack get caught &lt;em&gt;and&lt;/em&gt; automatically contained, using nothing but Sysmon, Wazuh's correlation rules, and Windows' built-in lockout policy, was the most satisfying moment of the whole project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I hit a real wall
&lt;/h2&gt;

&lt;p&gt;The built-in Wazuh rule that caught the SAM/System/Security export (T1003.002) is generic — it flags registry hive exports in general, not credential dumping specifically. I wanted to write a custom rule that would fire specifically on that credential-dumping pattern.&lt;/p&gt;

&lt;p&gt;I couldn't get it working. The XML was valid, I tried several different rule approaches, and it just never fired. Rather than fudge a fake success or quietly drop it from the writeup, I documented it as an open item. I'd rather show a real unresolved problem than a lab that pretends everything worked — because in an actual SOC, not everything resolves cleanly in one sitting, and knowing how to document a stuck detection is its own skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dashboards and hunting
&lt;/h2&gt;

&lt;p&gt;With the attack data sitting in Wazuh, I built a 3-panel dashboard — &lt;strong&gt;Alerts Over Time&lt;/strong&gt;, &lt;strong&gt;Top MITRE Techniques&lt;/strong&gt;, and &lt;strong&gt;Alerts by Agent&lt;/strong&gt; — to get a SOC-analyst's-eye view of the lab at a glance.&lt;/p&gt;

&lt;p&gt;Then I wrote and validated 5 manual threat-hunting queries against my own data, covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encoded PowerShell execution&lt;/li&gt;
&lt;li&gt;Registry hive exports&lt;/li&gt;
&lt;li&gt;Suspicious parent-child process relationships&lt;/li&gt;
&lt;li&gt;Persistence via registry run keys&lt;/li&gt;
&lt;li&gt;Brute-force logon patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every query returned real hits against the attacks I'd already run — proof the queries actually work, not just that they're syntactically valid.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's left
&lt;/h2&gt;

&lt;p&gt;The infrastructure and attack work is done. What's left is writing it up properly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A one-page incident report for each attack (the kind of artifact a SOC analyst or DFIR responder actually produces after an incident)&lt;/li&gt;
&lt;li&gt;A main README tying the whole project together&lt;/li&gt;
&lt;li&gt;Pushing the full thing to GitHub as &lt;strong&gt;Glasshouse&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm also weighing two stretch goals once the core writeup is done: adding Suricata for network-based detection, and taking another run at that custom Wazuh rule with fresh eyes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;I can't get a SOC job to get SOC experience, so I built the experience myself — including the parts that broke. If you're on the same Blue Team path and stuck at "how do I get hands-on without a job," this is the answer: stand up a target, attack it, and make yourself prove the detection actually caught it. The infrastructure pain is not a detour from that goal — it &lt;em&gt;is&lt;/em&gt; the goal.&lt;/p&gt;

&lt;p&gt;The full repo, with all the detection rules, hunting queries, and incident writeups, will be up on GitHub soon: &lt;strong&gt;&lt;a href="https://github.com/the-opalgupta/Glass-House" rel="noopener noreferrer"&gt;https://github.com/the-opalgupta/Glass-House&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm a Blue Team–focused Cybersecurity student and Blue Team Lead at my college's cybersecurity club. If you're working through something similar, I'd love to hear how your setup differs from mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>blueteam</category>
      <category>dfir</category>
      <category>homelab</category>
    </item>
    <item>
      <title>Blue Watch: in all its glory</title>
      <dc:creator>Luna Meadows</dc:creator>
      <pubDate>Fri, 24 Jul 2026 10:12:51 +0000</pubDate>
      <link>https://dev.to/luna_meadows/blue-watch-in-all-its-glory-19a2</link>
      <guid>https://dev.to/luna_meadows/blue-watch-in-all-its-glory-19a2</guid>
      <description>&lt;p&gt;Spent the last few weeks building something I've wanted to understand properly for a while: how a SIEM actually thinks.&lt;/p&gt;

&lt;p&gt;Not using one. Building one.&lt;/p&gt;

&lt;p&gt;Blue Watch is a mini SIEM I built from scratch in Python. No fancy platform, no black box, just logs, regex, and a lot of "wait, why isn't this matching."&lt;/p&gt;

&lt;p&gt;Here's what building it actually taught me:&lt;/p&gt;

&lt;p&gt;Logs are messy. Every source speaks its own language, sshd says one thing, sudo says another, Apache says something else entirely. Parsing is just teaching Python to translate all of it into one shared vocabulary.&lt;/p&gt;

&lt;p&gt;Normalization is the real magic. Once every event looks the same underneath (same fields, same shape), detection stops caring where the log came from. Write one rule, it works everywhere.&lt;/p&gt;

&lt;p&gt;Automation is just patience, structured. Rules living in a config file instead of buried in code meant I could add a whole new log source later without touching my detection logic at all. Past me set that up without even realizing how much time it'd save future me.&lt;/p&gt;

&lt;p&gt;Turns out good architecture is a bit like good code and good rhyme,&lt;br&gt;
get the structure right once, and it'll scale with time.&lt;/p&gt;

&lt;p&gt;Genuinely one of the more satisfying builds I've done. Watching a brute force attack chain get reconstructed automatically, timestamped, scored, and MITRE-tagged, from a file I wrote myself, hit different.&lt;/p&gt;

&lt;p&gt;If you're learning security or building your own detection tooling, what's the one concept that finally "clicked" for you the way normalization did for me?&lt;br&gt;
&lt;a href="https://github.com/the-opalgupta/Blue-Watch" rel="noopener noreferrer"&gt;Go check it out yourself :)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fti73dvkh4pq74etug97y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fti73dvkh4pq74etug97y.png" alt=" " width="800" height="380"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi9eorflh532ue1fy7c12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi9eorflh532ue1fy7c12.png" alt=" " width="800" height="380"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fczqbfa99dqubr45y5lha.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fczqbfa99dqubr45y5lha.png" alt=" " width="800" height="380"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fovz5d9ux057kyzvctbhq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fovz5d9ux057kyzvctbhq.png" alt=" " width="800" height="380"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd3ex5nttmnh4k7yomihb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd3ex5nttmnh4k7yomihb.png" alt=" " width="800" height="380"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwgdnnelpagvclkiwi7na.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwgdnnelpagvclkiwi7na.png" alt=" " width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>productivity</category>
      <category>forensics</category>
      <category>security</category>
    </item>
    <item>
      <title>Blue Watch, Day 4-6: From Alerts to a Story</title>
      <dc:creator>Luna Meadows</dc:creator>
      <pubDate>Mon, 20 Jul 2026 13:05:28 +0000</pubDate>
      <link>https://dev.to/luna_meadows/blue-watch-day-4-6-from-alerts-to-a-story-2hb6</link>
      <guid>https://dev.to/luna_meadows/blue-watch-day-4-6-from-alerts-to-a-story-2hb6</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;The problem: one attacker, four disguises&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;That's the job of scorer.py.&lt;/p&gt;

&lt;p&gt;build_ip_to_user_map() and get_entity()&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First step:&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;Then get_entity() decides who an alert really "belongs to," in priority order:&lt;/p&gt;

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

&lt;p&gt;That fallback chain is the whole trick. It's the difference between four disconnected alerts and one entity with four alerts against its name.&lt;/p&gt;

&lt;p&gt;Scoring severity into a single number&lt;/p&gt;

&lt;p&gt;Once every alert has an owner, score_alerts() sums up severity points per entity:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;low: 20&lt;br&gt;
medium: 50&lt;br&gt;
high: 70&lt;br&gt;
critical: 100&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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."&lt;/p&gt;

&lt;p&gt;The output layer&lt;/p&gt;

&lt;p&gt;Two ways to see the result:&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Day 6: the timeline&lt;/p&gt;

&lt;p&gt;Scoring answers "how bad is this?" The last piece answers "what actually happened, in order?"&lt;/p&gt;

&lt;p&gt;timeline.py is deliberately small:&lt;/p&gt;

&lt;p&gt;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.&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>security</category>
      <category>100daysofcode</category>
    </item>
    <item>
      <title>Building Blue Watch: a tiny SIEM with a big attitude</title>
      <dc:creator>Luna Meadows</dc:creator>
      <pubDate>Sun, 19 Jul 2026 21:50:35 +0000</pubDate>
      <link>https://dev.to/luna_meadows/building-blue-watch-a-tiny-siem-with-a-big-attitude-3g5k</link>
      <guid>https://dev.to/luna_meadows/building-blue-watch-a-tiny-siem-with-a-big-attitude-3g5k</guid>
      <description>&lt;p&gt;Okay so I'm building a mini SIEM. If you just went "a mini what?"  same energy I had a week ago. &lt;br&gt;
A &lt;strong&gt;SIEM (Security Information and Event Management system)&lt;/strong&gt; is basically the thing that watches your logs, notices when something shady is happening, and yells about it. Big companies pay big money for big versions of this. I am making a smol one, called Blue Watch, mostly out of stubbornness and curiosity.&lt;br&gt;
Rule number one I set for myself: config over code. Detection logic should live in a config file, not be hardcoded spaghetti buried in if statements. Future me will thank present me. Probably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step zero: fake my own crime scene&lt;/strong&gt;&lt;br&gt;
Before writing a single line of detection logic, I needed logs to detect things in. Real-world logs are messy and you never actually know the "right answer" — so instead I hand-crafted a single deliberate attack story and wrote it out as a realistic auth.log file, syslog-style timestamps and all:&lt;/p&gt;

&lt;p&gt;🕵️ An attacker brute-forces their way into an admin account (lots of failed logins, then... success)&lt;br&gt;
🐚 Admin (now compromised) creates a sneaky new user — a classic "leave myself a backdoor" move&lt;br&gt;
🔓 That new backdoor account immediately goes digging somewhere it really shouldn't&lt;br&gt;
👩‍💻 Meanwhile, a totally innocent employee logs in from a normal IP at roughly the same time, just doing her job&lt;/p&gt;

&lt;p&gt;That last one was the important bit — I needed to know my detector could tell the difference between an attacker and someone just... checking their email.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 1: teaching myself regex, the hard way&lt;/strong&gt;&lt;br&gt;
I will be honest: I did not know regex before this. Now I have opinions about it.&lt;br&gt;
I built a parser that reads raw log lines and pulls structure out of chaos using named capture groups, character classes, and way too much trial and error. Three different line "shapes" (logins, privilege escalations, account creations) each needed their own pattern.&lt;br&gt;
The real win wasn't the regex though — it was writing a normalize() step that takes whatever any of the three patterns spat out and mushes it into one consistent event shape. Doesn't matter which log line it came from, downstream code doesn't need to care.&lt;br&gt;
Bugs squashed along the way: unterminated strings, mysterious NoneType crashes (regex didn't match, oops), off-by-one group numbering, and — my personal favorite — an IndentationError caused by tabs sneaking in next to spaces like a tiny gremlin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 2: rules as data, not dogma&lt;/strong&gt;&lt;br&gt;
This was the fun part. Instead of writing "if failed login count &amp;gt; 5" buried somewhere in Python, I moved every detection rule into a YAML file. Each rule even gets tagged with a real MITRE ATT&amp;amp;CK technique ID, because if I'm going to pretend to be a security engineer I might as well pretend properly.&lt;br&gt;
Ended up with four rules covering: repeated failed logins, repeated suspicious IP activity, privilege escalation right after login, and — the spiciest one — persistence via account creation (a.k.a. "the attacker just planted a way back in").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 3: the detector finally detects something&lt;/strong&gt;&lt;br&gt;
Two core functions carried the whole day:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One that groups events by any field a rule asks for (a user, an IP, whatever) and checks it against a threshold — so one function now powers multiple rules instead of copy-pasted near-duplicates.&lt;/li&gt;
&lt;li&gt;One that checks whether event A is followed by event B within some time window, using real datetime math instead of vibes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And then — the best kind of bug — a false positive. My privilege-escalation rule was matching people to each other's activity just because the timing lined up. My innocent employee's login was accidentally getting blamed for the backdoor account's actions, purely by coincidence of timestamps. Fixed it by adding a same_user toggle to the rule config, since some sequences genuinely need "same person did both things" and others (like sudo → new account) very much don't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;End result:&lt;/strong&gt; all four rules fired exactly once each, correctly reconstructing the entire attack from first failed login to backdoor to the shadow file — and zero false alarms on the one honest person in the logs. 🎉&lt;br&gt;
What's next&lt;br&gt;
&lt;strong&gt;Day 4 is scorer.py&lt;/strong&gt; — turning a pile of individual alerts into one aggregate threat score per user/IP, so instead of "here are 4 alerts," Blue Watch can say "hey, this person is very clearly the problem."&lt;br&gt;
More soon 👁️🔵&lt;br&gt;
xoxo &lt;/p&gt;

&lt;p&gt;Following along? This is a build-in-public series — feedback, questions, and "actually you should use X instead" comments always welcome.&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>beginners</category>
      <category>100daysofcode</category>
    </item>
  </channel>
</rss>
