Build an AI Security Log Analyzer

Use AI to classify threats, map MITRE ATT&CK techniques, and generate incident reports.

Introduction

30 Second Summary

Every day, security teams are drowning in thousands of log alerts. Most get ignored because there simply aren't enough analysts to review them all.

In this project, you will build an AI-powered security log analyzer that reads attack logs, classifies threats using a free LLM, and generates professional incident reports enriched with real vulnerability data.

What You'll Build

A multi-agent Python pipeline that processes security logs through four specialized AI agents, queries the NIST National Vulnerability Database for real CVEs, and maps attacks to the MITRE ATT&CK framework.

By the end of this project, you'll have:

  • A working log analysis agent that uses Groq's free LLM to identify attack types, source IPs, and targeted services from raw security logs.
  • A CVE enrichment module that queries the live NIST NVD API for real vulnerability data with CVSS severity scores.
  • A report generator that compiles findings into professional incident reports with MITRE ATT&CK technique mappings and remediation recommendations.
  • Secret Mission: Add a threat intelligence layer that scores flagged IPs and includes severity classifications in your final report.

Are there any prerequisites?

You'll need basic Python knowledge (functions, imports, file I/O). No prior cybersecurity or AI tooling experience is required.

Before We Start

Security operations centers process thousands of log entries every day. Most analysts spend hours manually reading logs, classifying threats, and researching vulnerabilities. AI can do that heavy lifting in seconds.

In this step, you'll install Python, connect to Groq's free LLM API, and create a sample log file that simulates real attacks from a home lab environment.

In this step, get ready to:
  • Install Python 3.13 and create your project workspace.
  • Set up Groq and install your Python dependencies.
  • Create a sample attack log file for testing.
Install Python and create a project folder

Your AI log analyzer is a Python project. You need Python 3.13 installed on your Windows machine to run it.

  • Press the Windows key to open your search bar.
  • Type Windows Terminal and press Enter to open it.
  • Check if Python is already installed by running this command:
python --version

✔️ I see version 3.13 or higher

You're all set. Python is installed and ready to go.

ⓧ I see an older version

You have Python installed, but you need version 3.13 or higher for this project. The packages we use require at least Python 3.10, and 3.13 is the current stable release.

  • Download the Python 3.13 installer from the official downloads page:
  • https://www.python.org/downloads/release/python-31314/
  • Select the Windows installer (64-bit) option.
  • On the first installer page, check the box labelled Add python.exe to PATH before clicking Install Now.
  • After installation completes, close and reopen Windows Terminal.
  • Run python --version again to confirm it now shows 3.13.

Multiple Python versions

If you still see the old version after installing, the old Python may be first on your PATH. Try running python3.13 --version instead. If that works, use python3.13 in place of python throughout this project.

ⓧ Command not found

Python is not installed yet. Let's get it set up.

  • Download the Python 3.13 installer from the official downloads page:
  • https://www.python.org/downloads/release/python-31314/
  • Select the Windows installer (64-bit) option.
  • On the first installer page, check the box labelled Add python.exe to PATH before clicking Install Now.
  • After installation completes, close and reopen Windows Terminal.
  • Run python --version to confirm it shows 3.13.

Still seeing 'command not found'?

Open the Start menu, search for Manage app execution aliases, and make sure the python.exe alias is toggled on for Python. If it still doesn't work, restart your computer to ensure PATH changes take effect.

Need more help? Help me troubleshoot Python not being recognized in my terminal after installing it on Windows.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir ai-log-analyzer
cd ai-log-analyzer
  • Create a virtual environment to keep your project dependencies isolated by running:
python -m venv venv

What is a virtual environment?

A virtual environment is a self-contained Python installation inside your project folder. Packages you install here won't interfere with other Python projects on your machine.

  • Activate the virtual environment by running:
venv\Scripts\activate

You should see (venv) appear at the beginning of your terminal prompt. This confirms the virtual environment is active.

Seeing a permissions error?

Windows may block script execution by default. Run this command to allow it for the current session, then try activating again:

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

Still stuck? Help me fix the PowerShell execution policy error when activating a Python virtual environment on Windows.

Install dependencies and set up Groq

Your project uses three Python packages: the Groq SDK to call the LLM, python-dotenv to load environment variables, and requests to call the NIST vulnerability API.

  • Install all three packages by running:
pip install groq==1.4.0 python-dotenv==1.2.2 requests

What do these packages do?

  • groq is the official Python SDK for Groq's LLM API. It lets you send prompts and receive AI-generated responses.
  • python-dotenv reads key-value pairs from a .env file and loads them as environment variables. This keeps secrets out of your code.
  • requests is a popular HTTP library for making API calls. You'll use it to query the NIST NVD for real CVE data.

You should see a success message for each package. If you see version numbers and "Successfully installed", you're good.

Installation failed?

Make sure you see (venv) in your terminal prompt. If you don't, your virtual environment is not active. Run venv\Scripts\activate again before retrying the install.

Still failing? Help me debug why pip install is failing in my virtual environment on Windows.

Now you need a Groq API key. Groq offers a free tier that never charges your account, so no credit card is needed.

  • Open your browser and go to https://console.groq.com/.
  • Sign up for a free account. No credit card is required.
  • After signing in, navigate to the API Keys section.
  • Click Create API Key.
  • Give your key a name (e.g. log-analyzer) and click Submit.
  • Copy the API key immediately. You won't be able to see it again after closing the modal.

Why Groq?

Groq provides access to production-grade LLMs like Llama 3.3 70B on a genuinely free tier. It never charges on the Free Plan. If you hit rate limits, you'll get a 429 error instead of a bill.

For this project's 3-4 API calls per run, you'll stay well within the free limits.

  • Back in your terminal, create a .env file to store your API key by running:
New-Item .env
  • Open the .env file in a text editor and add this line, replacing your-api-key-here with the API key you just copied:
GROQ_API_KEY=your-api-key-here

What does this file do?

The .env file keeps your API key separate from your code. The python-dotenv package reads this file at runtime so your key is available as an environment variable without being hardcoded.

  • Save the .env file.
Create a sample attack log file

Your AI analyzer needs log data to work with. You'll create a sample log file containing three types of attacks: SSH brute force attempts, port scans, and privilege escalation attempts.

What are these log entries?

These entries simulate what you'd capture from a real SIEM or syslog on your Kali/Ubuntu/Windows Server lab environment. Each line follows standard Linux syslog format with timestamps, hostnames, and service identifiers.

  • Create a file called sample_logs.txt in your ai-log-analyzer folder and paste in the following content:
Jun 23 08:15:22 webserver sshd[1234]: Failed password for root from 192.168.1.105 port 22 ssh2
Jun 23 08:15:23 webserver sshd[1234]: Failed password for root from 192.168.1.105 port 22 ssh2
Jun 23 08:15:24 webserver sshd[1234]: Failed password for root from 192.168.1.105 port 22 ssh2
Jun 23 08:15:25 webserver sshd[1234]: Failed password for root from 192.168.1.105 port 22 ssh2
Jun 23 08:15:26 webserver sshd[1234]: Failed password for root from 192.168.1.105 port 22 ssh2
Jun 23 08:15:27 webserver sshd[1234]: Failed password for admin from 192.168.1.105 port 22 ssh2
Jun 23 08:15:28 webserver sshd[1234]: Failed password for admin from 192.168.1.105 port 22 ssh2
Jun 23 08:20:01 webserver kernel: [UFW BLOCK] IN=eth0 OUT= SRC=10.0.0.50 DST=192.168.1.10 PROTO=TCP SPT=44321 DPT=445 WINDOW=1024 SYN
Jun 23 08:20:02 webserver kernel: [UFW BLOCK] IN=eth0 OUT= SRC=10.0.0.50 DST=192.168.1.10 PROTO=TCP SPT=44322 DPT=3389 WINDOW=1024 SYN
Jun 23 08:20:03 webserver kernel: [UFW BLOCK] IN=eth0 OUT= SRC=10.0.0.50 DST=192.168.1.10 PROTO=TCP SPT=44323 DPT=8080 WINDOW=1024 SYN
Jun 23 08:20:04 webserver kernel: [UFW BLOCK] IN=eth0 OUT= SRC=10.0.0.50 DST=192.168.1.10 PROTO=TCP SPT=44324 DPT=443 WINDOW=1024 SYN
Jun 23 08:20:05 webserver kernel: [UFW BLOCK] IN=eth0 OUT= SRC=10.0.0.50 DST=192.168.1.10 PROTO=TCP SPT=44325 DPT=21 WINDOW=1024 SYN
Jun 23 08:25:10 webserver sudo: attacker : TTY=pts/0 ; PWD=/tmp ; USER=root ; COMMAND=/bin/bash
Jun 23 08:25:15 webserver sudo: pam_unix(sudo:auth): authentication failure; logname=attacker uid=1001 euid=0 tty=/dev/pts/0 ruser=attacker rhost= user=root
Jun 23 08:25:20 webserver kernel: audit: type=1400 msg=audit(1719130520.123:456): apparmor="DENIED" operation="open" profile="/usr/sbin/sshd" name="/etc/shadow" pid=5678 comm="cat"

What attacks are in this file?

  • Lines 1-7: SSH brute force attack. IP 192.168.1.105 is repeatedly guessing passwords for root and admin accounts.
  • Lines 8-12: Port scan. IP 10.0.0.50 is probing common service ports (SMB, RDP, HTTP, HTTPS, FTP).
  • Lines 13-15: Privilege escalation. A user named attacker is trying to gain root access and read the shadow password file.
  • Save sample_logs.txt.

Now verify that everything is wired up correctly. This command imports the Groq library and confirms your environment can reach it.

  • Test your setup by running:
python -c "from groq import Groq; print('Groq ready')"

You should see Groq ready printed in your terminal. This confirms Python, the virtual environment, and the Groq package are all working together.

Seeing an import error?

If you see ModuleNotFoundError, the package wasn't installed in your active virtual environment. Confirm you see (venv) in your prompt, then re-run the pip install command from earlier.

Need help? Help me fix a ModuleNotFoundError for the groq package after I installed it with pip.

✔️ Awesome, I've got everything!

Great. Double check that you've saved both .env and sample_logs.txt in your ai-log-analyzer folder.

ⓧ I'd like to double check the full code

Here is what each file should contain:

GROQ_API_KEY=your-api-key-here
Jun 23 08:15:22 webserver sshd[1234]: Failed password for root from 192.168.1.105 port 22 ssh2
Jun 23 08:15:23 webserver sshd[1234]: Failed password for root from 192.168.1.105 port 22 ssh2
Jun 23 08:15:24 webserver sshd[1234]: Failed password for root from 192.168.1.105 port 22 ssh2
Jun 23 08:15:25 webserver sshd[1234]: Failed password for root from 192.168.1.105 port 22 ssh2
Jun 23 08:15:26 webserver sshd[1234]: Failed password for root from 192.168.1.105 port 22 ssh2
Jun 23 08:15:27 webserver sshd[1234]: Failed password for admin from 192.168.1.105 port 22 ssh2
Jun 23 08:15:28 webserver sshd[1234]: Failed password for admin from 192.168.1.105 port 22 ssh2
Jun 23 08:20:01 webserver kernel: [UFW BLOCK] IN=eth0 OUT= SRC=10.0.0.50 DST=192.168.1.10 PROTO=TCP SPT=44321 DPT=445 WINDOW=1024 SYN
Jun 23 08:20:02 webserver kernel: [UFW BLOCK] IN=eth0 OUT= SRC=10.0.0.50 DST=192.168.1.10 PROTO=TCP SPT=44322 DPT=3389 WINDOW=1024 SYN
Jun 23 08:20:03 webserver kernel: [UFW BLOCK] IN=eth0 OUT= SRC=10.0.0.50 DST=192.168.1.10 PROTO=TCP SPT=44323 DPT=8080 WINDOW=1024 SYN
Jun 23 08:20:04 webserver kernel: [UFW BLOCK] IN=eth0 OUT= SRC=10.0.0.50 DST=192.168.1.10 PROTO=TCP SPT=44324 DPT=443 WINDOW=1024 SYN
Jun 23 08:20:05 webserver kernel: [UFW BLOCK] IN=eth0 OUT= SRC=10.0.0.50 DST=192.168.1.10 PROTO=TCP SPT=44325 DPT=21 WINDOW=1024 SYN
Jun 23 08:25:10 webserver sudo: attacker : TTY=pts/0 ; PWD=/tmp ; USER=root ; COMMAND=/bin/bash
Jun 23 08:25:15 webserver sudo: pam_unix(sudo:auth): authentication failure; logname=attacker uid=1001 euid=0 tty=/dev/pts/0 ruser=attacker rhost= user=root
Jun 23 08:25:20 webserver kernel: audit: type=1400 msg=audit(1719130520.123:456): apparmor="DENIED" operation="open" profile="/usr/sbin/sshd" name="/etc/shadow" pid=5678 comm="cat"

Your environment is ready. Next up, you'll build the first AI agent that reads these logs and classifies the attacks it finds.

Build the Log Analysis Agent

Your environment is ready. Now it is time to build the brain of this project: an AI agent that reads raw security logs and extracts structured threat intelligence from them.

A real SOC analyst might spend 30 minutes reading through log entries to spot attack patterns. Your agent will do it in seconds by sending the logs to Groq's LLM with a carefully crafted system prompt that tells the model exactly what to look for.

In this step, get ready to:
  • Create the main orchestrator file that coordinates your agent pipeline.
  • Build Agent 1, the Log Analyzer, that sends logs to Groq's LLM for threat extraction.
  • Test the log analyzer and verify it identifies distinct attack events.
Create the main orchestrator file

The orchestrator is the entry point for your entire pipeline. It loads your API key, reads the log file, and calls each agent in sequence.

You also need an agents/ directory to hold your agent modules. Python uses an __init__.py file to recognize a folder as an importable package.

  • Create the agents directory and its init file by running these commands:
mkdir agents
New-Item agents/__init__.py
  • Confirm the directory was created by running:
ls agents

You should see __init__.py listed in the output.

What is __init__.py?

This file can be completely empty. Its presence tells Python that the agents/ folder is a package you can import from. Without it, from agents.log_analyzer import analyze_logs would fail.

  • Create a new file called main.py in your ai-log-analyzer folder (not inside agents/) and paste the following code:
import os
import sys
from datetime import datetime
from dotenv import load_dotenv

from agents.log_analyzer import analyze_logs
from agents.cve_lookup import lookup_cves
from agents.mitre_mapper import map_to_mitre
from agents.report_generator import generate_report

load_dotenv()

def main():
    api_key = os.environ.get("GROQ_API_KEY")
    if not api_key:
        print("Error: GROQ_API_KEY not found in .env file")
        sys.exit(1)

    log_file = "sample_logs.txt"
    if not os.path.exists(log_file):
        print(f"Error: {log_file} not found")
        sys.exit(1)

    with open(log_file, "r") as f:
        log_data = f.read()

    print("=" * 60)
    print("AI SECURITY LOG ANALYZER")
    print("=" * 60)

    print("\n[Agent 1] Analyzing logs...")
    analysis = analyze_logs(api_key, log_data)
    print("  Done. Threats identified.")

    print("\n[Agent 2] Looking up CVEs from NIST NVD...")
    cve_data = lookup_cves(analysis)
    print(f"  Done. Found {len(cve_data)} relevant CVEs.")

    print("\n[Agent 3] Mapping to MITRE ATT&CK framework...")
    mitre_data = map_to_mitre(api_key, analysis)
    print("  Done. Techniques mapped.")

    print("\n[Agent 4] Generating incident report...")
    report = generate_report(api_key, analysis, cve_data, mitre_data)

    os.makedirs("reports", exist_ok=True)
    timestamp = datetime.now().strftime("%Y-%m-%d_%H%M%S")
    report_path = f"reports/incident_report_{timestamp}.txt"
    with open(report_path, "w") as f:
        f.write(report)

    print(f"  Done. Report saved to: {report_path}")
    print("\n" + "=" * 60)
    print("ANALYSIS COMPLETE")
    print("=" * 60)
    print(f"\nFull report: {report_path}")

if __name__ == "__main__":
    main()

What does this code do?

This file is the conductor of your multi-agent pipeline. It coordinates 4 agents in sequence.

  • load_dotenv() reads your .env file and makes GROQ_API_KEY available as an environment variable.
  • The error checks at the top ensure the API key and log file exist before the pipeline starts.
  • Each agent is called in order: Log Analyzer, CVE Lookup, MITRE Mapper, then Report Generator.
  • The final report is saved to a timestamped file in a reports/ directory.

Why build the full orchestrator now?

You are writing the full pipeline structure upfront so you can see how all the agents connect. You will build each agent module in the coming steps. Running main.py won't work yet because the other agent files don't exist. You will test Agent 1 directly in substep 3.

  • Save main.py.
Build Agent 1: the Log Analyzer

This agent is where the AI magic happens. It takes raw log text, sends it to Groq's llama-3.3-70b-versatile model with a security-focused system prompt, and gets back a structured breakdown of every attack event.

What is prompt engineering for security?

The system prompt tells the LLM to behave as a senior SOC analyst. It specifies the exact output format you need: attack type, source IP, severity, and CVE keywords.

This structured format is critical because later agents parse this output programmatically. Vague instructions would produce inconsistent results that break downstream processing.

  • Create a new file called log_analyzer.py inside the agents/ directory and paste this code:
from groq import Groq


def analyze_logs(api_key, log_data):
    client = Groq(api_key=api_key)

    system_prompt = """You are a senior SOC analyst. Analyze the following security logs and extract:
1. Each distinct attack event (type, source IP, target, timestamp range)
2. The severity of each attack (CRITICAL, HIGH, MEDIUM, LOW)
3. Keywords that can be used to search for related CVEs

Respond in this exact format for each attack found:

ATTACK: <attack type>
SOURCE_IP: <ip address>
TARGET: <target service or system>
TIMESTAMPS: <start time> - <end time>
SEVERITY: <CRITICAL|HIGH|MEDIUM|LOW>
CVE_KEYWORDS: <comma-separated keywords for CVE search>
DESCRIPTION: <brief description of what happened>

---
"""

    chat_completion = client.chat.completions.create(
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"Analyze these security logs:\n\n{log_data}"}
        ],
        model="llama-3.3-70b-versatile",
        temperature=0.1,
        max_tokens=2000
    )

    return chat_completion.choices[0].message.content

What does this code do?

  • Groq(api_key=api_key) creates an authenticated client that can call Groq's API.
  • The system_prompt instructs the model to respond in a strict format. This makes the output parseable by downstream agents.
  • temperature=0.1 keeps the model deterministic. Security analysis needs consistency, not creativity.
  • The function returns the raw text response from the LLM, which contains structured attack data.
  • Save agents/log_analyzer.py.

Seeing a red underline on the import?

If your editor shows an error on from groq import Groq, make sure your virtual environment is active. Your terminal prompt should show (venv) at the beginning.

help me fix import errors in my Python virtual environment

Test the log analyzer

Since the other agent modules don't exist yet, main.py can't run its full pipeline. You will test Agent 1 directly to confirm it can analyze your sample logs.

  • Test the log analyzer by running this command:
python -c "import os; from dotenv import load_dotenv; load_dotenv(); from agents.log_analyzer import analyze_logs; print(analyze_logs(os.environ['GROQ_API_KEY'], open('sample_logs.txt').read()))"

After a few seconds, you should see structured output identifying at least 3 distinct attack events: SSH brute force, port scanning, and privilege escalation.

Each event includes fields like ATTACK, SOURCE_IP, SEVERITY, and CVE_KEYWORDS.

Seeing an API error or empty output?

  • Check that your .env file contains your actual Groq API key (not the placeholder your-api-key-here).
  • Make sure .env is in your ai-log-analyzer folder (same level as main.py), not inside agents/.
  • If you see a rate limit error, wait 60 seconds and try again. Groq's free tier has request limits but never charges.

help me debug my Groq API connection

✔️ Awesome, I've got everything!

Great. Double check you have saved both files before moving on.

ⓧ I'd like to double check the full code

Here is what each file should look like at this point. Your agents/__init__.py file should exist but be empty.

import os
import sys
from datetime import datetime
from dotenv import load_dotenv

from agents.log_analyzer import analyze_logs
from agents.cve_lookup import lookup_cves
from agents.mitre_mapper import map_to_mitre
from agents.report_generator import generate_report

load_dotenv()

def main():
    api_key = os.environ.get("GROQ_API_KEY")
    if not api_key:
        print("Error: GROQ_API_KEY not found in .env file")
        sys.exit(1)

    log_file = "sample_logs.txt"
    if not os.path.exists(log_file):
        print(f"Error: {log_file} not found")
        sys.exit(1)

    with open(log_file, "r") as f:
        log_data = f.read()

    print("=" * 60)
    print("AI SECURITY LOG ANALYZER")
    print("=" * 60)

    print("\n[Agent 1] Analyzing logs...")
    analysis = analyze_logs(api_key, log_data)
    print("  Done. Threats identified.")

    print("\n[Agent 2] Looking up CVEs from NIST NVD...")
    cve_data = lookup_cves(analysis)
    print(f"  Done. Found {len(cve_data)} relevant CVEs.")

    print("\n[Agent 3] Mapping to MITRE ATT&CK framework...")
    mitre_data = map_to_mitre(api_key, analysis)
    print("  Done. Techniques mapped.")

    print("\n[Agent 4] Generating incident report...")
    report = generate_report(api_key, analysis, cve_data, mitre_data)

    os.makedirs("reports", exist_ok=True)
    timestamp = datetime.now().strftime("%Y-%m-%d_%H%M%S")
    report_path = f"reports/incident_report_{timestamp}.txt"
    with open(report_path, "w") as f:
        f.write(report)

    print(f"  Done. Report saved to: {report_path}")
    print("\n" + "=" * 60)
    print("ANALYSIS COMPLETE")
    print("=" * 60)
    print(f"\nFull report: {report_path}")

if __name__ == "__main__":
    main()
from groq import Groq


def analyze_logs(api_key, log_data):
    client = Groq(api_key=api_key)

    system_prompt = """You are a senior SOC analyst. Analyze the following security logs and extract:
1. Each distinct attack event (type, source IP, target, timestamp range)
2. The severity of each attack (CRITICAL, HIGH, MEDIUM, LOW)
3. Keywords that can be used to search for related CVEs

Respond in this exact format for each attack found:

ATTACK: <attack type>
SOURCE_IP: <ip address>
TARGET: <target service or system>
TIMESTAMPS: <start time> - <end time>
SEVERITY: <CRITICAL|HIGH|MEDIUM|LOW>
CVE_KEYWORDS: <comma-separated keywords for CVE search>
DESCRIPTION: <brief description of what happened>

---
"""

    chat_completion = client.chat.completions.create(
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"Analyze these security logs:\n\n{log_data}"}
        ],
        model="llama-3.3-70b-versatile",
        temperature=0.1,
        max_tokens=2000
    )

    return chat_completion.choices[0].message.content

Agent 1 is working. It takes raw, messy log entries and outputs clean, structured threat data. Next up, you will build the CVE Lookup and MITRE ATT&CK agents that enrich this analysis with real-world vulnerability data.

Add CVE Enrichment and MITRE ATT&CK Mapping

Your Log Analyzer agent can now identify attacks in raw log data. That structured output is useful, but it lacks the context a SOC team needs to prioritize response.

In this step, you will enrich each detected threat with real vulnerability data from the NIST National Vulnerability Database and map attack behaviors to the MITRE ATT&CK framework. These two layers transform a basic alert into an actionable intelligence package.

In this step, get ready to:
  • Build a CVE Lookup agent that queries the live NIST NVD API for real vulnerability data.
  • Build a MITRE ATT&CK Mapper agent that classifies attacks by tactic and technique ID.
  • Verify both agents produce structured output for the pipeline.
Build the CVE Lookup agent

The NIST NVD API is a free, public database of known software vulnerabilities. Each entry includes a CVE ID and a CVSS severity score.

Your CVE Lookup agent will take the keywords extracted by the Log Analyzer and search the NVD for matching vulnerabilities. This connects abstract attack patterns to documented, real-world security flaws.

  • Create a new file called cve_lookup.py inside the agents folder.
  • Add the imports and the main lookup_cves function by pasting this code:
import requests
import time


def lookup_cves(analysis):
    cve_results = []

    keywords = extract_keywords(analysis)

    for keyword in keywords[:3]:
        cves = search_nvd(keyword)
        cve_results.extend(cves)
        time.sleep(6)

    return cve_results

What does this code do?

  • lookup_cves() is the entry point that other agents call. It extracts keywords from the analysis, searches NVD for the first 3 keywords, and collects all matching CVEs.
  • time.sleep(6) pauses 6 seconds between API requests. NIST recommends this to stay within the public rate limit of 5 requests per 30 seconds.
  • keywords[:3] limits the search to 3 keywords, keeping the script fast while still gathering relevant CVEs.
  • Below the lookup_cves function, add the extract_keywords helper function:

def extract_keywords(analysis):
    keywords = []
    for line in analysis.split("\n"):
        if line.startswith("CVE_KEYWORDS:"):
            kw = line.replace("CVE_KEYWORDS:", "").strip()
            for k in kw.split(","):
                cleaned = k.strip()
                if cleaned and cleaned not in keywords:
                    keywords.append(cleaned)
    return keywords

What does this code do?

extract_keywords() parses the structured output from Agent 1. It scans for lines starting with CVE_KEYWORDS: and splits the comma-separated values into a deduplicated list.

This is why the Log Analyzer's system prompt asked for keywords in a specific format. Each agent in the pipeline produces output that the next agent can parse.

  • Below extract_keywords, add the search_nvd function that makes the actual API call:

def search_nvd(keyword):
    url = "https://services.nvd.nist.gov/rest/json/cves/2.0"
    params = {
        "keywordSearch": keyword,
        "resultsPerPage": 3
    }

    try:
        response = requests.get(url, params=params, timeout=30)
        response.raise_for_status()
        data = response.json()

        cves = []
        for vuln in data.get("vulnerabilities", []):
            cve = vuln.get("cve", {})
            cve_id = cve.get("id", "Unknown")

            descriptions = cve.get("descriptions", [])
            description = ""
            for desc in descriptions:
                if desc.get("lang") == "en":
                    description = desc.get("value", "")
                    break

            cvss_score = "N/A"
            metrics = cve.get("metrics", {})
            if "cvssMetricV31" in metrics:
                cvss_score = metrics["cvssMetricV31"][0]["cvssData"]["baseScore"]
            elif "cvssMetricV2" in metrics:
                cvss_score = metrics["cvssMetricV2"][0]["cvssData"]["baseScore"]

            cves.append({
                "id": cve_id,
                "description": description[:200],
                "cvss_score": cvss_score,
                "keyword": keyword
            })

        return cves

    except requests.exceptions.RequestException as e:
        print(f"  Warning: NVD API request failed for '{keyword}': {e}")
        return []

What does this code do?

  • search_nvd() sends a GET request to the NVD API at https://services.nvd.nist.gov/rest/json/cves/2.0 with the keyword as a search parameter.
  • resultsPerPage: 3 limits each search to 3 CVEs. This keeps the pipeline fast while still returning relevant data.
  • The CVSS score extraction prefers v3.1 metrics but falls back to v2 if v3.1 is unavailable. This handles older CVEs that only have v2 scores.
  • The try/except block handles network errors gracefully. If a request fails, the pipeline continues with whatever CVEs it found so far.
  • Save agents/cve_lookup.py.

Seeing a syntax error?

Make sure each function definition starts with def at the top level (no indentation). The functions should not be nested inside each other.

Also check that the f-string on the last line uses a regular single quote inside the curly braces, not a backtick.

Help me fix a syntax error in cve_lookup.py

✔️ Awesome, I've got everything!

Great. Make sure you have saved agents/cve_lookup.py before moving on.

ⓧ I'd like to double check the full code

import requests
import time


def lookup_cves(analysis):
    cve_results = []

    keywords = extract_keywords(analysis)

    for keyword in keywords[:3]:
        cves = search_nvd(keyword)
        cve_results.extend(cves)
        time.sleep(6)

    return cve_results


def extract_keywords(analysis):
    keywords = []
    for line in analysis.split("\n"):
        if line.startswith("CVE_KEYWORDS:"):
            kw = line.replace("CVE_KEYWORDS:", "").strip()
            for k in kw.split(","):
                cleaned = k.strip()
                if cleaned and cleaned not in keywords:
                    keywords.append(cleaned)
    return keywords


def search_nvd(keyword):
    url = "https://services.nvd.nist.gov/rest/json/cves/2.0"
    params = {
        "keywordSearch": keyword,
        "resultsPerPage": 3
    }

    try:
        response = requests.get(url, params=params, timeout=30)
        response.raise_for_status()
        data = response.json()

        cves = []
        for vuln in data.get("vulnerabilities", []):
            cve = vuln.get("cve", {})
            cve_id = cve.get("id", "Unknown")

            descriptions = cve.get("descriptions", [])
            description = ""
            for desc in descriptions:
                if desc.get("lang") == "en":
                    description = desc.get("value", "")
                    break

            cvss_score = "N/A"
            metrics = cve.get("metrics", {})
            if "cvssMetricV31" in metrics:
                cvss_score = metrics["cvssMetricV31"][0]["cvssData"]["baseScore"]
            elif "cvssMetricV2" in metrics:
                cvss_score = metrics["cvssMetricV2"][0]["cvssData"]["baseScore"]

            cves.append({
                "id": cve_id,
                "description": description[:200],
                "cvss_score": cvss_score,
                "keyword": keyword
            })

        return cves

    except requests.exceptions.RequestException as e:
        print(f"  Warning: NVD API request failed for '{keyword}': {e}")
        return []

Why no API key for NVD?

The NIST NVD API is publicly accessible without authentication. The public rate limit is 5 requests per 30 seconds, which is more than enough for this project's 3 keyword searches.

The 6-second sleep between requests follows NIST's official recommendation to stay within limits gracefully.

Build the MITRE ATT&CK Mapper agent

The MITRE ATT&CK framework is a standardized catalog of adversary tactics and techniques used by every SOC team worldwide. Each technique has a unique ID like T1110 (Brute Force) or T1046 (Network Service Discovery).

This agent uses Groq's LLM to classify the detected attacks into the correct MITRE technique categories. The result gives your incident report a shared vocabulary that any security professional recognizes.

  • Create a new file called mitre_mapper.py inside the agents folder.
  • Paste the following code:
from groq import Groq


def map_to_mitre(api_key, analysis):
    client = Groq(api_key=api_key)

    system_prompt = """You are a MITRE ATT&CK framework expert. Given the following security analysis, 
map each identified attack to the appropriate MITRE ATT&CK technique.

For each attack, provide:
TECHNIQUE_ID: <e.g., T1110>
TECHNIQUE_NAME: <e.g., Brute Force>
TACTIC: <e.g., Credential Access>
SUB_TECHNIQUE: <e.g., T1110.001 - Password Guessing> (if applicable)
DESCRIPTION: <brief explanation of why this mapping applies>

---
"""

    chat_completion = client.chat.completions.create(
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"Map these attacks to MITRE ATT&CK:\n\n{analysis}"}
        ],
        model="llama-3.3-70b-versatile",
        temperature=0.1,
        max_tokens=2000
    )

    return chat_completion.choices[0].message.content

What does this code do?

  • The system prompt tells the LLM to act as a MITRE ATT&CK expert. It provides a structured output format with technique ID, name, tactic, and sub-technique.
  • temperature=0.1 keeps the model's responses consistent and factual. For classification tasks like this, you want low creativity.
  • The user message passes the full analysis from Agent 1. The LLM uses its training knowledge of the ATT&CK framework to map each attack behavior to the correct technique.
  • Save agents/mitre_mapper.py.

Seeing an indentation error?

The triple-quoted system prompt string must be indented consistently. Make sure the closing """ is at the same indentation level as the content lines inside it.

Help me fix an indentation error in mitre_mapper.py

✔️ Awesome, I've got everything!

Great. Make sure you have saved agents/mitre_mapper.py before moving on.

ⓧ I'd like to double check the full code

from groq import Groq


def map_to_mitre(api_key, analysis):
    client = Groq(api_key=api_key)

    system_prompt = """You are a MITRE ATT&CK framework expert. Given the following security analysis, 
map each identified attack to the appropriate MITRE ATT&CK technique.

For each attack, provide:
TECHNIQUE_ID: <e.g., T1110>
TECHNIQUE_NAME: <e.g., Brute Force>
TACTIC: <e.g., Credential Access>
SUB_TECHNIQUE: <e.g., T1110.001 - Password Guessing> (if applicable)
DESCRIPTION: <brief explanation of why this mapping applies>

---
"""

    chat_completion = client.chat.completions.create(
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"Map these attacks to MITRE ATT&CK:\n\n{analysis}"}
        ],
        model="llama-3.3-70b-versatile",
        temperature=0.1,
        max_tokens=2000
    )

    return chat_completion.choices[0].message.content
Test both agents in the pipeline

Your main.py already imports and calls both agents in sequence. The data flow is: Log Analyzer output feeds into CVE Lookup (via keywords) and MITRE Mapper (via the full analysis text).

  • Verify both agent files are importable by running this command:
python -c "from agents.cve_lookup import lookup_cves; from agents.mitre_mapper import map_to_mitre; print('Both agents ready')"
  • You should see Both agents ready printed with no errors.
  • Test the keyword extraction logic by running:
python -c "from agents.cve_lookup import extract_keywords; print(extract_keywords('CVE_KEYWORDS: ssh brute force, privilege escalation'))"
  • You should see a list like ['ssh brute force', 'privilege escalation'] confirming the parser works.

Why not run the full pipeline yet?

Your main.py also imports report_generator which you have not built yet. You will create that in the next step. Once it exists, the full pipeline will run end-to-end.

Seeing ModuleNotFoundError?

Make sure your virtual environment is still active. You should see (venv) at the start of your terminal prompt. If not, reactivate it with venv\Scripts\activate.

Also check that both files are saved inside the agents folder (not in the project root) and that __init__.py still exists in that folder.

Help me fix a ModuleNotFoundError for my agents

Both enrichment agents are ready. Next up, you will build the Report Generator that compiles all of this data into a professional incident report and run the full pipeline end-to-end.

Generate the Incident Report

Your pipeline now identifies threats, pulls real CVE data from the NIST database, and maps attacks to the MITRE ATT&CK framework.

The final piece is turning all that raw intelligence into something actionable. In this step, you will build Agent 4, the Report Generator, which compiles every finding into a structured incident report that mirrors what real SOC teams produce.

In this step, get ready to:
  • Build the Report Generator agent that compiles all findings into a professional 7-section incident report.
  • Save timestamped reports to a dedicated folder.
  • Run the complete 4-agent pipeline end-to-end and review the generated report.
Build the Report Generator agent

The Report Generator takes the structured output from all three previous agents and asks the LLM to synthesize it into a formal incident report. This is where raw data becomes a document you could hand to a security manager.

  • Create a new file called report_generator.py inside your agents folder.
  • Add the generate_report function by pasting this code:
from groq import Groq
from datetime import datetime


def generate_report(api_key, analysis, cve_data, mitre_data):
    client = Groq(api_key=api_key)

    cve_summary = format_cve_data(cve_data)

    system_prompt = """You are a senior incident response analyst writing a formal incident report.
Generate a professional incident report with these 7 sections:

1. EXECUTIVE SUMMARY - 2-3 sentence non-technical overview
2. INCIDENT OVERVIEW - What happened and how
3. ATTACK TIMELINE - Sequence of events from the logs
4. CVE VULNERABILITIES IDENTIFIED - Real CVEs with CVSS scores
5. MITRE ATT&CK MAPPING - Tactics and techniques with IDs
6. RISK SEVERITY - Overall risk rating with justification
7. RECOMMENDED ACTIONS - Immediate steps and long-term improvements

Be specific, professional, and actionable. Use the real CVE and MITRE data provided.
"""

    user_content = f"""Generate the incident report based on:

LOG ANALYSIS:
{analysis}

CVE DATA FROM NIST NVD:
{cve_summary}

MITRE ATT&CK MAPPING:
{mitre_data}

Report timestamp: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
"""

    chat_completion = client.chat.completions.create(
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_content}
        ],
        model="llama-3.3-70b-versatile",
        temperature=0.2,
        max_tokens=4000
    )

    report = chat_completion.choices[0].message.content

    header = f"""{'=' * 70}
INCIDENT REPORT
Generated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
Analyst: AI Security Log Analyzer v1.0
{'=' * 70}

"""

    return header + report

What does this code do?

This function receives the output of all three previous agents and feeds them into a single LLM call. The system prompt instructs the model to produce exactly 7 structured sections.

The temperature=0.2 setting keeps the output focused and professional. A higher temperature would produce more creative but less consistent reports.

The function also prepends a formatted header with the generation timestamp and tool name, making the report look like it came from a real security tool.

  • Now add the format_cve_data helper function below the generate_report function in the same file:
def format_cve_data(cve_data):
    if not cve_data:
        return "No CVEs found matching the detected attack patterns."

    lines = []
    for cve in cve_data:
        lines.append(f"- {cve['id']} (CVSS: {cve['cvss_score']}) [{cve['keyword']}]")
        lines.append(f"  {cve['description']}")
        lines.append("")

    return "\n".join(lines)

What does this helper do?

The format_cve_data function transforms the raw list of CVE dictionaries into a human-readable string. Each CVE gets its ID, CVSS score, search keyword, and truncated description on separate lines.

This formatted text is what gets injected into the LLM prompt so the model can reference real vulnerability data in the report.

  • Save agents/report_generator.py.

Seeing a syntax error?

The triple-quoted strings (""") for system_prompt and user_content must each have matching opening and closing quotes on their own lines. Make sure no extra quote characters snuck in.

Also check that format_cve_data is defined at the module level (not indented inside generate_report).

Help me fix a syntax error in my report_generator.py file.

✔️ Awesome, I've got everything!

Great. Make sure you have saved agents/report_generator.py before moving on.

ⓧ I'd like to double check the full code

from groq import Groq
from datetime import datetime


def generate_report(api_key, analysis, cve_data, mitre_data):
    client = Groq(api_key=api_key)

    cve_summary = format_cve_data(cve_data)

    system_prompt = """You are a senior incident response analyst writing a formal incident report.
Generate a professional incident report with these 7 sections:

1. EXECUTIVE SUMMARY - 2-3 sentence non-technical overview
2. INCIDENT OVERVIEW - What happened and how
3. ATTACK TIMELINE - Sequence of events from the logs
4. CVE VULNERABILITIES IDENTIFIED - Real CVEs with CVSS scores
5. MITRE ATT&CK MAPPING - Tactics and techniques with IDs
6. RISK SEVERITY - Overall risk rating with justification
7. RECOMMENDED ACTIONS - Immediate steps and long-term improvements

Be specific, professional, and actionable. Use the real CVE and MITRE data provided.
"""

    user_content = f"""Generate the incident report based on:

LOG ANALYSIS:
{analysis}

CVE DATA FROM NIST NVD:
{cve_summary}

MITRE ATT&CK MAPPING:
{mitre_data}

Report timestamp: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
"""

    chat_completion = client.chat.completions.create(
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_content}
        ],
        model="llama-3.3-70b-versatile",
        temperature=0.2,
        max_tokens=4000
    )

    report = chat_completion.choices[0].message.content

    header = f"""{'=' * 70}
INCIDENT REPORT
Generated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
Analyst: AI Security Log Analyzer v1.0
{'=' * 70}

"""

    return header + report


def format_cve_data(cve_data):
    if not cve_data:
        return "No CVEs found matching the detected attack patterns."

    lines = []
    for cve in cve_data:
        lines.append(f"- {cve['id']} (CVSS: {cve['cvss_score']}) [{cve['keyword']}]")
        lines.append(f"  {cve['description']}")
        lines.append("")

    return "\n".join(lines)
Review the report-saving logic

Your main.py already contains the code that calls Agent 4 and saves the generated report to disk. Take a look at the bottom section of your main.py to see how it works.

  • In main.py, find these lines near the bottom of the main() function:
    os.makedirs("reports", exist_ok=True)
    timestamp = datetime.now().strftime("%Y-%m-%d_%H%M%S")
    report_path = f"reports/incident_report_{timestamp}.txt"
    with open(report_path, "w") as f:
        f.write(report)

What does this section do?

This code creates a reports/ directory if it does not already exist. The exist_ok=True flag prevents errors if the folder is already there from a previous run.

Each report gets a unique filename using the current date and time (e.g., incident_report_2026-06-23_143022.txt). This means you can run the analyzer multiple times without overwriting previous reports.

Run the complete pipeline end-to-end

All four agents are now in place. Time to run the full pipeline and see your incident report come together.

  • Run the complete analyzer by executing this command in your terminal:
python main.py

What should I see?

The terminal prints progress updates as each agent runs. You will see messages for Agent 1 through Agent 4, followed by the path where your report was saved.

The NVD API calls in Agent 2 take about 12-18 seconds total due to the 6-second sleep between requests. This is expected.

  • Once the pipeline finishes, open the generated report file by running:
dir reports
  • You should see a file named something like incident_report_2026-06-23_143022.txt (with your current date and time).
  • Open the report to review its contents by running:
type reports\incident_report_2026-06-23_143022.txt

Use your actual filename

Replace the filename in the type command with the actual filename shown by dir reports. Your timestamp will be different.

Your report should contain all 7 sections: Executive Summary, Incident Overview, Attack Timeline, CVE Vulnerabilities, MITRE ATT&CK Mapping, Risk Severity, and Recommended Actions.

Seeing an import error for report_generator?

Make sure your file is named exactly report_generator.py and is inside the agents folder (not in the project root).

Also confirm that agents/__init__.py still exists. Python needs this file to treat the folder as a package.

Help me debug an import error when running my AI log analyzer pipeline.

Getting a Groq rate limit error?

The free tier has rate limits on requests per minute. If you ran the pipeline multiple times in quick succession, wait 60 seconds and try again.

Help me understand the Groq rate limit error I'm seeing.

Secret mission

Add Threat Intelligence IP Scoring

Your analyzer identifies attacks and maps them to frameworks. But a real SOC analyst would want to know: how dangerous is each source IP? In this secret mission, you'll build a threat intelligence agent that scores flagged IPs based on attack frequency, CVE severity, and privilege escalation attempts.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. The Groq free tier never charges your account, and the NIST NVD API is free and public with no account required.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • ai-log-analyzer project folder (Python files, virtual environment, and generated reports).
  • Groq API key stored in .env (free tier, never charges).

Keep everything running

No action needed. Choose this if you are still actively building or plan to extend the project further.

  • Your project files are local. There are no cloud resources to manage.
  • The Groq free tier has rate limits but never charges. You can continue using your API key for future projects.
  • The NIST NVD API is public and requires no account, so there is nothing to maintain.

Pause - I'll come back to this later

Shut down any running processes to free up memory. Your files stay on disk for when you return.

  • Close any open terminal windows running the analyzer.
  • Deactivate your virtual environment by running:
deactivate
  • When you come back, reactivate it with venv\Scripts\activate from inside your ai-log-analyzer folder.

Delete - I don't want to use this again

Remove all project files and start fresh. Your Groq account remains free and inactive.

  • Delete your project folder by running this command in the terminal from the parent directory (the folder that contains ai-log-analyzer):

macOS

rm -rf ai-log-analyzer

Windows

Remove-Item -Recurse -Force ai-log-analyzer
  • If you want to revoke your Groq API key, navigate to the API Keys section at console.groq.com/keys and delete the key you created for this project.
  • Your Groq account itself is free and never charges. There is no financial reason to delete it.

Nice Work!

Nice Work!

You just built a production-grade AI security operations tool from scratch. Your analyzer transforms raw attack logs into professional incident reports complete with real CVE data, MITRE ATT&CK mappings, and severity ratings.

You've learned how to:

  • Build a multi-agent AI pipeline that processes security logs through 4 specialized Python agents orchestrated by a single main.py file.
  • Query the live NIST NVD API for real CVE vulnerability data and enrich detected threats with CVSS scores.
  • Map detected attacks to the MITRE ATT&CK framework and generate professional incident reports with severity ratings and remediation recommendations.
  • Secret Mission: Added a threat intelligence layer that scores flagged IPs based on attack frequency, CVE severity, and privilege escalation attempts.

Ready to quiz yourself?