Build an AI Security Log Analyzer

Use Gemini AI to detect and classify attack patterns in web server logs.

Introduction

30 Second Summary

Every day, web servers generate thousands of log entries. Hidden in that noise are the fingerprints of attackers probing for weaknesses, and real security analysts sift through these logs to catch threats before damage is done.

In this project, you will build a Python command-line tool that analyzes security logs using Google's Gemini AI. Your tool detects attack patterns like SQL injection and brute force attempts, classifies them using the industry-standard MITRE ATT&CK framework, and outputs a structured threat intelligence report.

What You'll Build

A command-line security log analyzer that ingests simulated attack traffic, sends it to Gemini AI for intelligent threat classification, and generates a JSON threat report with MITRE ATT&CK mappings and remediation advice.

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

  • A log generator that creates realistic web server traffic with embedded SQL injection, brute force, and directory traversal attacks.
  • A log parser that extracts structured data from raw entries and flags suspicious activity using pattern-matching heuristics.
  • An AI-powered threat analyzer that classifies threats, maps them to MITRE ATT&CK techniques, assigns severity scores, and suggests remediation steps.
  • Secret Mission: Generate automated firewall rules that translate detected threats into actionable defense configurations.

Are there any prerequisites?

You should be comfortable with basic Python (variables, functions, and file I/O) and familiar with using the command line. No prior cybersecurity or AI experience is needed.

Set Up Your Environment

Before you can analyze security logs with AI, you need a workspace where your scripts, virtual environment, and API credentials all live together.

In this step, you will create a project folder, set up a Python virtual environment, install dependencies from a requirements.txt file, and store your Gemini API key in a .env file.

In this step, get ready to:
  • Create a project folder with a virtual environment and four empty Python files.
  • Install dependencies from a requirements.txt file.
  • Store your Gemini API key in a .env file.
Create your project folder and virtual environment

A virtual environment isolates your project's Python packages from the rest of your system. This prevents version conflicts between projects.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Open the integrated terminal by pressing Ctrl+` (backtick key, next to the 1 key on your keyboard).
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir ai-security-analyzer
cd ai-security-analyzer

Now create the virtual environment and activate it. The activation command differs by operating system.

  • Create the virtual environment by running:

macOS

python3 -m venv venv
source venv/bin/activate

Windows

python -m venv venv
.\venv\Scripts\Activate

How do I know the virtual environment is active?

You should see (venv) appear at the beginning of your terminal prompt. This confirms you are working inside the isolated environment.

Now create the four project files. These are empty for now.

  • Create the project files by running:

macOS

touch log_generator.py log_parser.py threat_analyzer.py main.py

Windows

New-Item log_generator.py, log_parser.py, threat_analyzer.py, main.py
  • Confirm the files exist by running:
ls

You should see log_generator.py, log_parser.py, threat_analyzer.py, main.py, and a venv folder in the output.

Now open this project folder in VS Code so the file explorer shows your files.

  • Click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the ai-security-analyzer folder, and click Open.
  • If VS Code asks whether you trust the authors of the files in this folder, click Yes, I trust the authors.
  • Open a new terminal with Ctrl+` and re-activate your virtual environment:

macOS

source venv/bin/activate

Windows

.\venv\Scripts\Activate

Don't see (venv) in your prompt?

On Windows, PowerShell may block script execution by default. Run Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process and then try the activation command again.

On macOS, make sure you are using source venv/bin/activate (not venv/bin/activate without source).

Help me activate my Python virtual environment.

Install the project dependencies

Your project needs two packages: google-genai (Google's SDK for the Gemini API) and python-dotenv (to load environment variables from a file). A requirements.txt file lists these so anyone can install them in one command.

  • In VS Code, create a new file called requirements.txt in your ai-security-analyzer folder.
  • Add the following content to requirements.txt:
google-genai
python-dotenv
  • Save requirements.txt.
  • Install both packages by running this command in your terminal:
pip install -r requirements.txt

What is a requirements.txt file?

A requirements.txt file is a standard Python convention for listing project dependencies. Anyone who clones your project can run pip install -r requirements.txt to get the exact same packages you used.

  • Verify the installation by running:
python -c "from google import genai; from dotenv import load_dotenv; print('All packages installed successfully')"

You should see All packages installed successfully printed in your terminal.

Seeing a ModuleNotFoundError?

Make sure your virtual environment is active (you see (venv) in your prompt). If not, re-activate it and run pip install -r requirements.txt again.

If you see a permissions error, confirm you are installing inside the virtual environment, not system-wide.

Help me fix my package installation.

Get your Gemini API key

The Gemini API requires an API key for authentication. Google AI Studio provides free API keys that work with the free tier.

  • Open your browser and go to aistudio.google.com.
  • Sign in with your Google account if prompted.
  • Click Get API key in the left sidebar.
  • Click Create API key.
  • Copy the key that appears. You will only see it once, so keep it somewhere safe temporarily.

What is an API key?

An API key is a unique string that identifies your application to a service. The Gemini API uses it to track usage and enforce rate limits. Treat it like a password.

Now store the API key in a .env file. This keeps the key out of your source code and loads it automatically when your scripts run.

  • In VS Code, create a new file called .env in your ai-security-analyzer folder.
  • Add the following line, replacing your-api-key-here with the actual key you copied from Google AI Studio:
API_KEY=your-api-key-here
  • Save .env.
  • Now create a second file called .env.example with the same structure but no real key:
API_KEY=your-api-key-here

Why two files?

The .env file holds your real secret and should never be committed to version control. The .env.example file shows collaborators which variables they need to set, without exposing your actual key.

  • Save .env.example.
  • Verify your .env file loads correctly by running:
python -c "from dotenv import load_dotenv; import os; load_dotenv(); print('Key set!' if os.environ.get('API_KEY') else 'Key missing')"

You should see Key set! printed in your terminal.

Seeing 'Key missing'?

Make sure your .env file is saved in the root of your ai-security-analyzer folder (the same folder where your Python files live).

Double-check that there are no extra spaces around the = sign and that you replaced your-api-key-here with your actual key.

Help me load my API key from the .env file.

Your environment is ready. Next up, you will build the log generator that creates realistic attack traffic for your analyzer to detect.

Generate Realistic Security Logs

Your environment is ready. Now it is time to build the data that your AI analyzer will ingest.

Every security analysis starts with logs. Web servers record every incoming request in a structured format, and buried inside that stream of normal traffic are the fingerprints of attackers. In this step, you will build a log generator that produces realistic server logs with three types of embedded attacks.

In this step, get ready to:
  • Build a log generator that outputs Apache Combined Log Format entries.
  • Embed three attack patterns (SQL injection, brute force, directory traversal) into normal traffic.
  • Test the generator and verify the output file.
Build the log generator

Real web servers write one line per request to an access log file. The industry-standard format is the Apache Combined Log Format. Each line records the client IP, timestamp, HTTP method, path, status code, response size, and user agent.

Your generator needs to know what normal traffic looks like and what attacks look like. You will define both as data lists, then the generation logic will mix them together.

  • Open log_generator.py in the VS Code editor.
  • Paste the following code, which defines the function signature and all the data your generator needs:
import random
import datetime


def generate_logs(num_entries=50, output_file="access.log"):
    """Generate realistic Apache Combined Log Format entries with embedded attacks."""
    
    # Normal website paths that regular users would visit
    normal_paths = [
        "/index.html", "/about", "/contact", "/products", "/api/users",
        "/static/style.css", "/images/logo.png", "/blog/post-1", "/faq",
        "/static/app.js"
    ]
    
    # IP addresses representing legitimate visitors
    normal_ips = [
        "192.168.1.100", "10.0.0.45", "172.16.0.88", "192.168.2.201",
        "10.0.1.15", "172.16.1.50", "192.168.3.77", "10.0.2.200"
    ]
    
    # Attacker IPs - each attacker uses a dedicated IP
    attacker_ips = ["203.0.113.42", "198.51.100.77", "45.33.32.156"]
    
    # Mix of browser and script-based user agents
    user_agents = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
        "curl/7.68.0",
        "python-requests/2.28.0"
    ]
    
    # SQL injection attack paths targeting URL parameters
    sql_injection_paths = [
        "/products?id=1' OR 1=1 --",
        "/api/users?name=admin' UNION SELECT * FROM passwords --",
        "/search?q='; DROP TABLE users; --",
        "/login?user=admin'--&pass=anything",
        "/products?category=1; SELECT * FROM information_schema.tables --"
    ]
    
    # Directory traversal paths attempting to read system files
    traversal_paths = [
        "/static/../../etc/passwd",
        "/images/../../../etc/shadow",
        "/files/....//....//etc/hosts",
        "/download?file=../../config/database.yml",
        "/assets/../../proc/self/environ"
    ]

What does this code do?

This sets up the vocabulary for your log generator. Each list represents a category of data:

  • normal_paths and normal_ips represent legitimate website visitors browsing standard pages.
  • attacker_ips assigns a dedicated IP to each attack type so the AI can later correlate IPs with attack patterns.
  • sql_injection_paths contains URL parameters with SQL injection payloads like ' OR 1=1 -- that try to manipulate database queries.
  • traversal_paths contains directory traversal attempts using ../ patterns to escape the web root and read sensitive system files.

You will not see a change yet. The next substep adds the logic that generates and writes the log entries.

Add realistic variation

Real attack traffic hides inside a sea of normal requests. Your generator will mix 70% normal traffic with 30% attack traffic, randomize timestamps across a 24-hour window, and split attacks evenly across three types.

  • In log_generator.py, paste the following code directly below the traversal_paths list you just added:
    base_time = datetime.datetime(2026, 6, 20, 0, 0, 0)
    logs = []
    
    num_attacks = int(num_entries * 0.3)
    num_normal = num_entries - num_attacks
    
    # Generate normal traffic
    for _ in range(num_normal):
        ip = random.choice(normal_ips)
        path = random.choice(normal_paths)
        method = "GET"
        status = random.choice([200, 200, 200, 304, 301])
        size = random.randint(200, 15000)
        agent = random.choice(user_agents[:3])
        timestamp = base_time + datetime.timedelta(
            hours=random.randint(0, 23),
            minutes=random.randint(0, 59),
            seconds=random.randint(0, 59)
        )
        logs.append((timestamp, ip, method, path, status, size, agent))
    
    # Generate SQL injection attacks
    sql_count = num_attacks // 3
    for _ in range(sql_count):
        ip = attacker_ips[0]
        path = random.choice(sql_injection_paths)
        method = "GET"
        status = random.choice([200, 500, 403])
        size = random.randint(0, 500)
        agent = random.choice(user_agents[3:])
        timestamp = base_time + datetime.timedelta(
            hours=random.randint(2, 4),
            minutes=random.randint(0, 59),
            seconds=random.randint(0, 59)
        )
        logs.append((timestamp, ip, method, path, status, size, agent))
    
    # Generate brute force attacks
    brute_count = num_attacks // 3
    for _ in range(brute_count):
        ip = attacker_ips[1]
        path = "/login"
        method = "POST"
        status = 401
        size = random.randint(100, 300)
        agent = random.choice(user_agents[3:])
        timestamp = base_time + datetime.timedelta(
            hours=random.randint(1, 3),
            minutes=random.randint(0, 59),
            seconds=random.randint(0, 59)
        )
        logs.append((timestamp, ip, method, path, status, size, agent))
    
    # Generate directory traversal attacks
    traversal_count = num_attacks - sql_count - brute_count
    for _ in range(traversal_count):
        ip = attacker_ips[2]
        path = random.choice(traversal_paths)
        method = "GET"
        status = random.choice([200, 403, 404])
        size = random.randint(0, 1000)
        agent = random.choice(user_agents[3:])
        timestamp = base_time + datetime.timedelta(
            hours=random.randint(3, 5),
            minutes=random.randint(0, 59),
            seconds=random.randint(0, 59)
        )
        logs.append((timestamp, ip, method, path, status, size, agent))
    
    # Sort by timestamp
    logs.sort(key=lambda x: x[0])
    
    # Write to file in Apache Combined Log Format
    with open(output_file, "w") as f:
        for timestamp, ip, method, path, status, size, agent in logs:
            time_str = timestamp.strftime("%d/%b/%Y:%H:%M:%S +0000")
            log_line = (
                f'{ip} - - [{time_str}] '
                f'"\{method} {path} HTTP/1.1" {status} {size} '
                f'"-" "{agent}"'
            )
            f.write(log_line + "\n")
    
    print(f"Generated {num_entries} log entries in '{output_file}'")
    print(f"  - Normal traffic: {num_normal}")
    print(f"  - Attack traffic: {num_attacks}")
    return output_file


if __name__ == "__main__":
    generate_logs()

What does this code do?

This is the generation engine. It creates entries in four phases:

  • Normal traffic (70%): random GETs to standard pages from legitimate IPs, using browser user agents. Status codes are mostly 200 (success) with occasional 304 and 301 responses.
  • SQL injection (10%): requests from 203.0.113.42 using command-line tools like curl or python-requests.
  • Brute force (10%): repeated POST requests to /login from 198.51.100.77, all returning 401 (unauthorized).
  • Directory traversal (10%): path manipulation attempts from 45.33.32.156.

After generating all entries, the code sorts them by timestamp so the log reads chronologically. Then it writes each entry in the standard Apache Combined Log Format.

  • Save log_generator.py.

Seeing a syntax error on save?

  • Make sure the second code block is indented inside the generate_logs function body. Every line from base_time through return output_file must have 4 spaces of indentation.
  • The if __name__ block at the bottom should have zero indentation (it sits outside the function).
  • Check that the f-string quotes in the file-writing section are not accidentally modified. The outer quotes must be single quotes and the inner quotes must be double quotes.

Still stuck? Help me fix a syntax error in my log_generator.py file

Test the generator

Your generator is complete. Time to run it and confirm it produces a valid log file.

  • Run the generator by executing this command in your terminal:
python log_generator.py
  • You should see output confirming 50 log entries were generated:
Generated 50 log entries in 'access.log'
  - Normal traffic: 35
  - Attack traffic: 15
  • Visually inspect the first few entries of your log file by running:
python -c "f=open('access.log'); [print(l.strip()) for l in f.readlines()[:5]]; f.close()"

You should see lines in this format, with IP addresses, timestamps, HTTP requests, and status codes.

What should I see?

Each line follows the Apache Combined Log Format pattern:

192.168.1.100 - - [20/Jun/2026:01:23:45 +0000] "GET /about HTTP/1.1" 200 4521 "-" "Mozilla/5.0 ..."

Scroll through and look for suspicious entries. You should spot SQL keywords like OR 1=1 in paths, repeated POST /login requests with 401 status codes, and ../ patterns in URLs.

🙋‍♀️ See 0 entries or a missing file?

  • Make sure you saved log_generator.py before running it.
  • Make sure your terminal is in the ai-security-analyzer folder. Run ls and confirm you see log_generator.py listed.
  • If you see an IndentationError, double-check the tab in the full code below matches your file.

Still stuck? Help me debug why my log generator is not producing output

\u2714\ufe0f Awesome, I've got everything!

Great. Make sure you have saved log_generator.py and that access.log exists in your project folder.

\u24e7 I'd like to double check the full code

Here is the complete log_generator.py file. Make sure your version matches exactly:

import random
import datetime


def generate_logs(num_entries=50, output_file="access.log"):
    """Generate realistic Apache Combined Log Format entries with embedded attacks."""
    
    normal_paths = [
        "/index.html", "/about", "/contact", "/products", "/api/users",
        "/static/style.css", "/images/logo.png", "/blog/post-1", "/faq",
        "/static/app.js"
    ]
    
    normal_ips = [
        "192.168.1.100", "10.0.0.45", "172.16.0.88", "192.168.2.201",
        "10.0.1.15", "172.16.1.50", "192.168.3.77", "10.0.2.200"
    ]
    
    attacker_ips = ["203.0.113.42", "198.51.100.77", "45.33.32.156"]
    
    user_agents = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
        "curl/7.68.0",
        "python-requests/2.28.0"
    ]
    
    sql_injection_paths = [
        "/products?id=1' OR 1=1 --",
        "/api/users?name=admin' UNION SELECT * FROM passwords --",
        "/search?q='; DROP TABLE users; --",
        "/login?user=admin'--&pass=anything",
        "/products?category=1; SELECT * FROM information_schema.tables --"
    ]
    
    traversal_paths = [
        "/static/../../etc/passwd",
        "/images/../../../etc/shadow",
        "/files/....//....//etc/hosts",
        "/download?file=../../config/database.yml",
        "/assets/../../proc/self/environ"
    ]
    
    base_time = datetime.datetime(2026, 6, 20, 0, 0, 0)
    logs = []
    
    num_attacks = int(num_entries * 0.3)
    num_normal = num_entries - num_attacks
    
    # Generate normal traffic
    for _ in range(num_normal):
        ip = random.choice(normal_ips)
        path = random.choice(normal_paths)
        method = "GET"
        status = random.choice([200, 200, 200, 304, 301])
        size = random.randint(200, 15000)
        agent = random.choice(user_agents[:3])
        timestamp = base_time + datetime.timedelta(
            hours=random.randint(0, 23),
            minutes=random.randint(0, 59),
            seconds=random.randint(0, 59)
        )
        logs.append((timestamp, ip, method, path, status, size, agent))
    
    # Generate SQL injection attacks
    sql_count = num_attacks // 3
    for _ in range(sql_count):
        ip = attacker_ips[0]
        path = random.choice(sql_injection_paths)
        method = "GET"
        status = random.choice([200, 500, 403])
        size = random.randint(0, 500)
        agent = random.choice(user_agents[3:])
        timestamp = base_time + datetime.timedelta(
            hours=random.randint(2, 4),
            minutes=random.randint(0, 59),
            seconds=random.randint(0, 59)
        )
        logs.append((timestamp, ip, method, path, status, size, agent))
    
    # Generate brute force attacks
    brute_count = num_attacks // 3
    for _ in range(brute_count):
        ip = attacker_ips[1]
        path = "/login"
        method = "POST"
        status = 401
        size = random.randint(100, 300)
        agent = random.choice(user_agents[3:])
        timestamp = base_time + datetime.timedelta(
            hours=random.randint(1, 3),
            minutes=random.randint(0, 59),
            seconds=random.randint(0, 59)
        )
        logs.append((timestamp, ip, method, path, status, size, agent))
    
    # Generate directory traversal attacks
    traversal_count = num_attacks - sql_count - brute_count
    for _ in range(traversal_count):
        ip = attacker_ips[2]
        path = random.choice(traversal_paths)
        method = "GET"
        status = random.choice([200, 403, 404])
        size = random.randint(0, 1000)
        agent = random.choice(user_agents[3:])
        timestamp = base_time + datetime.timedelta(
            hours=random.randint(3, 5),
            minutes=random.randint(0, 59),
            seconds=random.randint(0, 59)
        )
        logs.append((timestamp, ip, method, path, status, size, agent))
    
    # Sort by timestamp
    logs.sort(key=lambda x: x[0])
    
    # Write to file in Apache Combined Log Format
    with open(output_file, "w") as f:
        for timestamp, ip, method, path, status, size, agent in logs:
            time_str = timestamp.strftime("%d/%b/%Y:%H:%M:%S +0000")
            log_line = (
                f'{ip} - - [{time_str}] '
                f'"\{method} {path} HTTP/1.1" {status} {size} '
                f'"-" "{agent}"'
            )
            f.write(log_line + "\n")
    
    print(f"Generated {num_entries} log entries in '{output_file}'")
    print(f"  - Normal traffic: {num_normal}")
    print(f"  - Attack traffic: {num_attacks}")
    return output_file


if __name__ == "__main__":
    generate_logs()

You now have a file full of simulated security log data. Next up, you will build a parser that extracts structure from these raw text lines and flags the suspicious ones before the AI even gets involved.

Parse and Structure the Logs

You now have 50 lines of raw log data sitting in access.log. A real SOC analyst doesn't read logs line by line. They parse them into structured data so they can query, filter, and flag suspicious activity programmatically.

In this step, you'll build a log parser that extracts fields from each line using regex, then adds a pre-classification layer that flags entries as suspicious before the AI even sees them. This gives the AI a head start by narrowing its focus to the entries that actually matter.

In this step, get ready to:
  • Build a regex-powered log parser that turns raw text into structured dictionaries.
  • Add heuristic pre-classification that flags SQL injection, directory traversal, and brute force patterns.
  • Test the parser and confirm it correctly identifies the attack traffic in your logs.
Build the log parser

Each line in access.log follows the Apache Combined Log Format. A single regex pattern can extract every field (IP, timestamp, method, path, status, size, user agent) from each line into a Python dictionary.

  • Open log_parser.py in the VS Code editor (click it in the Explorer sidebar).
  • Add the imports and constant definitions by pasting this code:
import re
from collections import Counter


LOG_PATTERN = re.compile(
    r'(?P<ip>\d+\.\d+\.\d+\.\d+) - - '
    r'\[(?P<timestamp>[^\]]+)\] '
    r'"(?P<method>\w+) (?P<path>[^ ]+) HTTP/[^"]+" '
    r'(?P<status>\d+) (?P<size>\d+) '
    r'"[^"]*" "(?P<user_agent>[^"]*)"'
)

SQL_KEYWORDS = ["SELECT", "UNION", "DROP", "OR 1=1", "INSERT", "DELETE", "--", ";"]
TRAVERSAL_PATTERNS = ["../", "..\\"]

What does this code do?

LOG_PATTERN is a compiled regex that uses named capture groups (?P<ip>, ?P<method>, etc.) to extract each field from a log line into a dictionary automatically.

SQL_KEYWORDS and TRAVERSAL_PATTERNS define the suspicious signatures you'll check against later. These are the same patterns a real web application firewall watches for.

Now you'll write the function that uses this regex to parse the entire log file.

  • Add the parse_log_file function below the constants by pasting this code:

def parse_log_file(filepath):
    """Parse an Apache Combined Log Format file into structured entries."""
    entries = []
    with open(filepath, "r") as f:
        for line_num, line in enumerate(f, 1):
            match = LOG_PATTERN.match(line.strip())
            if match:
                entry = match.groupdict()
                entry["status"] = int(entry["status"])
                entry["size"] = int(entry["size"])
                entry["line_number"] = line_num
                entry["raw"] = line.strip()
                entries.append(entry)
    return entries

What does this function do?

  • It reads the log file line by line and applies LOG_PATTERN to each line.
  • match.groupdict() converts named regex groups into a dictionary with keys like ip, method, path, and status.
  • It casts status and size to integers so you can do numeric comparisons (like checking for 401 status codes).
  • It also stores the original raw line and line number for traceability.
  • Save log_parser.py.

Seeing a red underline on the regex?

VS Code may show a linting warning on the multi-line regex string. This is cosmetic. The regex uses raw strings (r'...') and Python's implicit string concatenation, which is valid syntax.

If you see an actual SyntaxError when you run the file later, check that every opening parenthesis and quote has a matching closing one.

Help me debug my regex pattern in log_parser.py

Add pre-classification heuristics

Sending all 50 log entries to the AI would waste tokens on harmless traffic. A heuristic pre-filter catches the obvious attack signatures first, so the AI only analyzes entries that are likely threats.

  • Add the flag_suspicious function below parse_log_file by pasting this code:

def flag_suspicious(entries):
    """Flag entries as suspicious based on heuristic pattern matching."""
    ip_fail_count = Counter()

    # Count failed logins per IP
    for entry in entries:
        if entry["status"] == 401:
            ip_fail_count[entry["ip"]] += 1

    suspicious = []
    for entry in entries:
        reasons = []
        path_upper = entry["path"].upper()

        # Check for SQL injection patterns
        for keyword in SQL_KEYWORDS:
            if keyword.upper() in path_upper:
                reasons.append(f"SQL keyword detected: {keyword}")
                break

        # Check for directory traversal
        for pattern in TRAVERSAL_PATTERNS:
            if pattern in entry["path"]:
                reasons.append(f"Directory traversal pattern: {pattern}")
                break

        # Check for brute force (IP with > 5 failed logins)
        if ip_fail_count[entry["ip"]] > 5 and entry["status"] == 401:
            reasons.append(
                f"Brute force suspect: {ip_fail_count[entry['ip']]} failed logins"
            )

        if reasons:
            entry["suspicious_reasons"] = reasons
            suspicious.append(entry)

    return suspicious

What does this function do?

This function applies three heuristic checks to each log entry:

  • SQL injection detection checks if the request path contains keywords like SELECT, UNION, or OR 1=1.
  • Directory traversal detection looks for ../ in the path, which attackers use to escape the web root and read system files.
  • Brute force detection uses Counter to count 401 responses per IP. Any IP with more than 5 failed logins gets flagged.

💡 Why pre-classify before sending to AI?

This is the same approach real SOC teams use. Automated rules catch the obvious threats, then analysts (or in our case, AI) spend their time on the entries that need deeper judgment. It reduces noise and keeps API costs down.

  • Save log_parser.py.
Test the parser

You'll add a test block at the bottom of the file so you can run the parser directly and confirm it works against your generated logs.

  • Add the test block at the bottom of log_parser.py by pasting this code:

if __name__ == "__main__":
    entries = parse_log_file("access.log")
    suspicious = flag_suspicious(entries)
    print(f"Total entries parsed: {len(entries)}")
    print(f"Suspicious entries flagged: {len(suspicious)}")
    for entry in suspicious[:5]:
        print(f"  [{entry['ip']}] {entry['method']} {entry['path']}")
        print(f"    Reasons: {entry['suspicious_reasons']}")

What does this test block do?

The if __name__ == "__main__" guard means this code only runs when you execute the file directly. It parses the log file, runs the heuristic flagging, then prints a summary plus the first 5 suspicious entries for visual inspection.

  • Save log_parser.py.
  • Run the parser by executing this command in your terminal:
python log_parser.py

You should see output showing 50 total entries parsed and approximately 15 suspicious entries flagged. The first 5 suspicious entries should show attacker IPs and their reasons (SQL keywords, traversal patterns, or brute force counts).

Seeing 0 entries parsed?

Make sure access.log exists in the same folder as log_parser.py. If you deleted it, regenerate it by running python log_generator.py first.

If entries are parsed but 0 are flagged as suspicious, check that SQL_KEYWORDS and TRAVERSAL_PATTERNS are defined at the top of your file (not inside a function).

Help me debug why my log parser returns 0 entries or 0 suspicious flags

✔️ Awesome, I've got everything!

Great. Double check you've saved log_parser.py and that your terminal output confirms entries are being parsed and flagged.

ⓧ I'd like to double check the full code

Here is the complete log_parser.py file. Compare it against yours:

import re
from collections import Counter


LOG_PATTERN = re.compile(
    r'(?P<ip>\d+\.\d+\.\d+\.\d+) - - '
    r'\[(?P<timestamp>[^\]]+)\] '
    r'"(?P<method>\w+) (?P<path>[^ ]+) HTTP/[^"]+" '
    r'(?P<status>\d+) (?P<size>\d+) '
    r'"[^"]*" "(?P<user_agent>[^"]*)"'
)

SQL_KEYWORDS = ["SELECT", "UNION", "DROP", "OR 1=1", "INSERT", "DELETE", "--", ";"]
TRAVERSAL_PATTERNS = ["../", "..\\"]


def parse_log_file(filepath):
    """Parse an Apache Combined Log Format file into structured entries."""
    entries = []
    with open(filepath, "r") as f:
        for line_num, line in enumerate(f, 1):
            match = LOG_PATTERN.match(line.strip())
            if match:
                entry = match.groupdict()
                entry["status"] = int(entry["status"])
                entry["size"] = int(entry["size"])
                entry["line_number"] = line_num
                entry["raw"] = line.strip()
                entries.append(entry)
    return entries


def flag_suspicious(entries):
    """Flag entries as suspicious based on heuristic pattern matching."""
    ip_fail_count = Counter()

    # Count failed logins per IP
    for entry in entries:
        if entry["status"] == 401:
            ip_fail_count[entry["ip"]] += 1

    suspicious = []
    for entry in entries:
        reasons = []
        path_upper = entry["path"].upper()

        # Check for SQL injection patterns
        for keyword in SQL_KEYWORDS:
            if keyword.upper() in path_upper:
                reasons.append(f"SQL keyword detected: {keyword}")
                break

        # Check for directory traversal
        for pattern in TRAVERSAL_PATTERNS:
            if pattern in entry["path"]:
                reasons.append(f"Directory traversal pattern: {pattern}")
                break

        # Check for brute force (IP with > 5 failed logins)
        if ip_fail_count[entry["ip"]] > 5 and entry["status"] == 401:
            reasons.append(
                f"Brute force suspect: {ip_fail_count[entry['ip']]} failed logins"
            )

        if reasons:
            entry["suspicious_reasons"] = reasons
            suspicious.append(entry)

    return suspicious


if __name__ == "__main__":
    entries = parse_log_file("access.log")
    suspicious = flag_suspicious(entries)
    print(f"Total entries parsed: {len(entries)}")
    print(f"Suspicious entries flagged: {len(suspicious)}")
    for entry in suspicious[:5]:
        print(f"  [{entry['ip']}] {entry['method']} {entry['path']}")
        print(f"    Reasons: {entry['suspicious_reasons']}")

Your parser can now read raw logs, structure them, and pre-classify suspicious activity. Next up, you'll connect the Gemini AI to analyze these flagged entries like a professional SOC analyst would.

Integrate the Gemini AI for Threat Analysis

Your parser flagged suspicious entries, but a human analyst still needs to review each one, classify the attack type, and map it to a known framework. That manual process is exactly what SOC (Security Operations Center) teams spend hours on every day.

In this step, you will connect your tool to Google's Gemini AI so it can classify threats automatically. The AI will act as a senior SOC analyst, mapping each suspicious log entry to the MITRE ATT&CK framework and returning structured JSON you can process programmatically.

In this step, get ready to:
  • Write a system instruction that gives the AI a SOC analyst persona.
  • Define a JSON schema that forces structured threat classifications from the AI.
  • Build batch processing with retry logic for reliable API calls.
Build the AI analyzer

The analyzer needs two things before it can call the API: a system instruction that tells the AI how to behave, and a client function that establishes the connection.

  • In VS Code, open threat_analyzer.py (currently empty).
  • Add the following imports and system instruction at the top of the file:
import os
import json
import time

from google import genai
from google.genai import types
from dotenv import load_dotenv


SYSTEM_INSTRUCTION = """You are a senior SOC (Security Operations Center) analyst 
with expertise in threat detection and the MITRE ATT&CK framework.

Analyze the following suspicious web server log entries and classify each threat.

For each entry, provide:
- attack_type: The category of attack (SQL Injection, Brute Force, Directory Traversal, or Other)
- mitre_technique_id: The MITRE ATT&CK technique ID (T1190 for Exploit Public-Facing Application which covers SQL injection and directory traversal, T1110 for Brute Force)
- mitre_technique_name: The full technique name
- severity: A score from 1-10 (10 being critical)
- confidence: Your confidence level from 0.0 to 1.0
- remediation: A specific, actionable remediation step

Be precise and technical in your analysis."""

What does this code do?

The imports bring in the Google GenAI SDK (from google import genai), python-dotenv for loading your .env file, and standard libraries for JSON parsing, environment variables, and timing.

The SYSTEM_INSTRUCTION string defines the AI's persona. It tells Gemini to act as a senior SOC analyst and specifies exactly which MITRE ATT&CK technique IDs to use: T1190 for SQL injection and directory traversal, T1110 for brute force.

  • Below the SYSTEM_INSTRUCTION string, add the create_client() function:
def create_client():
    """Create a Gemini API client."""
    load_dotenv()
    api_key = os.environ.get("API_KEY")
    if not api_key:
        raise ValueError(
            "API_KEY not found in .env file. "
            "Get your key at https://aistudio.google.com/apikey"
        )
    return genai.Client(api_key=api_key)

What does this function do?

The create_client() function calls load_dotenv() to read your .env file, then pulls the API_KEY value into an environment variable and creates an authenticated connection to the Gemini API.

If the key is missing, it raises a helpful error message instead of failing silently later.

  • Save threat_analyzer.py.

You won't see a change yet. The next substep adds the schema that defines the AI's output format.

Configure structured JSON output

Without a schema, the AI might return free-text analysis one time and a bullet list the next. A JSON schema forces the model to return a consistent, machine-readable structure every single time.

  • In threat_analyzer.py, find the blank line between your imports and SYSTEM_INSTRUCTION. Add this schema dictionary above SYSTEM_INSTRUCTION:
THREAT_SCHEMA = {
    "type": "object",
    "properties": {
        "threats": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "log_entry": {"type": "string"},
                    "attack_type": {"type": "string"},
                    "mitre_technique_id": {"type": "string"},
                    "mitre_technique_name": {"type": "string"},
                    "severity": {"type": "integer"},
                    "confidence": {"type": "number"},
                    "remediation": {"type": "string"}
                },
                "required": [
                    "log_entry", "attack_type", "mitre_technique_id",
                    "mitre_technique_name", "severity", "confidence",
                    "remediation"
                ]
            }
        }
    },
    "required": ["threats"]
}

What does this schema do?

This Python dictionary defines the exact JSON structure the AI must return. Every threat object must include seven fields: the original log entry, the attack type classification, the MITRE technique ID and name, a severity score, a confidence level, and a remediation suggestion.

When you pass this to the Gemini API with response_mime_type='application/json', the model is constrained to output only valid JSON matching this schema. No free-text, no hallucinated fields.

  • Save threat_analyzer.py.

You won't see a change yet. The next substep uses both the schema and system instruction in the actual API call.

Seeing a red underline under the imports?

If VS Code shows import errors for google.genai, make sure your virtual environment is selected as the Python interpreter. Press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows) and type Python: Select Interpreter, then choose the one pointing to your venv folder.

Help me fix this import error

Handle rate limits and errors

The Gemini free tier allows 10 requests per minute. Your analyzer needs to handle rate-limit responses gracefully and process logs in small batches to stay within limits.

  • At the bottom of threat_analyzer.py (below create_client()), add the analyze_batch() function:
def analyze_batch(client, entries, batch_num, total_batches):
    """Analyze a batch of suspicious log entries using Gemini."""
    log_text = "\n".join(
        f"Entry {i+1}: [{e['ip']}] {e['method']} {e['path']} "
        f"(Status: {e['status']}, Reasons: {e.get('suspicious_reasons', [])})"
        for i, e in enumerate(entries)
    )
    
    prompt = f"Analyze these {len(entries)} suspicious log entries:\n\n{log_text}"
    
    max_retries = 3
    for attempt in range(max_retries):
        try:
            response = client.models.generate_content(
                model="gemini-2.5-flash",
                contents=prompt,
                config=types.GenerateContentConfig(
                    system_instruction=SYSTEM_INSTRUCTION,
                    response_mime_type="application/json",
                    response_json_schema=THREAT_SCHEMA,
                    temperature=0.2,
                )
            )
            
            result = json.loads(response.text)
            print(
                f"  Batch {batch_num}/{total_batches}: "
                f"Analyzed {len(entries)} entries, "
                f"found {len(result.get('threats', []))} threats"
            )
            return result.get("threats", [])
            
        except Exception as e:
            if "429" in str(e) and attempt < max_retries - 1:
                wait_time = 2 ** (attempt + 1)
                print(
                    f"  Rate limited. Waiting {wait_time}s before retry..."
                )
                time.sleep(wait_time)
            else:
                print(f"  Error analyzing batch {batch_num}: {e}")
                return []
    
    return []

What does this function do?

The analyze_batch() function formats suspicious log entries into a prompt, sends them to Gemini, and parses the structured JSON response.

The retry logic handles 429 (rate limit) errors using exponential backoff. On the first retry it waits 2 seconds, on the second retry 4 seconds. The low temperature=0.2 setting makes the AI's classifications more deterministic and consistent.

  • Below analyze_batch(), add the analyze_threats() function that orchestrates batching:
def analyze_threats(suspicious_entries, batch_size=10):
    """Analyze all suspicious entries in batches."""
    client = create_client()
    all_threats = []
    
    batches = [
        suspicious_entries[i:i + batch_size]
        for i in range(0, len(suspicious_entries), batch_size)
    ]
    
    total_batches = len(batches)
    print(f"\nAnalyzing {len(suspicious_entries)} suspicious entries "
          f"in {total_batches} batch(es)...")
    
    for i, batch in enumerate(batches, 1):
        threats = analyze_batch(client, batch, i, total_batches)
        all_threats.extend(threats)
        
        # Small delay between batches to respect rate limits
        if i < total_batches:
            time.sleep(1)
    
    return all_threats

What does this function do?

The analyze_threats() function splits all suspicious entries into groups of 10 and feeds each group to analyze_batch() one at a time. With your 50 log entries (roughly 15 suspicious), this means just 1-2 API calls total.

The 1-second delay between batches prevents hitting the 10 requests-per-minute limit on the free tier.

  • Save threat_analyzer.py.
  • Verify the module loads without errors by running this in your terminal:
python -c "from threat_analyzer import analyze_threats; print('Module loaded successfully')"

You should see Module loaded successfully printed in the terminal.

Seeing an import error?

If you see ModuleNotFoundError: No module named 'google.genai', your virtual environment may not be activated. Run venv\Scripts\activate (Windows) or source venv/bin/activate (macOS/Linux) and try again.

If you see a SyntaxError, double-check that the triple-quoted SYSTEM_INSTRUCTION string has matching opening and closing """ delimiters.

Help me debug this import error

✔️ Awesome, I've got everything!

Great. Double check you have saved threat_analyzer.py before moving on.

ⓧ I'd like to double check the full code

Here is the complete threat_analyzer.py file. Make sure yours matches:

import os
import json
import time

from google import genai
from google.genai import types
from dotenv import load_dotenv


THREAT_SCHEMA = {
    "type": "object",
    "properties": {
        "threats": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "log_entry": {"type": "string"},
                    "attack_type": {"type": "string"},
                    "mitre_technique_id": {"type": "string"},
                    "mitre_technique_name": {"type": "string"},
                    "severity": {"type": "integer"},
                    "confidence": {"type": "number"},
                    "remediation": {"type": "string"}
                },
                "required": [
                    "log_entry", "attack_type", "mitre_technique_id",
                    "mitre_technique_name", "severity", "confidence",
                    "remediation"
                ]
            }
        }
    },
    "required": ["threats"]
}

SYSTEM_INSTRUCTION = """You are a senior SOC (Security Operations Center) analyst 
with expertise in threat detection and the MITRE ATT&CK framework.

Analyze the following suspicious web server log entries and classify each threat.

For each entry, provide:
- attack_type: The category of attack (SQL Injection, Brute Force, Directory Traversal, or Other)
- mitre_technique_id: The MITRE ATT&CK technique ID (T1190 for Exploit Public-Facing Application which covers SQL injection and directory traversal, T1110 for Brute Force)
- mitre_technique_name: The full technique name
- severity: A score from 1-10 (10 being critical)
- confidence: Your confidence level from 0.0 to 1.0
- remediation: A specific, actionable remediation step

Be precise and technical in your analysis."""


def create_client():
    """Create a Gemini API client."""
    load_dotenv()
    api_key = os.environ.get("API_KEY")
    if not api_key:
        raise ValueError(
            "API_KEY not found in .env file. "
            "Get your key at https://aistudio.google.com/apikey"
        )
    return genai.Client(api_key=api_key)


def analyze_batch(client, entries, batch_num, total_batches):
    """Analyze a batch of suspicious log entries using Gemini."""
    log_text = "\n".join(
        f"Entry {i+1}: [{e['ip']}] {e['method']} {e['path']} "
        f"(Status: {e['status']}, Reasons: {e.get('suspicious_reasons', [])})"
        for i, e in enumerate(entries)
    )
    
    prompt = f"Analyze these {len(entries)} suspicious log entries:\n\n{log_text}"
    
    max_retries = 3
    for attempt in range(max_retries):
        try:
            response = client.models.generate_content(
                model="gemini-2.5-flash",
                contents=prompt,
                config=types.GenerateContentConfig(
                    system_instruction=SYSTEM_INSTRUCTION,
                    response_mime_type="application/json",
                    response_json_schema=THREAT_SCHEMA,
                    temperature=0.2,
                )
            )
            
            result = json.loads(response.text)
            print(
                f"  Batch {batch_num}/{total_batches}: "
                f"Analyzed {len(entries)} entries, "
                f"found {len(result.get('threats', []))} threats"
            )
            return result.get("threats", [])
            
        except Exception as e:
            if "429" in str(e) and attempt < max_retries - 1:
                wait_time = 2 ** (attempt + 1)
                print(
                    f"  Rate limited. Waiting {wait_time}s before retry..."
                )
                time.sleep(wait_time)
            else:
                print(f"  Error analyzing batch {batch_num}: {e}")
                return []
    
    return []


def analyze_threats(suspicious_entries, batch_size=10):
    """Analyze all suspicious entries in batches."""
    client = create_client()
    all_threats = []
    
    batches = [
        suspicious_entries[i:i + batch_size]
        for i in range(0, len(suspicious_entries), batch_size)
    ]
    
    total_batches = len(batches)
    print(f"\nAnalyzing {len(suspicious_entries)} suspicious entries "
          f"in {total_batches} batch(es)...")
    
    for i, batch in enumerate(batches, 1):
        threats = analyze_batch(client, batch, i, total_batches)
        all_threats.extend(threats)
        
        # Small delay between batches to respect rate limits
        if i < total_batches:
            time.sleep(1)
    
    return all_threats

Your AI-powered threat analyzer is ready. Next up, you will wire everything together in main.py to run the full pipeline end-to-end and generate your threat intelligence report.

Generate the Threat Intelligence Report

Your AI threat analyzer can now classify suspicious log entries and map them to MITRE ATT&CK techniques. The missing piece is bringing everything together into a single command that runs the full pipeline and produces a professional threat intelligence report.

In this step, you will build the orchestration layer in main.py. This file ties together your log generator, parser, and AI analyzer into a 4-step pipeline that outputs both a formatted console summary and a structured JSON report.

In this step, get ready to:
  • Build a report generator that compiles AI results into structured JSON with severity scores and MITRE mappings.
  • Add formatted console output showing severity distribution, attack types, and top attacker IPs.
  • Run the complete end-to-end pipeline and verify the threat intelligence report.
Build the report generator

A threat intelligence report is only useful if it is structured and machine-readable. The generate_report() function takes the raw AI results and organizes them into a report with metadata, a statistical summary, and the full threat list.

  • In main.py, add the imports and the generate_report() function by copying the following code:
import json
import datetime
from collections import Counter

from log_generator import generate_logs
from log_parser import parse_log_file, flag_suspicious
from threat_analyzer import analyze_threats


def generate_report(all_entries, threats):
    """Generate a structured threat intelligence report."""
    severity_dist = Counter()
    attack_types = Counter()
    attacker_ips = Counter()

    for threat in threats:
        severity = threat.get("severity", 0)
        if severity >= 9:
            severity_dist["critical"] += 1
        elif severity >= 7:
            severity_dist["high"] += 1
        elif severity >= 4:
            severity_dist["medium"] += 1
        else:
            severity_dist["low"] += 1

        attack_types[threat.get("attack_type", "Unknown")] += 1

        # Extract IP from log_entry if present
        log_entry = threat.get("log_entry", "")
        if "[" in log_entry and "]" in log_entry:
            ip = log_entry.split("[")[1].split("]")[0]
            attacker_ips[ip] += 1

    report = {
        "report_metadata": {
            "generated_at": datetime.datetime.now().isoformat(),
            "analyzer_version": "1.0.0",
            "total_logs_analyzed": len(all_entries),
            "threats_detected": len(threats)
        },
        "summary": {
            "severity_distribution": dict(severity_dist),
            "attack_type_breakdown": dict(attack_types),
            "top_attacker_ips": dict(attacker_ips.most_common(5))
        },
        "threats": threats
    }

    return report

What does this code do?

The imports pull in all three modules you built in earlier steps. The generate_report() function is where raw AI output becomes a proper threat intelligence product.

  • It categorizes each threat's severity into buckets (critical, high, medium, low) based on the score the AI assigned.
  • It counts attack types and attacker IPs to build the summary section.
  • It packages everything into a dictionary with report_metadata (when the analysis ran, how many logs were processed) and the full threats list with all MITRE ATT&CK mappings intact.
  • Save main.py.

You won't see a change yet. The next chunk adds the console output that makes this report human-readable.

Add console output

A JSON file is great for machines, but a SOC analyst needs a quick visual summary at a glance. The print_summary() function formats the report into a dashboard-style terminal output with severity bars and technique listings.

  • In main.py, add the print_summary() function below generate_report():
def print_summary(report):
    """Print a formatted summary to the console."""
    meta = report["report_metadata"]
    summary = report["summary"]

    print("\n" + "=" * 60)
    print("  AI SECURITY LOG ANALYSIS - THREAT REPORT")
    print("=" * 60)
    print(f"\n  Analysis Time: {meta['generated_at']}")
    print(f"  Total Logs Processed: {meta['total_logs_analyzed']}")
    print(f"  Threats Detected: {meta['threats_detected']}")

    print("\n  SEVERITY DISTRIBUTION:")
    for level in ["critical", "high", "medium", "low"]:
        count = summary["severity_distribution"].get(level, 0)
        bar = "#" * count
        print(f"    {level.upper():10s} | {bar} ({count})")

    print("\n  ATTACK TYPES:")
    for attack_type, count in summary["attack_type_breakdown"].items():
        print(f"    - {attack_type}: {count}")

    print("\n  TOP ATTACKER IPs:")
    for ip, count in summary["top_attacker_ips"].items():
        print(f"    - {ip}: {count} threats")

    print("\n  MITRE ATT&CK TECHNIQUES OBSERVED:")
    techniques = set()
    for threat in report["threats"]:
        tid = threat.get("mitre_technique_id", "")
        tname = threat.get("mitre_technique_name", "")
        if tid:
            techniques.add(f"{tid} - {tname}")
    for tech in sorted(techniques):
        print(f"    - {tech}")

    print("\n" + "=" * 60)
    print("  Full report saved to: threat_report.json")
    print("=" * 60 + "\n")

What does this code do?

  • It prints a bordered header with the analysis timestamp and total counts.
  • The severity distribution uses # characters as a simple bar chart so you can see threat severity at a glance.
  • It loops through the threats list to extract unique MITRE ATT&CK technique IDs (like T1190 and T1110) and displays them sorted.
  • Save main.py.

You won't see output yet. The next chunk adds the main() function that calls everything.

Run the complete pipeline

The main() function is the orchestrator. It runs all four stages in sequence: generate logs, parse them, flag suspicious entries, send them to the AI, and compile the final report.

  • In main.py, add the main() function and the entry point below print_summary():
def main():
    """Run the complete security log analysis pipeline."""
    print("AI Security Log Analyzer v1.0")
    print("-" * 40)

    # Step 1: Generate logs
    print("\n[1/4] Generating security logs...")
    log_file = generate_logs(num_entries=50)

    # Step 2: Parse logs
    print("\n[2/4] Parsing log entries...")
    entries = parse_log_file(log_file)
    print(f"  Parsed {len(entries)} entries")

    # Step 3: Flag suspicious entries
    print("\n[3/4] Flagging suspicious entries...")
    suspicious = flag_suspicious(entries)
    print(f"  Flagged {len(suspicious)} suspicious entries")

    # Step 4: AI analysis
    print("\n[4/4] Running AI threat analysis...")
    threats = analyze_threats(suspicious)

    # Generate and save report
    report = generate_report(entries, threats)

    with open("threat_report.json", "w") as f:
        json.dump(report, f, indent=2)

    print_summary(report)


if __name__ == "__main__":
    main()

What does this code do?

  • The function calls generate_logs() to create fresh attack data, then parse_log_file() and flag_suspicious() to extract and pre-filter entries.
  • It passes only the suspicious entries to analyze_threats(), which sends them to Gemini in batches.
  • The json.dump() call saves the full report to threat_report.json with readable indentation.
  • The if __name__ == "__main__" guard ensures the pipeline only runs when you execute the file directly, not when it is imported.
  • Save main.py.
  • Run the full pipeline by executing this command in your terminal:
python main.py

The pipeline takes 10-30 seconds depending on API response time. You should see progress updates for each of the 4 stages, followed by a formatted threat report in your terminal.

What should I see?

Your terminal should display the pipeline progress ([1/4] through [4/4]), followed by a bordered report showing severity distribution, attack types, top attacker IPs, and MITRE ATT&CK technique IDs like T1190 and T1110.

A new file called threat_report.json should also appear in your project folder.

  • Open threat_report.json in VS Code and confirm it contains classified threats with mitre_technique_id fields and remediation suggestions.

Seeing an error when running the pipeline?

  • If you see ModuleNotFoundError, make sure your virtual environment is still activated. You should see (venv) at the start of your terminal prompt.
  • If you see GEMINI_API_KEY environment variable is not set, your environment variable may have expired. Re-run the set GEMINI_API_KEY=... command from Step 1.
  • If you see a rate limit error, wait 60 seconds and try again. The free tier allows 10 requests per minute.

Help me debug my pipeline error

✔️ Awesome, I've got everything!

Great. Double check you have saved main.py and that threat_report.json exists in your project folder.

ⓧ I'd like to double check the full code

import json
import datetime
from collections import Counter

from log_generator import generate_logs
from log_parser import parse_log_file, flag_suspicious
from threat_analyzer import analyze_threats


def generate_report(all_entries, threats):
    """Generate a structured threat intelligence report."""
    severity_dist = Counter()
    attack_types = Counter()
    attacker_ips = Counter()

    for threat in threats:
        severity = threat.get("severity", 0)
        if severity >= 9:
            severity_dist["critical"] += 1
        elif severity >= 7:
            severity_dist["high"] += 1
        elif severity >= 4:
            severity_dist["medium"] += 1
        else:
            severity_dist["low"] += 1

        attack_types[threat.get("attack_type", "Unknown")] += 1

        # Extract IP from log_entry if present
        log_entry = threat.get("log_entry", "")
        if "[" in log_entry and "]" in log_entry:
            ip = log_entry.split("[")[1].split("]")[0]
            attacker_ips[ip] += 1

    report = {
        "report_metadata": {
            "generated_at": datetime.datetime.now().isoformat(),
            "analyzer_version": "1.0.0",
            "total_logs_analyzed": len(all_entries),
            "threats_detected": len(threats)
        },
        "summary": {
            "severity_distribution": dict(severity_dist),
            "attack_type_breakdown": dict(attack_types),
            "top_attacker_ips": dict(attacker_ips.most_common(5))
        },
        "threats": threats
    }

    return report


def print_summary(report):
    """Print a formatted summary to the console."""
    meta = report["report_metadata"]
    summary = report["summary"]

    print("\n" + "=" * 60)
    print("  AI SECURITY LOG ANALYSIS - THREAT REPORT")
    print("=" * 60)
    print(f"\n  Analysis Time: {meta['generated_at']}")
    print(f"  Total Logs Processed: {meta['total_logs_analyzed']}")
    print(f"  Threats Detected: {meta['threats_detected']}")

    print("\n  SEVERITY DISTRIBUTION:")
    for level in ["critical", "high", "medium", "low"]:
        count = summary["severity_distribution"].get(level, 0)
        bar = "#" * count
        print(f"    {level.upper():10s} | {bar} ({count})")

    print("\n  ATTACK TYPES:")
    for attack_type, count in summary["attack_type_breakdown"].items():
        print(f"    - {attack_type}: {count}")

    print("\n  TOP ATTACKER IPs:")
    for ip, count in summary["top_attacker_ips"].items():
        print(f"    - {ip}: {count} threats")

    print("\n  MITRE ATT&CK TECHNIQUES OBSERVED:")
    techniques = set()
    for threat in report["threats"]:
        tid = threat.get("mitre_technique_id", "")
        tname = threat.get("mitre_technique_name", "")
        if tid:
            techniques.add(f"{tid} - {tname}")
    for tech in sorted(techniques):
        print(f"    - {tech}")

    print("\n" + "=" * 60)
    print("  Full report saved to: threat_report.json")
    print("=" * 60 + "\n")


def main():
    """Run the complete security log analysis pipeline."""
    print("AI Security Log Analyzer v1.0")
    print("-" * 40)

    # Step 1: Generate logs
    print("\n[1/4] Generating security logs...")
    log_file = generate_logs(num_entries=50)

    # Step 2: Parse logs
    print("\n[2/4] Parsing log entries...")
    entries = parse_log_file(log_file)
    print(f"  Parsed {len(entries)} entries")

    # Step 3: Flag suspicious entries
    print("\n[3/4] Flagging suspicious entries...")
    suspicious = flag_suspicious(entries)
    print(f"  Flagged {len(suspicious)} suspicious entries")

    # Step 4: AI analysis
    print("\n[4/4] Running AI threat analysis...")
    threats = analyze_threats(suspicious)

    # Generate and save report
    report = generate_report(entries, threats)

    with open("threat_report.json", "w") as f:
        json.dump(report, f, indent=2)

    print_summary(report)


if __name__ == "__main__":
    main()

You now have a fully working AI-powered security log analyzer. It generates realistic attack traffic, parses raw logs, classifies threats using Gemini, and produces a professional threat intelligence report with MITRE ATT&CK mappings. Ready for a challenge? The Secret Mission takes this further by translating your threat detections into automated firewall rules.

Secret mission

Automated Firewall Rule Generation

Your analyzer detects threats. But what if it could also defend against them? In this extension, you'll add automated firewall rule generation that translates AI threat intelligence into actionable defense rules.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally with no ongoing costs. The Gemini API free tier covers all usage from this project, so there are no surprise charges.

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

Resources you used:

  • The ai-security-analyzer project folder containing all Python scripts, virtual environment, generated logs, and reports (free, local storage only).
  • A Gemini API key stored in Google AI Studio (free tier, no charges at this usage level).

Keep everything running

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

  • Your project files remain on disk in the ai-security-analyzer folder.
  • The Gemini API key stays active for future projects.
  • The virtual environment stays ready to reactivate whenever you return.

Pause - I'll come back to this later

Shut down running processes to free up memory. Your files stay on disk so you can pick up where you left off.

  • Deactivate the virtual environment by running:
deactivate
  • When you come back, reactivate it with:
venv\Scripts\activate
  • Your API key, project files, and generated reports all remain intact.

Delete - I don't want to use this again

Remove all project resources and start fresh. This deletes your code, virtual environment, generated logs, and reports.

  • First, deactivate the virtual environment if it is still active:
deactivate
  • Navigate to the parent folder where ai-security-analyzer lives and delete the project folder.

On macOS/Linux:

cd ~/Desktop
rm -rf ai-security-analyzer

On Windows:

cd ~\Desktop
rmdir /s /q ai-security-analyzer
  • Optionally, revoke your Gemini API key if you no longer need it.
  • Go to aistudio.google.com.
  • Click Get API key to see your keys.
  • Find the key you created for this project and delete it.

Nice Work!

Nice Work!

You just built a complete AI-powered security log analyzer from scratch. This is the exact type of tool that SOC analysts use every day to detect, classify, and respond to threats in production environments.

Here's what you learned:

  • Generated realistic attack traffic and parsed raw logs into structured data using regex and heuristic pattern matching to flag SQL injection, brute force, and directory traversal attempts.
  • Integrated the Gemini API with structured JSON output to classify threats like a senior SOC analyst, complete with severity scores and confidence levels.
  • Mapped every detected threat to the MITRE ATT&CK framework (T1190, T1110) and generated a structured JSON threat intelligence report.
  • Secret Mission: Created automated firewall rules that translate AI threat detection into actionable defense using iptables, ModSecurity, and nginx configurations.

Ready to quiz yourself?