Problem
The goal was to build a practical cybersecurity project that combines real SIEM telemetry with AI-assisted alert triage. Rather than simulate data, the project required connecting to a real SIEM environment and automating the analyst workflow: retrieve an alert, understand its context, and produce a structured triage report.
The specific challenge: how do you take raw security event data from a live SIEM, extract the meaningful fields, and produce something that reads like a trained analyst's assessment - automatically, on demand?
The secondary challenge was infrastructure. Before any code could run, the lab environment needed to be fully operational - which required real troubleshooting.
Environment
- Windows PC (analyst workstation) - 172.22.2.119
- Wazuh server VM running Rocky Linux - wazuh1 · 172.22.3.49
- Xen Orchestra (XOA) managing the virtualized environment
- Multiple Wazuh agents reporting telemetry (Windows and Linux endpoints)
- Wazuh Indexer (OpenSearch) on port 9200 storing alert documents
- OpenAI API for AI-assisted triage
- Python project folder on the Windows analyst workstation
The SIEM dashboard showed 8 agents (6 active, 2 disconnected) with real security events including authentication failures, MITRE ATT&CK-mapped alerts, and Windows/Linux log entries.
Final Architecture
The completed pipeline operates as a run-on-demand script that pulls the most recent security alert and produces a structured triage report:
The script is run manually: python triage_latest_wazuh_alert.py
It connects to the Wazuh Indexer, queries the latest alert, extracts key fields, sends a structured summary to OpenAI, prints the triage output, and saves both the report and raw alert JSON to disk.
Infrastructure Setup & Discovery
The project didn't begin with coding - it began with infrastructure troubleshooting. The only starting information was a sticky note with IP addresses: the Wazuh server at 172.22.3.49, managed through Xen Orchestra.
Initial problem: Every connection attempt to the Wazuh server failed - ping, SSH, and browser access all returned unreachable errors.
The troubleshooting process followed a deliberate layer-by-layer approach:
- Checked Xen Orchestra - found the Wazuh VM existed but was powered off. Started it.
- Opened the VM console - logged in as user shamar on Rocky Linux (hostname: wazuh1). Confirmed sudo access.
- Ran ip addr - the eth0 interface was present but had no IPv4 address assigned.
- Ran ip route - the routing table was completely empty.
- Checked NetworkManager -
nmcli device statusshowed eth0 as "disconnected". A saved profile named "eth0" existed but wasn't active. - Inspected the profile -
nmcli connection show eth0 | grep ipv4revealed a valid static config: 172.22.3.49/22, gateway 172.22.0.1. The configuration was correct - just inactive.
After restoring the network, the Wazuh dashboard became accessible at https://172.22.3.49. It showed 8 agents reporting telemetry and existing security events with MITRE ATT&CK technique mappings.
Python Automation
The Python project was created at C:\Users\shamar\ai-wazuh-triage with a virtual environment to isolate dependencies.
Phase 1 - Manual JSON triage: The first working script (triage_alert.py) loaded a manually exported Wazuh alert JSON, parsed key fields, sent a structured summary to OpenAI, printed the triage analysis, and saved a Markdown report to reports/.
Phase 2 - Automated alert retrieval: The manual export step was eliminated. The script was rewritten to connect directly to the Wazuh Indexer, query the wazuh-alerts-* index, retrieve the most recent alert by timestamp, and run the same triage process automatically.
Wazuh Indexer Integration
The Wazuh dashboard stores alert documents in the Wazuh Indexer (OpenSearch), accessible on port 9200. The Python script queries this directly to pull the latest alert:
The Indexer was initially bound only to localhost (127.0.0.1:9200). It had to be exposed to the LAN by updating /etc/wazuh-indexer/opensearch.yml to network.host: 0.0.0.0 and restarting the service. This is only appropriate in a trusted lab environment - not production.
Credentials for the Indexer were separate from the Wazuh API credentials. The correct admin credentials came from the installer-generated file at /root/wazuh-install-files/wazuh-passwords.txt.
Challenges & Fixes
Problem
Wazuh VM unreachable - ping, SSH, and browser access all failed.
Fix
VM was powered off. Started via Xen Orchestra, then activated the inactive NetworkManager profile with nmcli connection up eth0.
Problem
Network fix didn't survive reboot - Wazuh became unreachable again after restart.
Fix
Set connection.autoconnect yes on the eth0 profile so it activates automatically at boot.
Problem
PowerShell blocked venv activation: "running scripts is disabled on this system."
Fix
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass - changes only the current session.
Problem
OpenAI returned RateLimitError: insufficient_quota immediately after first request.
Fix
ChatGPT Plus and the OpenAI API are billed separately. Added API credits to the account - resolved immediately.
Problem
Port 9200 timed out from Windows - the Indexer was bound only to localhost.
Fix
Confirmed with ss -lntp | grep 9200 showing 127.0.0.1:9200. Updated opensearch.yml to network.host: 0.0.0.0, restarted service.
Problem
Wazuh API credentials (port 55000) kept returning 401 despite multiple credential attempts.
Fix
Found installer-generated credentials at /root/wazuh-install-files/wazuh-passwords.txt. The dashboard config credentials were not valid for direct API auth.
Results
The final script successfully:
- Connects to the Wazuh Indexer and retrieves the latest alert from
wazuh-alerts-* - Extracts agent name, rule description, severity level, MITRE ATT&CK IDs and techniques
- Handles both Windows alerts (Event ID, logon type, process name) and Linux alerts (PAM, SSH, syslog)
- Sends a structured summary to OpenAI and receives an analyst-style triage
- Saves the triage report as Markdown and the raw alert as JSON to
reports/
Skills Demonstrated
Planned Next Steps
- Add
--limit Noption to triage the latest N alerts in one run - Add severity filtering - only analyze alerts above a rule level threshold
- Track analyzed alert IDs locally to avoid duplicate triage
- Separate Windows vs. Linux alert parsing for better field coverage
- Build a simple Streamlit or Flask dashboard to browse alerts and view reports
- Add email or Slack notification for high-severity findings