Lesson 12 / 22
Containment
Calculating the item gap between early and late containment in the same incident: running eight short-term and four long-term actions across seven regimes; early containment stopping at minute 393 with 10 items reached and cutting 1,161 legitimate work items, versus long-term-only containment stopping at minute 765 with 15 items reached and cutting none; the cost of five extra items being 232 legitimate work items per extra item; the same cut rising to 11,687 items with no long-term action; and, in the yearly balance, the early policy preventing 30 extra items against 9,171 legitimate work items.
Published Updated
In this lesson
- Models short-term and long-term containment actions in terms of the assets they close off and hourly legitimate work
- Runs seven containment regimes on the same incident and derives the containment moment together with the items reached
- Calculates the decision's two-sided cost: legitimate work items cut versus the extra items that being late allows
Contents
The previous lesson measured scope, and the best criterion produced a decision at minute 370. In that lesson, containment was a single action: it applied to every asset in scope the same way, and access stopped. This lesson removes that simplification. Containment is not a single action; it is a set of actions that kick in at different minutes, close off different assets, and produce different costs.
The distinction is between short-term and long-term containment. A short-term action is applied within minutes and, from the moment it is applied, also stops legitimate work on those assets. A long-term action kicks in hours later, does not cut legitimate work, and lifts the short-term cut. What this lesson measures is the item gap between these two types in the same incident.
Containment Actions
IR15: The containment set consists of eight short-term and four long-term actions; each action kicks in a set number of minutes after the decision moment, closes off specific assets, and requires a set number of items to be manually rebuilt. IR16: Access stops once every asset affected at that moment is closed off; if even a single affected asset remains not closed off, access continues. IR17: Every asset class has an hourly legitimate work item count — collector 12, validator 26, billing 18, portal 40, work order 9, field unit 4, backup store and console 2. IR18: Short-term containment lasts until the first long-term action covering the same asset kicks in; if no such action exists, it lasts until the end of the 2,880-minute measurement window. IR19: The decision moment comes from the previous lesson: minute 370. IR20: A long-term action does not cut legitimate work, but it produces items that must be manually rebuilt. IR21: Six of the 24 verified alerts per year are real incidents; under the early-cut policy, short-term containment is applied before verification and, on a non-real alert, lasts for the duration of verification.
// containment/model.mjs — containment model: not a real incident or network. The access // curve, containment actions and legitimate-work rates are data structures; durations are fictional. export const DECISION = 370, WINDOW = 2880; // previous lesson's decision moment; measurement window (min) // access curve: [minute, asset, item name] (same data as the previous lesson) export const ACCESS = [ [0, "collector-03", "execution"], [26, "collector-03", "local identity store"], [58, "validator-01", "service account"], [95, "collector-07", "collector handoff"], [134, "collector-11", "collector handoff"], [171, "validator-02", "second validator"], [214, "billing-01", "read endpoint"], [263, "portal-01", "subscriber copy"], [318, "workorder-01", "work order queue"], [372, "field-01", "management channel"], [441, "store-01", "backup listing"], [505, "billing-01", "archive export"], [588, "collector-09", "second-region set"], [676, "console-01", "console read"], [762, "portal-02", "second portal copy"], [845, "store-01", "backup export"], [951, "billing-02", "second billing endpoint"], [1088, "console-01", "console write authority"]]; export const items = (min) => ACCESS.filter(([t]) => t <= min); export const affected = (min) => [...new Set(items(min).map(([, v]) => v))]; const makeIds = (prefix, n) => Array.from({ length: n }, (_, i) => `${prefix}-${String(i + 1).padStart(2, "0")}`); export const COLLECTOR = makeIds("collector", 12), VALIDATOR = makeIds("validator", 3); export const ACCOUNT = [...COLLECTOR, ...VALIDATOR]; // those that use the shared service account export const APPLICATION = [...makeIds("billing", 2), ...makeIds("portal", 2), "workorder-01"]; export const SCOPE = ["collector-03", "collector-07", "collector-11", "validator-01", "validator-02", "billing-01"]; // scope verified in the previous lesson // hourly legitimate work items per asset class (data) export const LEGIT = { collector: 12, validator: 26, billing: 18, portal: 40, workorder: 9, field: 4, store: 2, console: 2 }; export const hourly = (set) => set.reduce((s, a) => s + (LEGIT[a.split("-")[0]] ?? 0), 0); // containment action: name, type, minutes after decision to apply, asset closed off, // items to be manually rebuilt export const FIELD = makeIds("field", 8), ALL = [...ACCOUNT, ...APPLICATION, ...FIELD, "store-01", "console-01"]; export const ACTIONS = { accountLock: ["lock the service account", "short", 4, ACCOUNT, 0], consoleClose: ["close the management channel", "short", 9, ["console-01"], 0], segmentCut: ["cut the branch segment", "short", 11, COLLECTOR, 0], storeLock: ["set the backup store to read-only", "short", 13, ["store-01"], 0], applicationLock: ["set the application endpoint to read-only", "short", 16, APPLICATION, 0], scopeCut: ["take the verified scope off the network", "short", 19, SCOPE, 0], fieldIsolate: ["take the field units off the network", "short", 23, FIELD, 0], externalCut: ["cut the central pipe's external connection", "short", 31, ALL, 0], accountRekey: ["rekey the service account", "long", 214, ACCOUNT, 15], segmentRule: ["make the segmentation rule permanent", "long", 286, [...COLLECTOR, "console-01"], 4], applicationPatch: ["patch the application endpoint", "long", 341, [...APPLICATION, "store-01"], 5], endpointRebuild: ["rebuild the endpoint", "long", 395, [...FIELD, ...SCOPE], 6], }; export const actionOf = (k) => { const [name, type, min, assets, manual] = ACTIONS[k]; return { key: k, name, type, min, assets, manual }; }; // access stops once every asset affected at that moment is closed off export function apply(decision, keys) { const sorted = keys.map(actionOf).sort((a, b) => a.min - b.min); const closed = new Set(); for (const y of sorted) { const t = decision + y.min; for (const a of y.assets) closed.add(a); if (affected(t).every((a) => closed.has(a))) return { t, closed, stopped: y }; } return { t: null, closed, stopped: null }; } // short-term containment lasts until the long-term action covering the same assets kicks in export function measure(decision, keys) { const y = keys.map(actionOf), s = apply(decision, keys); const short = y.filter((x) => x.type === "short"), long = y.filter((x) => x.type === "long"); // for each asset, the short-term cut lasts until the first long-term action covering // that asset kicks in; if no such action exists, it lasts until the end of the measurement window const liftAt = (a) => Math.min(WINDOW, ...long.filter((u) => u.assets.includes(a)).map((u) => u.min)); let cut = 0; const cutFrom = new Map(); for (const k of short) for (const a of k.assets) cutFrom.set(a, Math.min(cutFrom.get(a) ?? Infinity, k.min)); for (const [a, start] of cutFrom) cut += (LEGIT[a.split("-")[0]] ?? 0) * Math.max(0, liftAt(a) - start) / 60; const end = s.t ?? decision + WINDOW; return { containment: s.t, items: items(end).length, open: s.t ? affected(s.t).filter((a) => !s.closed.has(a)).length : affected(end).length, cut: Math.round(cut), manual: y.reduce((a, x) => a + x.manual, 0), cutAssets: cutFrom.size, short: short.length, long: long.length }; }
The measurement file runs seven containment regimes on the same incident and at the same decision moment, then scans the decision moment to derive the item gap between early and late containment.
// containment/measure.mjs — the item gap between early and late containment in the same // incident: how many legitimate work items short-term containment cuts, how many minutes // later long-term containment kicks in, how many extra items are reached in those minutes. import { DECISION, WINDOW, ACCESS, items, ACTIONS, actionOf, hourly, measure } from "./model.mjs"; const write = (g, ...s) => console.log(s.map((v, i) => (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join("")); const t0 = process.hrtime.bigint(); console.log(`decision moment: minute ${DECISION} (the previous lesson's best scope criterion); ` + `measurement window ${WINDOW} min`); console.log("\n1. containment actions"); const A = [-43, 7, 9, 9, 14, 7]; write(A, "action", "type", "+minutes", "assets", "legit work/hr", "manual"); for (const k of Object.keys(ACTIONS)) { const y = actionOf(k); write(A, y.name, y.type, y.min, y.assets.length, y.type === "short" ? hourly(y.assets) : "-", y.manual || "-"); } const REGIMES = { "narrow short": ["scopeCut"], "account lock": ["accountLock"], "full short": ["scopeCut", "applicationLock", "fieldIsolate"], "wide short": ["accountLock", "consoleClose", "segmentCut", "storeLock", "applicationLock", "fieldIsolate"], "cut external connection": ["externalCut"], "long only": ["accountRekey", "segmentRule", "applicationPatch", "endpointRebuild"], "full short + long": ["scopeCut", "applicationLock", "fieldIsolate", "accountRekey", "segmentRule", "applicationPatch", "endpointRebuild"], }; console.log(`\n2. ${Object.keys(REGIMES).length} containment regimes, same incident and same decision moment`); const B = [-24, 12, 9, 8, 11, 13, 7]; write(B, "regime", "containment", "items", "open", "cut assets", "cut work", "manual"); const R = {}; for (const [name, k] of Object.entries(REGIMES)) { const r = R[name] = measure(DECISION, k); write(B, name, r.containment ?? "not stopped", `${r.items}/${ACCESS.length}`, r.open, r.cutAssets, r.cut, r.manual || "-"); } console.log("\n3. early and late containment in the same incident (decision moment scanned)"); const C = [-9, 11, 8, 11, 8, 12, 12, 16]; write(C, "decision", "early min", "items", "late min", "items", "extra items", "cut work", "work/extra item"); for (const decision of [311, 318, 370, 437, 656]) { const er = measure(decision, REGIMES["full short + long"]), ge = measure(decision, REGIMES["long only"]); const extra = ge.items - er.items; write(C, decision, er.containment, er.items, ge.containment, ge.items, extra, er.cut, extra ? (er.cut / extra).toFixed(0) : "-"); } // yearly alert set: 6 of 24 verified alerts are real incidents (data). Under the early // policy, short-term containment is applied before verification and, on a false alert, lasts VERIFY minutes. const ALERTS_YEAR = 24, REAL = 6, VERIFY = 30; const cutHourly = hourly([...new Set(REGIMES["full short + long"].flatMap((k) => actionOf(k).type === "short" ? actionOf(k).assets : []))]); console.log(`\n4. yearly balance (${ALERTS_YEAR} verified alerts, ${REAL} real incidents; ` + `false-alert cut lasts ${VERIFY} min)`); const D = [-18, 15, 16, 13, 17]; write(D, "policy", "real incident", "false alert", "year cut", "year extra items"); const erY = measure(DECISION, REGIMES["full short + long"]), geY = measure(DECISION, REGIMES["long only"]); const falseCost = Math.round((ALERTS_YEAR - REAL) * cutHourly * VERIFY / 60); write(D, "early cut", REAL * erY.cut, falseCost, REAL * erY.cut + falseCost, 0); write(D, "late cut", 0, 0, 0, REAL * (geY.items - erY.items)); console.log(` early policy's cost per item: ` + `${((REAL * erY.cut + falseCost) / (REAL * (geY.items - erY.items))).toFixed(0)} legitimate work items`); console.log("\n5. extra items late containment allows"); for (const [t, v, name] of items(geY.containment).filter(([t]) => t > erY.containment)) write([-4, -12, -18, -28], "", `minute ${t}`, v, name); const ms = Number(process.hrtime.bigint() - t0) / 1e6; // the raw measurement is not printed, only the band is checked console.log(`\nseven regimes and five decision moments within the 500 ms band: ${ms < 500 ? "yes" : "no"}`);
decision moment: minute 370 (the previous lesson's best scope criterion); measurement window 2880 min
1. containment actions
action type +minutes assets legit work/hr manual
lock the service account short 4 15 222 -
close the management channel short 9 1 2 -
cut the branch segment short 11 12 144 -
set the backup store to read-only short 13 1 2 -
set the application endpoint to read-only short 16 5 125 -
take the verified scope off the network short 19 6 106 -
take the field units off the network short 23 8 32 -
cut the central pipe's external connection short 31 30 383 -
rekey the service account long 214 15 - 15
make the segmentation rule permanent long 286 13 - 4
patch the application endpoint long 341 6 - 5
rebuild the endpoint long 395 14 - 6
2. 7 containment regimes, same incident and same decision moment
regime containment items open cut assets cut work manual
narrow short not stopped 18/18 14 6 5054 -
account lock not stopped 18/18 14 15 10641 -
full short 393 10/18 0 18 11687 -
wide short 393 10/18 0 30 18323 -
cut external connection 401 10/18 0 30 18186 -
long only 765 15/18 0 0 0 30
full short + long 393 10/18 0 18 1161 30
3. early and late containment in the same incident (decision moment scanned)
decision early min items late min items extra items cut work work/extra item
311 330 9 706 14 5 1161 232
318 337 9 713 14 5 1161 232
370 393 10 765 15 5 1161 232
437 778 15 832 15 0 1161 -
656 997 17 1051 17 0 1161 -
4. yearly balance (24 verified alerts, 6 real incidents; false-alert cut lasts 30 min)
policy real incident false alert year cut year extra items
early cut 6966 2205 9171 0
late cut 0 0 0 30
early policy's cost per item: 306 legitimate work items
5. extra items late containment allows
minute 441 store-01 backup listing
minute 505 billing-01 archive export
minute 588 collector-09 second-region set
minute 676 console-01 console read
minute 762 portal-02 second portal copy
seven regimes and five decision moments within the 500 ms band: yes
The numbers belong to the measurement class; their inputs are the assumptions above.
Eight Regimes, Same Incident
The second table runs seven regimes on the same incident and the same decision moment. The first two rows never stop access at all. The narrow short regime cuts six assets, spends 5,054 legitimate work items, and because fourteen assets stay open, all eighteen items are reached; account lock cuts fifteen assets and spends 10,641 items. Both regimes lose on both sides of the decision: legitimate work is cut and access continues.
The full short regime stops it at minute 393 with three actions; the items reached are ten, and no asset stays open. The wide short regime stops it at the same minute but cuts thirty assets and spends 18,323 items; cutting the external connection stops it at minute 401 and spends 18,186 items. The cost of three regimes that reach the same result ranges from 11,687 to 18,323.
Long-term actions set the duration of the short-term cut. In the full short regime, with no long-term action, the cut lasts until the end of the measurement window and spends 11,687 items; once four long-term actions are added, it drops to 1,161 items. Here, the long-term action is the short-term containment’s end date.
The Difference Between Early and Late
The third table is this lesson’s main measurement. Two regimes are compared on the same incident: early (full short plus long) and late (long only). The early regime stops at minute 393 with ten items reached; the late regime stops at minute 765 with fifteen items reached. The gap is five items, at a cost of 1,161 legitimate work items — that is, 232 legitimate work items per extra item. No matter how early the decision is made, this ratio does not change: at minutes 311 and 318 the gap is still five items and the ratio is still 232, because the distance between the short-term and long-term actions is independent of the decision moment.
The table’s last two rows break this. When the decision shifts to minute 437, the early regime stops at minute 778 instead of 393; with a decision made at minute 656, it climbs to minute 997. The reason is that the access curve moves outside scope: at minute 441 the backup store is reached, and at minute 588 the second-region collector set is reached, and the full short regime’s three actions do not cover these assets. Access stops only once the long-term action kicks in. In these two rows, the extra items early containment gains are zero, but the legitimate work it cuts is still 1,161 items. The value of short-term containment depends on the decision-moment scope still being valid.
The fifth table writes out, by name, the five items that late containment allows. Two are data exports, two are access spreading to new assets. The count is five, but the items are not equal in weight, and this model does not measure that.
The Yearly Balance
The fourth table carries the decision into the year. Under the early-cut policy, 6,966 legitimate work items are cut across six real incidents and 2,205 across eighteen non-real alerts — 9,171 items a year. Under the late-cut policy, no items are cut, and in exchange 30 extra items are reached; the early policy’s cost per extra item prevented is 306 legitimate work items.
These two numbers are not in the same unit, and the model cannot convert one into the other; whoever makes the decision sees the two columns and weighs them by their own measure. What the measurement does is keep the decision from being unnumbered: the difference between “contain early” and “verify first” is, in this incident, 9,171 items against 30 items.
Summary
- The three short regimes that stop access do so at minutes 393 and 401 with ten items reached, but their costs range from 11,687 to 18,323 items; once long-term action is added, the cost of the same cut drops to 1,161 items, at the price of thirty items being manually rebuilt.
- Early containment stops at minute 393 with ten items reached, late containment at minute 765 with fifteen; the cost of five extra items is 232 legitimate work items per extra item.
- When the decision is delayed to minute 437, the access curve moves outside scope: the extra items early containment gains drop to zero, while the legitimate work it cuts stays at 1,161 items.
- In the yearly balance, the early policy cuts 9,171 legitimate work items and prevents 30 extra items; 306 legitimate work items per extra item.
Next Step
Access stopped, but the only thing that stopped is access; when systems return to the network, they return with whatever is on them. The next lesson measures that: how many footholds are on the endpoints, how many of them detection methods find, how many cleanup removes, and how many days later a missed point produces reinfection.
To keep your progress and take notes, Log in
My notes
Log in to take notes.