◇ OpenCyber Academy
Engineering · Chapter 38 · English

Chapter 38 — Validate containment and restoration claims

Open, free learning for anyone who wants to protect people and technology. No account is required to read this text or its worked answers.

Chapter 38 — Validate containment and restoration claims

Lesson map

Lesson 38 published. Map, example, EX075.

flowchart TD
  N0["Learning outcomes and connection"]
  N1["38.1 Actions are not outcomes"]
  N2["38.2 Acceptance criteria and negative tests"]
  N3["38.4 Restoration claims"]
  N4["Chapter synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4
  N4 --> EX["Worked example"]
  EX --> Q["Exercise EX075"]

Logical drawing

flowchart LR
  N0["Learning outcomes and connection"]
  N1["38.1 Actions are not outcomes"]
  N2["38.2 Acceptance criteria and negative tests"]
  N3["38.4 Restoration claims"]
  N4["Chapter synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> Q["EX075"]
[Learning outcomes and connection]
    |
    v
[38.1 Actions are not outcomes]
    |
    v
[38.2 Acceptance criteria and negative tests]
    |
    v
[38.4 Restoration claims]
    |
    v
[Chapter synthesis]
    |
    v
[Worked example] --> [Exercise EX075]

Learning outcomes and connection

After this chapter you can (1) write acceptance criteria for containment and restoration claims; (2) design negative tests that try to show a claim false; (3) choose monitoring windows that detect failed containment; and (4) document a claim that cannot yet be supported and its residual risk. It builds on Chapters 15, 27 and 34 and prepares Chapters 45, 47 and 48.

38.1 Actions are not outcomes

"Account disabled" is an action. "Attacker can no longer act as the user" is an outcome. Between them lie sessions, refresh tokens, application passwords, API keys, delegated grants, mail forwarding rules, other footholds and propagation delays. Response reports often state actions as if they were outcomes. Engineering requires that each claimed outcome has an acceptance criterion and evidence. Lab 38 below makes the gap concrete for user u17: disabling the account at minute 55 leaves three tokens valid. The containment report in the worked example below shows how to separate what was done from what was achieved.

38.2 Acceptance criteria and negative tests

For each claim: the criterion (observable condition), the test (preferably negative — an attempt that must fail), and the monitoring window during which recurrence would refute the claim. Examples: "no valid session or token for the account" — test: enumerate tokens and sessions via the IdP; negative test: attempt API call with a captured (test) token; window: 14 days of sign-in and token-use monitoring. "Restored service is free of known persistence" — test: compare scheduled tasks, services and admin accounts with a known-good baseline; window: 30 days of monitoring for indicators.

38.3 Worked example (synthetic): account blocked, token alive

"""Lab 38 (synthetic): verifying a containment claim. Disabling an account does not end issued tokens."""
TOKENS = [  # (token id, user, issued_at minute, lifetime minutes, kind)
 ("tk1", "u17", 0, 60, "access"), ("tk2", "u17", 10, 60 * 24 * 14, "refresh"),
 ("tk3", "u17", 50, 60, "access"), ("tk4", "u22", 20, 60, "access")]
def active(t, disabled_at, revoked, enforce_disable_on_refresh):
    out = []
    for tid, u, iss, life, kind in TOKENS:
        if u != "u17" or tid in revoked: continue
        if iss <= t < iss + life:
            if kind == "refresh" and enforce_disable_on_refresh and t >= disabled_at: continue
            out.append(tid)
    return out
DISABLED_AT = 55
claims = {
 "C1 account disabled at t=55": True,
 "C2 no valid access token at t=56": not [x for x in active(56, DISABLED_AT, set(), False) if x in {"tk1", "tk3"}],
 "C3 no valid credential of any kind at t=56": not active(56, DISABLED_AT, set(), False),
}
for c, v in claims.items(): print(f"{'SUPPORTED  ' if v else 'UNSUPPORTED'} {c}")
print("still valid at t=56 without revocation:", active(56, DISABLED_AT, set(), False))
print("still valid at t=56 after revoking tk1,tk2,tk3:", active(56, DISABLED_AT, {"tk1", "tk2", "tk3"}, False))
print("negative test at t=120 (refresh blocked by provider):", active(120, DISABLED_AT, set(), True))

Expected output:

SUPPORTED   C1 account disabled at t=55
UNSUPPORTED C2 no valid access token at t=56
UNSUPPORTED C3 no valid credential of any kind at t=56
still valid at t=56 without revocation: ['tk1', 'tk2', 'tk3']
still valid at t=56 after revoking tk1,tk2,tk3: []
negative test at t=120 (refresh blocked by provider): []

At HVSS, the account was disabled at minute 55. C1 (account disabled) is supported. C2 (no valid access token) and C3 (no valid credential) are unsupported: two access tokens are valid until their expiry, and a 14-day refresh token remains valid unless the provider enforces account state on refresh. Explicit revocation of all three ends them. The last line is the negative test after configuration: with the provider enforcing account status on refresh, no credential remains at minute 120 — but the earlier access tokens would simply have expired by then too, so the test does not distinguish enforcement from expiry. A better negative test exercises the refresh token immediately after disablement.

The containment report therefore states: "Account disabled at 14:55 (supported). All sessions and tokens revoked at 15:02 (supported by IdP revocation event). Between 14:55 and 15:02 two access tokens and one refresh token remained valid; token-use logs for that interval show no use (supported). Claim that no other credential types (application passwords, OAuth grants to third-party apps) exist for the account: not yet supported — enumeration API for third-party grants not available to the integration identity; residual risk owner: identity service manager; action: grant read scope and enumerate by Friday."

38.4 Restoration claims

Restoration claims combine service and security: the service meets functional acceptance (Chapter 35) and the attacker's access paths identified in the case are closed and monitored. A claim "restored to a clean state" is rarely fully supportable; prefer "restored from a backup verified clean against indicators known at time T; monitoring for 30 days".

For the permits portal of Chapter 35, such a claim would name the 03:55 log-replay point and the indicators known at that time. It would also state that the backup-service credentials the intruder used had been replaced and that monitoring would continue for 30 days.

38.5 Errors and limits

The HVSS containment report from Lab 38 avoids all four errors. It separates the disablement at 14:55 from the revocation at 15:02 rather than reporting disablement as the outcome. It relies on token-use logs for the interval between them, and it holds the claim open for a monitoring window. It also states openly that third-party grants remain unenumerated, naming the identity service manager as owner with a Friday deadline.

Exercises — chapter 38

EX075 — Better negative test. Objective: design discriminating tests. Inputs: Lab 38. Steps: modify the code to test at minute 56 whether the refresh token tk2 is valid with and without provider enforcement; explain why this test is more informative than the minute-120 test. Expected evidence: code and output. Rubric: 3 = shows tk2 valid without enforcement and invalid with it at minute 56, with explanation; 2 = output only; 1 = attempt; 0 = none.

EX076 — Claim register. Objective: document claims honestly. Inputs: a contained ransomware case on one server restored from backup. Steps: write four claims with criterion, evidence, status (supported / unsupported / not yet supported) and residual-risk owner where needed. Expected evidence: table. Rubric: 3 = includes at least one honest "not yet supported" with action; 2 = all supported without justification; 1 = vague; 0 = none.

Chapter synthesis

Containment and restoration are claims about outcomes, not actions. Give each claim a criterion, a negative test and a monitoring window; report what is supported, what is not and who owns the remaining risk.

Continue studying — chapter 38 of 50

← Chapter 37 · Course chapter index · Chapter 39 →

Continue to the next authored lesson.