Capstone — Operate a small defensive security platform¶
Alice has a valid login. She asks for note 2, which is Bob’s payroll draft. The API returns 200 and the body.
By the end of the modules you can explain why that happened, spot it in the logs, and fire an alert on it. The capstone asks you to prove it end to end on one system. You model the platform, attack it inside the lab, detect it, investigate, contain, check that the fix actually held, and let a policy-bound agent help without letting it act alone.
You aren’t building a platform from scratch. notes-api, soc-lite, the
attack simulator and the agent are all provided. The work is operating them
and writing up what you find. Most of it you started in Modules 1–13. The
capstone collects that work, finishes it, and adds three detections you
write yourself.
Looking for a Python engineering project without an LLM component? Choose the independent vulnerability scanner capstone, which progresses from inventory to focused checks, validated paths, and repair verification.
What you hand in¶
Not sure where to start? How to work through maps each milestone to the module where you already began it.
Setup, then Artifacts A–I, all tracked from your local work folder
(docs/capstone/work/, gitignored). The letters are capstone artifacts, not module numbers. Start each file by copying its
template into work/. Never edit a template in place.
flowchart LR
S0["Setup"] --> A1["Artifact A — Model"] --> B1["Artifact B — Telemetry"] --> C1["Artifact C — Emulate"] --> D1["Artifact D — Detect"]
D1 --> E1["Artifact E — Investigate"] --> F1["Artifact F — Contain"] --> G1["Artifact G — Purple"] --> H1["Artifact H — Agent"] --> I1["Artifact I — Review"]
| Milestone | You do | Hand in | Template · example | Started in | Done when |
|---|---|---|---|---|---|
| Setup | make lab-up; read ethics |
— | — | Setup and first lab | Lab binds only to 127.0.0.1; health endpoints 200; simulate.py refuses a non-local --base; /fetch to an outside host returns 400 |
| Artifact A — Model | Turn your Module 1 diagram into a threat model of the whole stack | threat-model.md |
template · Helix | Module 1 | Names assets, trust boundaries, and residual risk |
| Artifact B — Telemetry | Check the events you need exist and carry the right fields. Fix gaps; don’t rebuild logging | cited in attack-coverage.md and incident-report.md |
— | Module 7 | Login, AuthZ, fetch, and search events have UTC time, event name, actor, object, and trace_id |
| Artifact C — Emulate | simulate.py --scenario all |
— | — | Modules 8–9 | Runs against loopback only |
| Artifact D — Detect | Keep DET-001–005 and write three new rules against events no rule covers yet. Give every rule a replay fixture | rules.yaml + fixtures/ + attack-coverage.md |
template, fixtures · Helix | Modules 8, 11 | Eight rules fire on sim traffic, or each miss is documented with a fix. Every fixture fires on its abnormal case and stays quiet on normal traffic. Every mapping has tactic, technique ID, confidence, and limitation |
| Artifact E — Investigate | Open a case; build a UTC timeline; weigh competing hypotheses; preserve-logs.sh |
incident-report.md |
template · Helix | Modules 10–11 | Case exists with timeline entries; the evidence snapshot is unchanged after preservation; RCA names a control defect |
| Artifact F — Contain | Preserve → contain → verify; redeploy with LAB_MODE=false or patch the code, then retest |
containment-runbook.md |
template · Helix | Module 11 | The runbook records the sequence you actually ran, evidence is preserved before containment, and at least one control change is retested |
| Artifact G — Purple | One hypothesis, re-test, TP/FN/TN, one improved rule or control | purple-report.md |
template · Helix | Module 9 | Records the before/after difference in detection or control |
| Artifact H — Agent | /investigate; show a denied action, then an approved one |
agent-run.json |
template | Module 12 | 403 without approval=APPROVE; tools outside the allowlist denied; instruction-like text in evidence ignored |
| Artifact I — Review | At least five architecture findings, plus one decision record for a control you chose | architecture-review.md + security-decision-record.md |
template · Helix; template · Helix | Module 13 | Five or more findings; the decision record names an alternative you considered and the residual risk |
Your three detections. The app already emits authz_failure (invalid
token, admin-blocked), fetch_blocked_safety_rail, ssrf_blocked,
search_error, and note_create. No rule covers any of them. Write your
three rules against those events in labs/detections/rules.yaml, using the
Module 11 DET-001 walkthrough as the model. A rule without a fixture hasn’t
been tested. You just believe it works.
Replay fixtures. For each of the eight rules, save two JSONL files in
work/fixtures/: one where the rule should fire and one where it should stay
quiet. The fire file must produce the rule ID. The quiet file must produce
nothing.
Agent run. Save the /investigate response you used as
work/agent-run.json. If you used a hosted LLM, redact it first.
Don’t commit the optional evidence tarball. It contains dummy secrets.
How long it takes¶
12–20 hours if you kept your module work in work/ (the rows in how to work through sum to 17), 25–30 hours if you’re starting cold (those rows sum to 27). That is the platform cell of the time table. The scanner’s only range is 25–35 hours, and it is a different capstone. How to work through shows which module each artifact comes from.
In a 13-week cohort this is week 13.
To pass¶
Score 80 or more on the rubric and meet every “Done when” in the table. On top of that: no real secrets and no testing outside scope.
Rubric (100 points)¶
Each criterion is marked from one milestone’s hand-in.
| Criterion | Points | Marked from | Full marks |
|---|---|---|---|
| Threat model clarity | 10 | Artifact A threat-model.md |
Assets, STRIDE-or-equivalent, residual risk |
| Telemetry quality | 10 | Artifact B, as cited in attack-coverage.md and incident-report.md |
UTC, event names, actor, object, trace_id |
| Detection quality | 15 | Artifact D rules.yaml + fixtures/ |
Eight rules with fixtures, not all IOC-only, documented FPs |
| ATT&CK discipline | 10 | Artifact D attack-coverage.md |
Confidence and “why wrong”; no matrix theatre |
| Investigation | 15 | Artifact E incident-report.md |
Timeline, hypotheses, impact, RCA |
| Response & recovery | 10 | Artifact F containment-runbook.md |
Approval gate; runbook with evidence-before-containment order; retest |
| Purple validation | 10 | Artifact G purple-report.md |
Hypothesis, evidence, delta |
| Agent safety | 10 | Artifact H agent-run.json |
Policy, untrusted evidence, no unbounded tools |
| Architecture writing | 10 | Artifact I architecture-review.md + security-decision-record.md |
Trade-offs, what not to automate, one decision record |
Worked example (Helix Tickets)¶
The Helix Tickets example is a small incident on a
different product. It shows what a finished timeline, runbook, and review
look like. Wrong product, right shape: if your work mentions Dana,
HELIX_DEBUG, or ticket 42, you’ve modelled the wrong system.
Core failure scenarios¶
Each of these is already part of a “Done when” above. Run them all before you hand in.
| Scenario | Expect | Milestone |
|---|---|---|
Alice reads /notes/2 in LAB_MODE |
DET-002; data in body | Artifact C/Artifact D |
| Same after LAB_MODE=false | 404; no DET-002 success event | Artifact F |
| Six bad passwords | DET-001 | Artifact D |
| Fetch mock-imds in LAB_MODE | dummy JSON; DET-003 | Artifact D |
Fetch http://example.com |
safety rail 400 | Setup |
simulate.py --base http://8.8.8.8 |
script exits | Setup |
Agent action approval=nope |
403 | Artifact H |
| Agent action not in allowlist | 403 | Artifact H |
| Instruction-like text in evidence | stripped or ignored | Artifact H |
Stretch scenarios and goals¶
Not scored. These are what a production SOC has to survive. Pick any that interest you once the core is done. Modules 14–17 lead into several of them.
| Scenario | Expect |
|---|---|
| One required field absent from an event | rejected/quarantined or explicitly marked partial; no silent match |
| Malformed JSON log line | pipeline continues; parse error becomes observable |
| Producer clock skew of five minutes | timeline flags skew; ordering does not silently claim certainty |
| Duplicate event delivery | idempotent ingest or documented duplicate suppression |
| Collector unavailable, then restored | buffered/lost interval measured; recovery documented |
| Stolen dummy session token replay | actor appears valid; behavior/object context drives detection |
| Suspicious but legitimate bulk API use | false positive recorded and rule tuned without hiding true abuse |
| Synthetic dependency alert | owner and reachability are enriched before severity decision |
| Container starts as root or privileged | posture check fails; workload does not pass production review |
| Threat-intel/enrichment source unavailable | investigation continues with lower confidence; no invented result |
| Misleading synthetic evidence contradicts primary log | conflict is surfaced; evidence trust is stated |
| Agent recommends an unsafe action | policy denies it even if a human types ambiguous approval |
| Allowed agent tool returns an error | action is not reported successful; tool error is audited |
| Action verification fails | workflow stops and proposes/executes the documented simulated rollback |
| Partial telemetry from one source only | scope and confidence remain explicitly limited |
Stretch goals:
- Run replay fixtures in CI, not just locally.
- Push past eight detections toward the full event surface (10-20 total).
- Non-root USER in notes-api Dockerfile.
- Rate-limit
/login. - Optional kind deploy with a NetworkPolicy denying metadata.
- Groundedness checks: fail
/investigateif mapping not in catalog. - OpenTelemetry traces exported to a file (not a full vendor APM).
Scope and ethics¶
Authorized lab use only. Scope is this repository’s compose stack on loopback ports. No real cloud accounts, no employer systems, no malware.
- Dummy credentials never go near a real IdP or cloud.
- No persistence, no ransomware simulation, no data destruction labs.
- Hosted LLMs: lab data only, and assume the provider logs it.
- If your repo is public, publish reports without raw dummy secrets.
- Cleanup:
make lab-resetat the end.