AI for Cyber Defenders · lesson 1 of 8 · 120 min
Every morning, the two-person security team at Riverbend Community Credit Union (a made-up bank we use all course) opens a file with thousands of login records. Nobody reads them line by line. They ask the data questions. Today you learn how.
Welcome to the defender’s side
This course is about defense: spotting attacks, sorting alerts, and explaining what happened. A SOC (security operations center) is the team that watches an organization’s systems and responds to trouble. You will build small versions of the tools a SOC uses, and you will learn where AI helps and where it gets things wrong.
Every dataset in this course is synthetic: fake data that our code creates on the spot. No real people, no real passwords, no downloads. IP addresses come from ranges that are reserved for documentation (192.0.2.x, 198.51.100.x, 203.0.113.x), and web addresses use reserved names like example.com and .test, so nothing points at a real site.
Set up your workspace
You have two choices:
- Google Colab — a notebook that runs Python in your browser. It needs a Google account. Google requires account holders in the US to be 13 or older; younger students need a parent-managed account. Since this course is for ages 15–18, a normal school or personal Google account works.
- Local Python 3 — install Python, then run
pip install pandas numpy scikit-learnin a terminal.
Colab already has pandas, numpy and scikit-learn installed, so there is nothing to set up there.
Python and pandas in five ideas
pandas is a Python library for working with tables. A table in pandas is called a DataFrame: rows are events, columns are facts about each event. Five moves cover most security work:
| Move | Code | Security question it answers |
|---|---|---|
| Peek | df.head() | What does this log even look like? |
| Count | df['user'].value_counts() | Who logs in (or fails) the most? |
| Filter | df[df['success'] == False] | Show me only the failures. |
| New column | df['hour'] = df['timestamp'].dt.hour | What time of day did it happen? |
| Group | df.groupby('user').agg(...) | Give me one summary row per person. |
We also use numpy to make random numbers. Setting a seed (default_rng(seed=7)) means you get the same “random” data every time, so your output matches ours.
The lab code
Open a new notebook at colab.research.google.com, paste the code into one cell, and press Shift+Enter. Running locally instead? Save it as a .py file and run python lab1.py.
# Lab 1: Python and Colab for security data
# Riverbend Community Credit Union (fictional) - synthetic login log
import numpy as np
import pandas as pd
rng = np.random.default_rng(seed=7) # same seed = same "random" data every run
users = ["aisha.k", "darnell.w", "keisha.m", "marcus.t", "jada.r",
"terrence.b", "imani.c", "andre.j", "nia.s", "malik.o"]
office_ips = [f"192.0.2.{i}" for i in range(10, 30)] # documentation range
outside_ips = [f"203.0.113.{i}" for i in range(1, 255)] # documentation range
devices = ["laptop", "desktop", "phone"]
rows = []
start = pd.Timestamp("2026-03-02 00:00")
for _ in range(600):
user = rng.choice(users)
# Most logins happen 7am-7pm on office IPs
hour = int(np.clip(rng.normal(13, 3), 0, 23))
minute = int(rng.integers(0, 60))
day = int(rng.integers(0, 7))
ts = start + pd.Timedelta(days=day, hours=hour, minutes=minute)
from_office = rng.random() < 0.85
ip = rng.choice(office_ips) if from_office else rng.choice(outside_ips)
success = rng.random() < 0.93
rows.append({"timestamp": ts, "user": user, "src_ip": ip,
"device": rng.choice(devices, p=[0.5, 0.3, 0.2]),
"success": success})
logins = pd.DataFrame(rows).sort_values("timestamp").reset_index(drop=True)
print("== First 5 rows ==")
print(logins.head())
print("\n== Shape (rows, columns) ==", logins.shape)
print("\n== Column types ==")
print(logins.dtypes)
print("\n== Success vs failure ==")
print(logins["success"].value_counts())
failed = logins[logins["success"] == False]
print("\n== Failed logins per user (top 5) ==")
print(failed["user"].value_counts().head(5))
logins["hour"] = logins["timestamp"].dt.hour
logins["outside_office"] = ~logins["src_ip"].str.startswith("192.0.2.")
print("\n== Logins by hour of day ==")
print(logins["hour"].value_counts().sort_index().to_string())
after_hours = logins[(logins["hour"] < 6) | (logins["hour"] >= 21)]
print("\n== After-hours logins (before 6am or after 9pm):", len(after_hours))
print(after_hours[["timestamp", "user", "src_ip", "success"]].head(10))
summary = logins.groupby("user").agg(
total=("success", "size"),
failures=("success", lambda s: (~s).sum()),
outside=("outside_office", "sum"),
)
summary["fail_rate"] = (summary["failures"] / summary["total"]).round(2)
print("\n== Per-user summary ==")
print(summary.sort_values("fail_rate", ascending=False))
# Save a copy (in Colab this appears in the Files panel)
logins.to_csv("riverbend_logins.csv", index=False)
print("\nSaved riverbend_logins.csv")
What you should see
About 600 logins with roughly 40–45 failures (around 7%). The busiest hours are 10am to 3pm, and only a handful of logins happen before 6am. The per-user summary puts one person (andre.j in our run) at the top with a fail rate near 0.14. That is not proof of an attack. Maybe he keeps mistyping. A defender’s job is to notice the pattern and then ask why.
Analyst habit: a number is a question, not an answer. High fail rate? Check where the failures came from and when, before you call it an attack.
Try it
- Run the lab code and confirm your counts look like ours.
- Add a filter that shows only failed logins from outside the office (
outside_office == True). How many are there? - Find which
devicetype has the highest failure rate usinggroupby('device'). - Change the seed from 7 to your birthday number and rerun. Which numbers changed a lot, and which stayed about the same?
- Write two sentences, as if to your SOC lead, about the user who worries you most and what you would check next.
Check yourself
Why do we use synthetic data and documentation IP ranges in this course?
So we can practice on realistic logs without exposing real people’s information or pointing tools at real systems. Reserved ranges never belong to a real network.
What does df[df['success'] == False] return?
A new DataFrame with only the rows where the login failed. It is a filter: it keeps rows that match the condition.
A user has a 14% failure rate. Is that an attack?
Not by itself. It is a lead. You check the source IPs, the times, and whether a success followed, before deciding.
Key words
- SOC — security operations center; the team that monitors and responds to security events
- Synthetic data — made-up data generated by code that looks realistic
- DataFrame — a pandas table of rows and columns
- Seed — a starting number that makes random results repeatable
- Log — a record of events, such as logins, written by a system
Back to AI for Cyber Defenders
Next lesson: Lab 2: Rule-based detection — catch brute-force logins
