Week 3, day 2. Work through the teaching and the worked checkpoint before attempting the assigned exercise(s). Compare your artifact with the final worked result only afterward.
3.4 Eradication requires a supported cause
Revoking a session is a containment action. It is not proof that a compromised credential, malicious browser extension, faulty training script or application bug has been removed. Before proposing eradication, the team needs to identify a cause well enough to choose a specific fix and a verification test. Examples of possible fixes include resetting a confirmed compromised credential, removing an identified malicious component, correcting a faulty application change or repairing an authorization rule. None of those causes is established in R1–R24. The case record should therefore say “cause unresolved; eradication plan pending evidence,” while continuing to check whether edits stop.
For example, suppose the session is revoked after 09:37 UTC and a new narrow query shows no later edits. That observation is compatible with at least three explanations: the revoked session caused the edits, the real source stopped independently, or the audit query missed later activity. R22, which was recorded at 09:32 UTC, cannot be used to verify a later revocation. Neither query can isolate a root cause. A proposed credential reset would become better supported if authorized identity evidence established credential misuse, but it would still require checking other sessions, tokens and recovery paths. A proposed application fix would require a reproducible software fault and a negative test demonstrating that the same unauthorized change no longer succeeds. The lesson is to match the fix and its verification to a supported mechanism.
From a plausible story to a falsifiable cause test
An eradication plan begins with a claim about how the unwanted change could occur, not with the name of a tool to run. In the fictional Harbor Library case, R17 says the training owner's listed work covered B17 and B18; R18 shows a later B20 edit associated with desk-a. That discrepancy deserves investigation, but the account label alone cannot identify the person, device or mechanism. R22's earlier quiet query, R23's backup-job status and R24's unattributed exposure concern do not close the causal gap. NIST SP 800-61 Rev. 3 places incident analysis, mitigation and learning within the wider response process. The following test design is OpenCyber Academy's classroom adaptation; it is not a finding about an actual service.
Write each candidate explanation so that a different observation could weaken it. A useful hypothesis record has five fields: the mechanism, current supporting evidence, a competing explanation, the least invasive authorized discriminator and a result that would change the decision. The discriminator should point to a defined source, owner, event interval and collection limit. “Look at more logs” lacks a falsifiable question. “Ask the identity custodian for session issuance and revocation events for desk-a around the B20 edit, with event IDs and a declared time window” can be answered or marked unavailable. Even a matching session event may identify a credential path without establishing who held the credential.
Lesson table; scroll horizontally to read all columns →
| Candidate explanation at 09:37 UTC | Supplied support and limit | Authorized discriminator to request | Result that would weaken this explanation |
|---|---|---|---|
The desk-a credential or session was misused. |
R18 associates the B20 edit with desk-a; the account label is not human attribution. |
Identity owner checks bounded session issuance, token use, device association and other active sessions; compare event IDs with the application edit. | A verified edit through a different principal or a reproducible application path that does not require this session. |
| An application authorization fault allowed an out-of-scope write. | An unplanned booking change exists; no supplied test demonstrates a software fault. | Application owner reproduces the B20-type edit in an isolated copy using an explicitly unauthorized synthetic identity and records before/after state and server-side decision. | The same controlled attempt is denied with reliable state and audit evidence, while another cause has independent support. |
| Approved work, an incomplete task list or a workflow mistake explains the edit. | R17 lists B17/B18, so B20 is not approved by that supplied list; later authorization details are unknown. | Service owner reconciles the change ticket, task version and separately held approval record against the B20 event. | The authoritative approved scope excludes B20 and no later approved change covers it. |
These are competing tests, not three diagnoses. A negative check is meaningful only inside its coverage. If an identity export omits older sessions, it cannot exclude older credential use; if an isolated application test exercises only the current build, it cannot exclude a fault in the earlier build. Record the source version, query interval, filters, clock assumptions and missing fields alongside the result. If a control change has already been made, preserve relevant pre-change evidence and note that a later failed reproduction may reflect the new control rather than the original behavior. A beginner can document these limits without touching a production system.
Build a cause-to-fix contract
For each supported mechanism, link evidence → authorized change → pre-change baseline → post-change test → monitoring → rollback or re-open trigger. The arrows express a decision sequence, not proof that a change succeeds. Containment can proceed while the mechanism remains uncertain; eradication requires a supported cause and a test tied to that cause. The incident lead may keep multiple hypotheses open and seek specialist help when no safe discriminator exists.
Consider a conditional credential-misuse branch. If authorized identity evidence later supports use of a specific compromised session or credential, an identity owner may propose revoking relevant sessions, rotating the credential and reviewing related tokens and recovery paths. Before calling the branch effective, the owner records which sessions and token types are in scope. A controlled synthetic login with the old credential should fail, a newly authorized login should work, and a bounded identity/application audit should show the attempts with usable times and IDs. If another token type or account remains untested, the decision record says so. An empty booking-edit query alone would not prove the old credential became unusable or that no other access path remains. None of these branch observations is supplied by R1–R24.
Consider instead a conditional application-fault branch. If an isolated reproduction shows an unauthorized synthetic account can change a booking through a particular server route, the application owner can design a server-side authorization correction for that route and check comparable routes. Keep a record of the failing baseline on the isolated pre-change build, then repeat the same labeled request against the corrected build. A credible pass requires a denied response, unchanged authoritative booking state and an audit event tied to the request. Also test that an authorized synthetic account can still carry out its intended work; a fix that disables all booking writes may stop the symptom while failing the service requirement. A rollback trigger is a later unauthorized state change or material service failure during the agreed observation window. W1 in section 3.3 illustrates why route coverage matters, but it is a separate write-pause fixture and is not evidence that Harbor Library had this application fault.
The workflow branch is different. If a later authoritative record establishes that B20 was approved but missing from R17's supplied task list, the team should correct the approval-to-execution workflow and its audit trail. It should also revise the incident classification. It would be unsound to describe a credential reset as eradication of an attacker that the evidence never established. If the missing record cannot be obtained, classify this explanation as unresolved rather than treating it as confirmed benign activity.
The decision owner should sign a short verification contract before any cause-specific change: (1) the exact mechanism claimed; (2) the evidence IDs and their limits; (3) the system and route to change; (4) the approving and executing roles; (5) the expected positive and negative synthetic tests; (6) the audit and state evidence to retain; (7) a bounded monitoring interval; and (8) the condition that reopens the case. A post-change test that was never run is recorded as planned, not passed. If the mechanism remains uncertain, the honest status is “containment under review; eradication not established.” Recovery may still be considered under temporary safeguards, but section 3.5 requires separate service-acceptance evidence.
Rebuilds, account deletion and broad purges are high-impact actions. They may destroy evidence or create recovery problems. They need the relevant owners, scope and change controls. A beginner should avoid treating “wipe and restore” as a synonym for responsible response.
3.5 Verify recovery, not merely activity
The recovery lead needs more than R23's successful backup status. A restore claim should name the copy, time, environment, expected contents and test result. NIST SP 800-34 Rev. 1, updated edition discusses contingency planning and recovery priorities; this classroom example adapts that idea to a small booking service. A safe test uses an authorized isolated copy, not the live database during an unresolved event.
For Harbor Library, a proposed return-to-service checklist is:
- Compare the approved booking source with R20 and identify which of
B17–B20need owner-reviewed correction. Do not overwrite bookings solely from a patron report. - Test that authorized staff can view and create a new synthetic booking in an isolated environment, while an unauthorized test identity cannot edit one.
- Confirm that a narrow audit query records the test change with a usable timestamp and actor label.
- Ask the service owner to accept any remaining manual-queue entries and confirm that patrons have a consistent schedule.
- Set a short monitoring window and an explicit rollback trigger if unexplained edits resume.
If the cause is unresolved, the lead may allow limited service with temporary safeguards and close monitoring, or may keep a restricted mode. The record must state that residual uncertainty. “The page loads” is not evidence that bookings are correct or that unauthorized edits have stopped.
Recovery objectives are promises to test, not facts supplied by a backup status
The service owner may define a recovery time objective (RTO) for how long a service can remain unavailable and a recovery point objective (RPO) for how far back its data can acceptably be restored. NIST SP 800-34 Rev. 1, section 3.2 distinguishes these objectives in contingency planning. OpenCyber Academy applies them here as planning questions for a fictional small service; a particular organization's definitions and approved targets must come from its own business-impact analysis.
As a hypothetical planning exercise, imagine the library says room booking should be usable within two hours of an interruption and no more than thirty minutes of accepted booking changes may be lost. Those are targets, not demonstrated performance. If the only usable copy were made at midnight and an interruption occurred at 09:45 UTC, that copy alone could omit more than nine hours of changes. It could not demonstrate a thirty-minute recovery point without another protected source or a reconciliation method. The actual R7 and R23 records do not provide a backup timestamp, a restore duration or a validated final schedule. They therefore establish neither objective. A successful restore test also needs the correct data and acceptable access controls; speed alone is insufficient.
Keep technical restoration and service acceptance separate. The recovery lead can report that a controlled copy starts and passes defined integrity checks. The service owner must still decide whether bookings are correct, manual-queue requests are reconciled and patrons can use the service with known limitations. The privacy contact reviews any newly supported exposure concern; R24 does not close that question. NIST SP 800-184 emphasizes planning, testing and improving recovery. The tests below are a classroom adaptation, not a claim that a backup, a single successful login or a quiet dashboard proves recovery.
3.6 Guided reconciliation and return-to-service lab
This is a paper or local-text simulation, with no live database, account or customer data. First choose and justify either proposed containment option from section 3.3. Then use the new exercise-only cards below. They are introduced to test recovery reasoning; they were not present in R1–R24 and must not be retroactively cited as evidence available to the 09:37 decision.
Card P1 — owner-approved target state at 09:40 UTC. For this lab, the fictional service owner confirms that the planned training changes to B17 and B18 may remain, while B19 and B20 should match the listed room assignments. This is a new fictional approval for the exercise, not an inference from R17.
Lesson table; scroll horizontally to read all columns →
| Booking | P1 approved target | C1 current restricted comparison copy |
|---|---|---|
| B17 | Room 4 | Room 4 |
| B18 | Cancelled | Cancelled |
| B19 | Room 1 | Room 3 |
| B20 | Room 3 | Room 1 |
Card V1 — isolated-copy trial of a proposed owner-approved correction. A synthetic staff test account can create a new test booking, and an unauthorized synthetic account is denied the same edit. The audit record for the authorized test includes a UTC time but has a blank actor identifier. These are supplied observations for the lab, not evidence that the production correction ran and not a test to run against any real service.
Step 1 — Reconcile state. Compare P1 and C1 row by row. List each mismatch and identify the owner who may approve a correction. Do not silently overwrite C1 or assume B17/B18 are wrong merely because they changed earlier; P1 explicitly approves their target states. Write a narrow proposed correction for mismatched bookings and a way to verify the resulting state.
Step 2 — Test access and observability. Use V1 to mark three claims as supported, contradicted or still untested: authorized staff can create a test booking; an unauthorized test identity cannot edit; the audit identifies the actor for every change. A successful application test does not repair missing audit attribution. Record whether full return-to-service acceptance is justified.
Step 3 — Account for the chosen branch. For session revocation, state that the old session's removal must be verified but cannot prove all access paths are closed. For the write pause, account for the two manual-queue requests in Branch P and obtain service-owner sign-off before resuming writes. In both cases identify one monitoring window and one observable rollback or escalation trigger.
Step 4 — Write an acceptance record. State what was verified, which claim failed, what remains unknown about the cause, who owns the unresolved audit defect, and the next review time. If you propose limited service despite the failed audit claim, specify the temporary compensating observation and the approving owner. “Recovery complete” is not an acceptable label while the target booking states or audit criteria remain unresolved.
Expected evidence: a four-row reconciliation table, a three-claim test assessment, a branch-specific queue or identity note, and a signed-off proposal for restricted or restored operation. Assessment rubric: one point each for correct mismatch identification, justified correction authority, accurate V1 interpretation, branch-specific service cost, and an honest acceptance state with an owner and review trigger. A five-point response is coherent for this lab; it is not a production recovery certificate. The worked result follows EX038 in the final answer section.
3.7 Contained lab: validate a recovery comparison before proposing changes
The paper comparison in section 3.6 assumes that every booking ID appears once, that the owner-approved target and the current copy use the same fields, and that the copy can be traced to a particular point in time. Those assumptions are easy to lose in a spreadsheet or an improvised script. A duplicate ID might cause one row to silently replace another in a dictionary; a missing ID might disappear from an inner join; an extra booking could be mistaken for an approved correction. This lab turns those cases into explicit failures or review items before anyone considers changing a booking.
The objective is to produce a deterministic read-only diff of the four synthetic P1 and C1 rows. Use only the Python standard library and in-memory text; the script opens no file, connects to no service and does not edit any record. P1 and C1 are released after the 09:37 containment decision in the simulation. Their values can inform a later recovery proposal, never the earlier decision. NIST SP 800-184 recommends planned recovery tests and improvement; the CSV schema, validation rules and output below are OpenCyber Academy's original classroom example, not a NIST procedure.
Inputs: two miniature CSV strings with exactly booking_id,state columns. The target string transcribes the owner-approved P1 values; current transcribes C1. In an actual authorized recovery, the team would record the approver, copy identifier, source time, collection method and access restriction before trusting a comparison. Those provenance fields are not supplied by this toy script and must not be fabricated. The state values are short fictional labels, not patron information.
Step 1 — Read and validate. Copy both parts of Listing 3.1, in order, into one local scratch Python 3 session you control. parse_state rejects a changed header, a blank or extra field, and a repeated booking ID. A failed input must stop the comparison; using the last duplicate row would make the output depend on input order. The program uses csv.DictReader so that the field names, not column position alone, drive interpretation.
Step 2 — Compare without mutation. compare walks the union of booking IDs. It reports MATCH, MISMATCH, MISSING_CURRENT or UNEXPECTED_CURRENT. The two latter categories are built into the method even though the ordinary four-row fixture has neither. An ID absent from C1 is not safely interpreted as “deleted” without checking whether the export filter or collection failed. An extra ID may be an unrelated legitimate booking; it must be reviewed, not overwritten automatically.
Step 3 — Challenge the input. The final line appends a second B20 row to the current fixture. The parser should reject it before a diff is printed for that variant. This is a deliberate malformed-input test, separate from the valid P1/C1 comparison. It demonstrates a data-quality failure rather than a new event in the Harbor Library case.
Listing 3.1, part 1 of 2 — fixture and validated parser. The next block completes the same program.
import csv
from io import StringIO
TARGET = """booking_id,state
B17,Room 4
B18,Cancelled
B19,Room 1
B20,Room 3
"""
CURRENT = """booking_id,state
B17,Room 4
B18,Cancelled
B19,Room 3
B20,Room 1
"""
def parse_state(label, source):
reader = csv.DictReader(StringIO(source))
if reader.fieldnames != ["booking_id", "state"]:
raise ValueError(f"{label}: unexpected CSV header")
rows = {}
for row in reader:
if None in row or not row["booking_id"] or not row["state"]:
raise ValueError(f"{label}: incomplete or extra field")
booking_id = row["booking_id"].strip()
state = row["state"].strip()
if not booking_id or not state:
raise ValueError(f"{label}: blank value")
if booking_id in rows:
raise ValueError(f"{label}: duplicate booking ID {booking_id}")
rows[booking_id] = state
return rows
Listing 3.1, part 2 of 2 — comparison and run. Append this block immediately after part 1 before executing.
def compare(target, current):
counts = {"MATCH": 0, "MISMATCH": 0, "MISSING_CURRENT": 0,
"UNEXPECTED_CURRENT": 0}
for booking_id in sorted(set(target) | set(current)):
if booking_id not in current:
status = "MISSING_CURRENT"
elif booking_id not in target:
status = "UNEXPECTED_CURRENT"
elif current[booking_id] == target[booking_id]:
status = "MATCH"
else:
status = "MISMATCH"
counts[status] += 1
print(f"{booking_id}: {status} target={target.get(booking_id, '-')}",
f"current={current.get(booking_id, '-')}")
print("counts:", ", ".join(f"{name}={count}" for name, count in counts.items()))
target = parse_state("target", TARGET)
current = parse_state("current", CURRENT)
compare(target, current)
try:
parse_state("current", CURRENT + "B20,Room 1\n")
except ValueError as error:
print("rejected variant:", error)
Supplemental frame check for the declared four-ID P1 exercise
The read-only Listing 3.1 reports differences in supplied rows. It cannot detect a booking ID absent from both files: removing B19 from P1 and C1 yields MATCH=2, MISMATCH=1 with zero missing-current rows; comparing two header-only files produces four zero counters. Neither output establishes that the approved four-booking test frame was present. Keep the original listing and its six-line transcript as a demonstration of a row comparison, then run the following standalone frame check. Its expected IDs are an independently declared premise of this fictional P1 exercise, not a method for discovering every real booking.
import csv
from io import StringIO
EXPECTED_IDS = frozenset({'B17', 'B18', 'B19', 'B20'})
P1 = '''booking_id,state
B17,Room 4
B18,Cancelled
B19,Room 1
B20,Room 3
'''
C1 = '''booking_id,state
B17,Room 4
B18,Cancelled
B19,Room 3
B20,Room 1
'''
def read_csv(source):
reader = csv.DictReader(StringIO(source))
if reader.fieldnames != ['booking_id', 'state']:
raise ValueError('bad header')
result = {}
for row in reader:
if None in row or not row['booking_id'] or not row['state']:
raise ValueError('invalid field count or empty field')
key, value = row['booking_id'].strip(), row['state'].strip()
if not key or not value or key in result:
raise ValueError('blank value or duplicate booking ID')
result[key] = value
return result
def assess_frame(target_csv, current_csv):
target, current = read_csv(target_csv), read_csv(current_csv)
missing_target = sorted(EXPECTED_IDS - target.keys())
extra_target = sorted(target.keys() - EXPECTED_IDS)
if missing_target or extra_target:
return (f'REJECT_TARGET_FRAME missing={",".join(missing_target) or "-"} '
f'extra={",".join(extra_target) or "-"}')
missing_current = sorted(EXPECTED_IDS - current.keys())
extra_current = sorted(current.keys() - EXPECTED_IDS)
matches = sum(target[key] == current[key] for key in EXPECTED_IDS & current.keys())
mismatches = sum(target[key] != current[key] for key in EXPECTED_IDS & current.keys())
state = 'REVIEW_CURRENT_FRAME' if missing_current or extra_current else 'FRAME_COMPLETE_FOR_FOUR_IDS'
return (f'{state} MATCH={matches} MISMATCH={mismatches} '
f'MISSING_CURRENT={len(missing_current)} UNEXPECTED_CURRENT={len(extra_current)}')
CASES = [
('BASE', P1, C1),
('BOTH_OMIT_B19', P1.replace('B19,Room 1\n', ''), C1.replace('B19,Room 3\n', '')),
('HEADER_ONLY', 'booking_id,state\n', 'booking_id,state\n'),
('MISSING_CURRENT_B20', P1, C1.replace('B20,Room 1\n', '')),
('EXTRA_CURRENT_B21', P1, C1 + 'B21,Room 2\n'),
]
for label, expected, observed in CASES:
print(f'{label}: {assess_frame(expected, observed)}')
assert assess_frame(P1, C1).startswith('FRAME_COMPLETE_FOR_FOUR_IDS')
Compare its five-line result with the supplemental worked output. A complete four-ID frame still contains B19/B20 mismatches. A missing or extra current row needs review, not an automatic edit; actual target approval, copy provenance, authorized correction and a new post-change comparison remain external to the script.
Expected evidence: four printed booking statuses, a count summary with two matches and two mismatches, and a separate rejected-duplicate message. Save the output as a classroom note along with the program text; record whether it was run and in which local Python version. The worked output appears in the final answer section. The B19/B20 differences are proposed correction candidates, not permission to edit. The service owner must confirm P1's authority and approve a bounded correction; an operator would then act in an authorized environment and a reviewer would compare a new state copy. C1 is only one point-in-time snapshot. A matching diff after a later correction would still need access and audit checks, including the failed actor field in V1.
Assessment: award one point each for (1) rejecting a duplicate instead of silently choosing a row, (2) identifying B19/B20 as mismatches while retaining B17/B18 as matches, (3) distinguishing missing and unexpected IDs in the method, (4) recording that target approval and copy provenance are external to the script, and (5) proposing an authorized, newly verified correction without claiming it occurred. A passing 5/5 demonstrates sound use of this small comparison, not recovery of a real service. If Python is unavailable, trace the four iterations and duplicate check on paper; the expected statuses and limits are the same.
Worked checkpoint
A suspicious account and an application fault imply different corrections. The response lead can compare a narrow test for each cause, name approval and reversal conditions, and keep eradication unclaimed while the discriminator is missing.
Practice — Lesson 12
EX030 — Risk of observation
Objective: make non-intervention an explicit, reviewable choice. Inputs: R18, R21–R22 and the 09:37 checkpoint. Output: an observation decision card with approver, ten-minute deadline, proposed audit source and interval, query-coverage limit, verified trigger for early intervention, and accepted harm if another edit occurs. State what happens if logging is unavailable or the query is incomplete. A score of 2 does not treat R22's earlier narrow zero result as evidence that the next ten minutes will be quiet.
Worked answer: EX030 in the final appendix.
EX031 — Evidence before change
Objective: preserve enough context without turning preservation into a reason for unbounded delay. Inputs: R9–R10, R13, R15 and R17–R20, all available within R1–R24 at the 09:37 checkpoint; use only the values supplied. R15 states a duration but not its deletion-clock anchor or an exact loss time. Output: a four-row preservation request naming the source/item, bounded purpose, custodian or approval, and what the item cannot establish. Add a decision rule for when verified ongoing harm justifies an approved containment action before all four requests finish; record any omitted item and reason. A score of 2 calls the records requests or supplied summaries unless a new collection actually occurred and avoids bulk patron-data copying.
Worked answer: EX031 in the final appendix.
EX032 — Avoid premature eradication
Objective: repair an unsupported closure claim by making two causes testable. Inputs: R17–R24 and sections 3.3–3.4. Output: (1) a three-sentence correction stating what a proposed session revocation is intended to contain, which actor/cause/path questions remain, and why eradication is not established; (2) a two-row hypothesis card for credential/session misuse and an application authorization fault, each with supplied support, an authorized discriminator, a result that would weaken it and a conditional fix-verification pair; and (3) one sentence explaining why R22, recorded before 09:37, cannot verify the later proposal. Keep planned tests separate from supplied observations. A score of 2 names at least one positive and one negative synthetic test for a proposed fix, plus a scope or provenance limit; it does not assert that revocation, root-cause testing or a fix happened.
Worked answer: EX032 in the final appendix.