05 · Detection and evidence
Depth: developed lesson · Prerequisites: web authorization lab · Time: two 90-minute sessions.
An alert is a decision made from observations. It becomes useful when you can explain which behavior it detects, which evidence it needs, what it misses and how much unrelated activity it flags. A rule that finds one dramatic example is not yet a measured detector.
This chapter uses six fictional requests from the bundled web lab. You will predict two rules, calculate their confusion matrices, and run an offline evaluator. These are synthetic teaching results, not Deadwire model benchmarks or estimates of production detection quality.
Outcomes
- Define the behavior being labeled before calculating a metric.
- Calculate true positives, false positives, false negatives and true negatives.
- Explain precision and recall with their exact denominators.
- Identify the telemetry missing from a seemingly plausible rule.
Start with a behavior, not an error code
Our target behavior is an intentional attempt to read another actor's record contrary to the lab policy. A blocked attempt is still an attempt. A successful cross-owner read is also an attempt, even though it returns HTTP 200. A learner accidentally requesting a missing record is not labeled malicious merely because it returns an error.
The scenario author supplies the intent labels. A server cannot recover intent just by looking at a status code. This distinction matters: the label is evaluation context, not a field that our detector is allowed to use as a prediction shortcut.
| Case | Mode | Scenario | Status | Target behavior? |
|---|---|---|---|---|
| c1 | Fixed | Alice reads her own record 101 | 200 | No |
| c2 | Vulnerable | Alice intentionally requests Bob's record 202 | 200 | Yes |
| c3 | Fixed | Bob reads his own record 202 | 200 | No |
| c4 | Fixed | Alice intentionally requests Bob's record 202 | 403 | Yes |
| c5 | Fixed | Alice follows a mistaken link to missing record 999 | 404 | No |
| c6 | Fixed | A legitimate learner forgets the identity header | 401 | No |
These six observations are checked against the fixture's response behavior in automated tests. They are not collected production traffic. The distinction between intentional and mistaken requests is part of this fictional scenario.
Session 1: score two rules by hand
Rule A alerts exactly when status == 403. Rule B alerts when status >= 400. Write each rule's alert case IDs before calculating anything.
A true positive is a labeled target behavior that alerts. A false positive is an alert on a negative label. A false negative is a positive label with no alert. A true negative is a negative label with no alert. Each case must occupy exactly one cell for each rule.
precision = TP / (TP + FP) # among alerts, how many match our label?
recall = TP / (TP + FN) # among labeled attempts, how many alert?
A zero denominator is undefined. The evaluator returns JSON null; it does not turn “no alerts” into perfect precision or “no positive examples” into perfect recall.
Worked matrices — compare after your prediction
| Rule | Alerts | TP | FP | FN | TN | Precision | Recall |
|---|---|---|---|---|---|---|---|
| A: exactly 403 | c4 | 1 | 0 | 1 | 4 | 1/1 = 100% | 1/2 = 50% |
| B: 400 or higher | c4, c5, c6 | 1 | 2 | 1 | 2 | 1/3 ≈ 33.3% | 1/2 = 50% |
Both miss c2, the successful cross-owner read. Broadening the error rule adds two false alerts here without finding another target attempt. Rule A's 100% precision is based on one alert in a hand-picked six-case set; it cannot establish reliable real-world performance.
Run the bundled evaluator
From the repository root, with Python 3.12 or newer:
python3 -m labs.detection
The command reads a small local JSON file, writes evaluation JSON to standard output, and makes no network requests. Expect records: 6, positive_labels: 2, synthetic: true, and the two matrices above. The output also records evaluator version 1 and a SHA-256 of the canonicalized JSON input, so a modified dataset has a different identity. No model, Docker service or private book is needed.
The fixture and evaluator live in labs/detection. The optional argument accepts a copied teaching dataset:
python3 -m labs.detection /path/to/your/copied-observations.json
Keep the original unchanged. The evaluator rejects duplicate case IDs, non-boolean labels, invalid statuses and files larger than one megabyte. A rejected dataset is a failed input check, not a detector score.
Session 2: challenge the rule
Copy the synthetic dataset to a private exercise directory. Add a seventh uniquely identified case where a legitimate request is denied because of a mistaken policy configuration. Use status 403 and label false. Before running it, predict the new counts.
Expected counterexample result
Rule A now has TP=1, FP=1, FN=1, TN=4: precision 1/2, recall 1/2. Rule B has TP=1, FP=3, FN=1, TN=2: precision 1/4, recall 1/2. The unchanged recall does not mean the new case was irrelevant; it changes the burden imposed by alerts.
Now ask what a better rule would need to recognize c2. Actor identity, object identity and the relevant ownership policy would let it compare the requested action with the declared boundary. But those values must come from trustworthy telemetry and the policy must be correct. Simply importing the evaluation label would leak the answer into the detector.
The bundled web server logs only request ID, status and mode. The teaching dataset's actor, path and intent context is richer than those logs. You cannot honestly claim an ownership detector is deployable from the current server log alone. Logging design should provide useful correlation while avoiding unnecessary sensitive data. OWASP Logging Cheat Sheet.
Interpret improvement carefully
A useful comparison keeps the dataset and label definition fixed, records the rule version, and explains changed errors. On this fixture, Rule B has worse precision and unchanged recall. In a different population the result may differ. Six deliberately chosen records do not estimate base rates, confidence intervals, alert fatigue or performance on novel attacks.
For a larger evaluation, separate development cases from held-out cases by source or scenario family. Adding a rule for every visible example and then reporting those same examples as independent validation measures memorization of the fixture. Also measure missing telemetry: a detector cannot distinguish cases it never observes.
Evidence and acceptance
Save your initial predictions, both JSON results, the added case and its rationale. Your totals must sum to the number of records for each rule. Your explanation must identify c2 as a miss and state that the labels include fictional intent unavailable from status alone.
Safety and scope
All input is synthetic and local. Do not replace this file with private production logs for publication. The evaluator does not replay HTTP requests or change the web server. Cleanup means removing your copied dataset when it is no longer needed.
Teach-back
- Why can a blocked request be a true positive and a successful response be a false negative?
- Why does a zero-alert rule lack defined precision?
- What changed in the seven-case experiment, and what stayed fixed?
- Which telemetry and policy evidence would be required before testing an ownership-aware rule?
Continue: 06 · Labs and reporting. Windows telemetry, query languages and larger hunting exercises remain in the coverage map.