Skip to content
academia.sh

Lesson 11 / 22

Identification

Measuring scope determination in candidates and minutes: how the seven scope criteria bring between 1 and 19 candidates in a thirty-asset inventory, how the scope step ranges between 3 and 73 minutes, how the log indicator's four candidates in 10 minutes leave the same number of items open as radius two's seventeen candidates in 68 minutes, how the widest criterion cuts legitimate work unnecessarily on thirteen assets, and how two reached collectors cannot be verified by any criterion because they transmit only network flow data.

Published Updated

In this lesson

  • Models the asset inventory, neighborhood graph, and log transmission as data structures
  • Runs seven scope criteria on the same incident: candidate count, scope minutes, and the containment moment
  • Counts together the items a narrow scope leaves open and the assets a wide scope cuts unnecessarily
Contents

The previous lesson split the chain into six steps and counted how many minutes preparation cut from each step. At full preparation, two steps stood out: verification at 30 minutes, scope at 60 minutes. Both answer a single question — is this alert really an incident, and how many assets were affected. The scope step’s 60 minutes was a fixed number in the previous lesson; in this lesson that number is derived from the chosen scope criterion.

Identification does two jobs: verification and scope. The second is a balancing problem, and both sides of the balance are countable. If scope is kept narrow, the scope step shortens and containment is applied early — but access does not stop on affected assets that never made the list. If scope is kept wide, the scope step lengthens, the curve keeps advancing for every minute containment is delayed, and legitimate work is cut unnecessarily on unaffected assets.

Asset, Log, and Access Curve

IR8: The fictional network’s inventory is thirty assets: twelve branch collectors, three validators, two billing systems, two portals, one work order service, one backup store, one management console, and eight field units. The neighborhood graph gives which asset talks to which. IR9: Each asset transmits specific log sources to the central pipe. Whether an asset was affected can only be verified if it transmits a process, identity, or application log entry; on an asset that transmits only network flow data, the connection is visible but execution or a session cannot be verified. IR10: The access curve is eighteen items; each item is reached at one minute on one asset. An asset is affected from the moment its name appears in the curve. IR11: The scope criterion determines the candidate set: topological radius, shared service account, log indicator, or a combination of these. The indicator set is read at the moment the verification step ends; an asset that has not dropped into the log by that point is not in the indicator set. IR12: The scope step’s minutes are derived from the candidate count — a candidate that transmits a verifiable log entry needs 7 minutes of review, one that does not needs 19, and the three-person shift works in parallel. IR13: The chain’s steps outside scope are at the previous lesson’s full-preparation values: verification 30, the remaining four steps 92 minutes total. IR14: Containment is applied to every asset in scope; on an affected asset outside scope, access continues; on an unaffected asset in scope, legitimate work is cut unnecessarily.

// identification/model.mjs — scope-determination model: no real network or incident record.
// The asset inventory, neighborhood graph, log transmission, and access curve are data structures.

export const ALERT = 186, FLAGGED = "collector-03";   // asset the alert flags
export const VERIFICATION = 30, REMAINING = 92;       // chain steps outside scope
export const SHIFT = 3;                               // people on shift

export const MIN_LOGGED = 7, MIN_UNCERTAIN = 19;      // review minutes per candidate

const make = (prefix, n) => Array.from({ length: n }, (_, i) => `${prefix}-${String(i + 1).padStart(2, "0")}`);
// asset: name -> { sources it transmits, uses a shared service account? }
export const ASSET = {};
for (const [i, a] of make("collector", 12).entries())
  ASSET[a] = { sources: i < 6 ? ["process", "network"] : ["network"], account: true };
for (const a of make("validator", 3)) ASSET[a] = { sources: ["process", "identity"], account: true };
for (const a of make("billing", 2)) ASSET[a] = { sources: ["identity", "application"], account: false };
for (const a of make("portal", 2)) ASSET[a] = { sources: ["application"], account: false };
ASSET["workorder-01"] = { sources: ["application"], account: false };
ASSET["store-01"] = { sources: [], account: false };
ASSET["console-01"] = { sources: ["identity"], account: false };
for (const [i, a] of make("field", 8).entries())
  ASSET[a] = { sources: i < 5 ? ["process"] : [], account: false };

// neighbor: which asset talks to which (undirected)
export const NEIGHBOR = {};
const link = (a, b) => { (NEIGHBOR[a] ??= []).push(b); (NEIGHBOR[b] ??= []).push(a); };
make("collector", 12).forEach((t, i) => link(t, `validator-${String(1 + (i / 4 | 0)).padStart(2, "0")}`));
for (const d of make("validator", 3)) { link(d, "billing-01"); link(d, "billing-02"); link(d, "store-01"); }
for (const f of make("billing", 2)) { link(f, "portal-01"); link(f, "portal-02"); }
for (const u of make("field", 8)) link(u, "workorder-01");
for (const t of make("collector", 12)) link("console-01", t);

// access curve: [minute, asset, item name] — the item is reached at that minute (data)
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 = (dk) => ACCESS.filter(([t]) => t <= dk);
export const affected = (dk) => [...new Set(items(dk).map(([, v]) => v))];

// assets reached from the flagged asset within radius r
export function radius(r) {
  let set = new Set([FLAGGED]);
  for (let i = 0; i < r; i++)
    set = new Set([...set, ...[...set].flatMap((a) => NEIGHBOR[a] ?? [])]);
  return [...set];
}
export const accountSet = () => Object.keys(ASSET).filter((a) => ASSET[a].account);
// log indicator: among assets reached by that point, those that dropped an indicator into the log
export const indicatorSet = (dk) =>
  affected(dk).filter((a) => ASSET[a].sources.some((k) => k !== "network"));

export const verifiable = (a) => ASSET[a].sources.some((k) => k !== "network");
export function scopeMinutes(candidates) {
  const k = candidates.filter(verifiable).length, b = candidates.length - k;
  return { candidates: candidates.length, logged: k, uncertain: b,
    min: Math.ceil((k * MIN_LOGGED + b * MIN_UNCERTAIN) / SHIFT) };
}

The measurement file runs seven criteria on the same incident and produces four numbers for each criterion: how many candidates, how many minutes the scope step takes, at which minute containment lands, and how many items are still open at that point.

// identification/measure.mjs — how many candidates a scope criterion brings, how many are
// verifiable, where the scope minute moves containment, how many items an asset left out of
// scope leaves open.
import { ALERT, FLAGGED, VERIFICATION, REMAINING, ASSET, ACCESS, items, affected,
  radius, accountSet, indicatorSet, verifiable, scopeMinutes } 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();
const union = (...k) => [...new Set(k.flat())];

const VERIFY_END = ALERT + VERIFICATION;   // the moment verification ends: the indicator set is read here
const CRITERIA = [["radius 0", radius(0)], ["radius 1", radius(1)], ["radius 2", radius(2)],
  ["shared service account", accountSet()], ["log indicator", indicatorSet(VERIFY_END)],
  ["indicator + account", union(indicatorSet(VERIFY_END), accountSet())],
  ["radius 2 + account", union(radius(2), accountSet())]];

const ALL = Object.keys(ASSET);
console.log(`${ALL.length} assets, ${ALL.filter(verifiable).length} of them transmit verifiable log ` +
  `entries; the alert flagged the "${FLAGGED}" asset at minute ${ALERT}`);
console.log(`verification finished at minute ${VERIFY_END}; assets reached by then ` +
  `${affected(VERIFY_END).length}: ` + affected(VERIFY_END).join(", "));

const R = CRITERIA.map(([name, set]) => {
  const k = scopeMinutes(set), containment = ALERT + VERIFICATION + k.min + REMAINING;
  const aff = affected(containment), covered = aff.filter((a) => set.includes(a));
  const entries = items(containment), closed = entries.filter(([, v]) => set.includes(v));
  return { name, set, ...k, containment, aff, covered, entries, closed,
    // verified: in scope, transmits logs, and actually reached
    verified: set.filter((a) => verifiable(a) && aff.includes(a)).length,
    ruledOut: set.filter((a) => verifiable(a) && !aff.includes(a)).length,
    affUncertain: aff.filter((a) => set.includes(a) && !verifiable(a)).length };
});

console.log("\n1. scope criterion, candidate count, and scope-step minutes");
const A = [-25, 7, 9, 10, 11, 13];
write(A, "criterion", "cand.", "logged", "uncertain", "scope min", "contain min");
for (const r of R) write(A, r.name, r.candidates, r.logged, r.uncertain, r.min, r.containment);

console.log("\n2. what scope sees and misses (at the moment of containment)");
const B = [-25, 11, 10, 11, 9, 7, 20];
write(B, "criterion", "affected", "covered", "missed", "closed", "open", "cut unnecessarily");
for (const r of R) write(B, r.name, r.aff.length, r.covered.length,
  r.aff.length - r.covered.length, r.closed.length, r.entries.length - r.closed.length,
  r.candidates - r.covered.length);

console.log("\n3. candidate verification outcome");
const C = [-25, 12, 11, 10, 24];
write(C, "criterion", "verified", "ruled out", "uncertain", "in scope but uncertain");
for (const r of R) write(C, r.name, r.verified, r.ruledOut, r.uncertain, r.affUncertain);

console.log("\n4. marginal cost of expanding scope");
const D = [-38, 8, 10, 12, 10, 15];
write(D, "expansion", "+cand.", "+minutes", "+covered", "-open", "min/item");
const find = (name) => R.find((r) => r.name === name);
for (const [x, y] of [["radius 0", "radius 1"], ["radius 1", "radius 2"],
    ["radius 2", "radius 2 + account"], ["log indicator", "indicator + account"],
    ["log indicator", "radius 2 + account"]]) {
  const a = find(x), b = find(y);
  const openA = a.entries.length - a.closed.length, openB = b.entries.length - b.closed.length;
  write(D, `${x} -> ${y}`, b.candidates - a.candidates, b.min - a.min,
    b.covered.length - a.covered.length, openA - openB,
    openA === openB ? "-" : ((b.min - a.min) / (openA - openB)).toFixed(1));
}

console.log("\n5. reached but not verifiable by any log");
const widest = find("radius 2 + account");
for (const a of widest.aff.filter((x) => !verifiable(x)))
  write([-4, -16, -32, -14], "", a, `sources it transmits: ${ASSET[a].sources.join(", ") || "none"}`,
    widest.set.includes(a) ? "in scope" : "out of scope");

const ms = Number(process.hrtime.bigint() - t0) / 1e6;   // raw measurement is not printed, only the band is checked
console.log(`\nseven criteria and four scans within the 500 ms band: ${ms < 500 ? "yes" : "no"}`);
30 assets, 20 of them transmit verifiable log entries; the alert flagged the "collector-03" asset at minute 186
verification finished at minute 216; assets reached by then 6: collector-03, validator-01, collector-07, collector-11, validator-02, billing-01

1. scope criterion, candidate count, and scope-step minutes
criterion                  cand.   logged uncertain  scope min  contain min
radius 0                       1        1         0          3          311
radius 1                       3        3         0          7          315
radius 2                      17       10         7         68          376
shared service account        15        9         6         59          367
log indicator                  4        4         0         10          318
indicator + account           16       10         6         62          370
radius 2 + account            19       12         7         73          381

2. what scope sees and misses (at the moment of containment)
criterion                   affected   covered     missed   closed   open   cut unnecessarily
radius 0                           7         1          6        2      6                   0
radius 1                           7         2          5        3      5                   1
radius 2                           9         5          4        6      4                  12
shared service account             8         5          3        6      3                  10
log indicator                      8         4          4        5      4                   0
indicator + account                8         6          2        7      2                  10
radius 2 + account                 9         6          3        7      3                  13

3. candidate verification outcome
criterion                    verified  ruled out uncertain  in scope but uncertain
radius 0                            1          0         0                       0
radius 1                            2          1         0                       0
radius 2                            3          7         7                       2
shared service account              3          6         6                       2
log indicator                       4          0         0                       0
indicator + account                 4          6         6                       2
radius 2 + account                  4          8         7                       2

4. marginal cost of expanding scope
expansion                               +cand.  +minutes    +covered     -open       min/item
radius 0 -> radius 1                         2         4           1         1            4.0
radius 1 -> radius 2                        14        61           3         1           61.0
radius 2 -> radius 2 + account               2         5           1         1            5.0
log indicator -> indicator + account        12        52           2         2           26.0
log indicator -> radius 2 + account         15        63           2         1           63.0

5. reached but not verifiable by any log
    collector-07    sources it transmits: network   in scope
    collector-11    sources it transmits: network   in scope

seven criteria and four scans within the 500 ms band: yes

The numbers belong to the measurement class; their inputs are the assumptions above.

Seven Scope Criteria

The first table converts candidate count into minutes. Radius zero is a single candidate and the scope step takes 3 minutes; radius one is three candidates at 7 minutes. Radius two jumps to seventeen candidates because the whole collector set enters the list through the validators and the management console — the scope step rises to 68 minutes. The shared service account yields fifteen candidates and 59 minutes. The log indicator is four candidates and 10 minutes: this criterion takes the assets that had dropped an indicator into the central pipe by the moment verification ends, and none of them is uncertain, because by definition all of them transmit logs.

The second table compares the same criteria against the outcome. At radius zero, containment is applied at minute 311; by then seven assets are affected and only one of them is in scope; two of the eight items close, six stay open. Radius two waits until minute 376, covers five of nine affected assets, and leaves four items open. The log indicator contains at minute 318, covers four of eight affected assets, and also leaves four items open. Radius two’s thirteen extra candidates and 58 extra minutes do not change the number of items left open.

The best outcome comes from the combination of indicator and account: sixteen candidates, 62 minutes, containment at minute 370, and two open items. The combination of radius two and account is wider at nineteen candidates, but the scope step rises to 73 minutes, containment slips to minute 381, and open items rise to three. A wider scope does not close more items; a delayed scope just lets the curve keep advancing.

Cut Unnecessarily and Unverifiable

The second table’s last column is the decision’s other side. Containment is applied to every asset in scope; on every asset that is in scope but unaffected, legitimate work is cut. Radius zero and the log indicator are zero in this column — every candidate they bring is genuinely affected. Radius two cuts legitimate work unnecessarily on twelve assets, indicator plus account on ten, radius two plus account on thirteen. The log indicator’s precision shows here: all four of its four candidates are affected.

The third table opens up the candidate set. Of radius two plus account’s nineteen candidates, four are verified, eight are ruled out, and seven stay uncertain — that is, neither verifiable nor ruleable-out, because those assets do not transmit a verifiable log. The last column pulls the most annoying subset out of this: the number of assets that are genuinely affected yet stay uncertain is two.

The fifth table names these two assets. collector-07 and collector-11 appear in the access curve at minutes 95 and 134; both transmit only network flow data. They are in scope and containment is applied to them too, but that they were affected cannot be verified by any log. On an asset that transmits no logs, the containment decision is either made by the scope criterion or not made at all.

The Marginal Cost of Expanding

The fourth table converts the cost of moving scope from one criterion to another into minutes per item. Going from radius zero to radius one costs two candidates and 4 minutes, and closes one item: 4.0 minutes per item. Going from radius one to radius two costs fourteen candidates and 61 minutes and still closes only one item: 61.0 minutes per item. Going from radius two to adding account closes one more item for two candidates and 5 minutes.

The cheaper of the two transitions that close two items at once is going from indicator to indicator plus account: two items for twelve candidates and 52 minutes, that is, 26.0 minutes per item. Topological radius is an expensive criterion on this network: because the management console is linked to every collector, the second radius step puts half the inventory on the list at once, and most of the assets it brings are unaffected.

Summary

  • Twenty of the thirty-asset inventory transmit verifiable logs; the seven scope criteria bring between 1 and 19 candidates, and the scope step ranges between 3 and 73 minutes.
  • The log indicator pulls containment to minute 318 with four candidates in 10 minutes and leaves four items open; radius two spends 68 minutes on seventeen candidates, contains at minute 376, and also leaves four items open.
  • The combination of indicator and account gives the fewest open items (two); radius two plus account brings three more candidates and delays by 11 minutes, so it leaves three items open.
  • The cost of a wide scope is unnecessary cutting: radius two plus account cuts legitimate work on thirteen unaffected assets, while the log indicator cuts none.
  • Two of the reached collectors transmit only network flow data; they are in scope, but that they were affected cannot be verified by any log — for the same reason, seven of the remaining seventeen candidates also stay uncertain.

Next Step

In this lesson containment was a single action, applied the same way to every asset in scope. In reality containment is not a single action: cutting a network segment is applied within minutes and stops all of a branch’s legitimate traffic, while rekeying a service account takes hours but reopens legitimate work. The next lesson runs these two types separately on the same incident: how many legitimate work items short-term containment cuts, how many minutes later long-term containment kicks in, and how many extra items get reached in those minutes.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close