---
title: Containment
source: 'https://academia.sh/en/courses/security-operations/containment'
course: 'Security Operations and Incident Response'
language: en
updated: '2026-09-26T03:02:57+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - '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"
---

# 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.

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.

```js
// 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.

```js
// 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.
