Lesson 02 / 15
Asset, Threat and Vulnerability
Separating three terms into the same inventory: the cartesian product of 8 assets, 6 threats, and 12 vulnerabilities is 576, while the valid-triple count drops to 19, or 3.3%; two assets each carrying two vulnerabilities produce 1 and 4 triples; a vulnerability no threat can reach carries zero triples; two threats carrying the same five triples touch 4 and 5 separate assets; closing five of twelve vulnerabilities closes 12 of the 19 triples; and narrowing an asset's zone closes 3, 2, 1, or no triples at all.
Published Updated
In this lesson
- Models the asset, threat, and vulnerability terms as separate lists in the same inventory and counts each one
- Shows step by step, through narrowing, why multiplying the three lists does not give the number of reachable paths
- Separates vulnerability count from triple count: how many triples closing a given number of vulnerabilities actually closes
Contents
The previous lesson scored eight items and counted which direction six decisions moved those scores. The whole computation ran on a single list, and the names of that list’s parts were left unclear: the thing being protected was called “asset,” but what was the thing that would lower a score, what was the thing that let it drop. This lesson splits the same inventory into three separate lists and counts how the three combine.
Asset is the thing being protected: data, a service, a device, a record. Threat is an agent capable of causing harm; the party capable of doing so is called a threat actor. Vulnerability is the weakness in the asset that lets that harm happen. The three are separate lists and are counted separately. In everyday speech they get used interchangeably; once counted, it becomes clear they are not interchangeable.
Three Lists Answer Three Separate Questions
The example is again the fictional regional measurement network, and the asset list is the same eight items as the previous lesson. The inventory below is a model: there is no real network, no real threat record, no real vulnerability list.
SC6: every asset is named by the widest zone it can be reached from — outer, field, inner.
SC7: every vulnerability lives in exactly one asset and carries a type. SC8: every threat
is defined by the set of zones it can reach and the vulnerability types it can use. SC9: an
(asset, vulnerability, threat) triple is valid only if all three conditions hold together — the
vulnerability lives in that asset, the threat reaches that zone, the threat can use that
vulnerability type.
// security/inventory.mjs — fictional measurement network's asset, threat, and vulnerability // inventory (model): there is no real network, threat record, or vulnerability list. No randomness. // SC6: every asset is named by the widest zone it can be reached from (outer, field, inner). export const ASSET = ` reading-data inner billing-record outer work-order-queue field identity-store inner configuration inner audit-log inner meter-firmware field field-app outer` .trim().split("\n").map((r) => { const [name, zone] = r.trim().split(/\s+/); return { name, zone }; }); // SC7: every vulnerability lives in exactly one asset and carries a type. export const VULN = ` outdated-library-version billing-record version default-password configuration credential broad-read-permission reading-data permission unvalidated-input field-app input open-management-endpoint configuration configuration unsigned-update meter-firmware integrity shared-account identity-store credential unencrypted-transfer work-order-queue transfer unretained-log audit-log visibility outdated-agent-version field-app version physical-access-point meter-firmware physical unvalidated-field audit-log input` .trim().split("\n").map((r) => { const [name, asset, type] = r.trim().split(/\s+/); return { name, asset, type, zone: ASSET.find((v) => v.name === asset).zone }; }); // SC8: every threat is defined by the zones it can reach and the vulnerability types it can use. export const THREAT = [ { name: "external-opportunist", zone: ["outer"], type: ["version", "credential", "input", "configuration"] }, { name: "targeted-external", zone: ["outer", "field"], type: ["version", "credential", "input", "configuration", "transfer", "integrity"] }, { name: "field-proximate", zone: ["field"], type: ["physical", "integrity", "transfer"] }, { name: "internal-user-error", zone: ["inner", "field"], type: ["configuration", "permission"] }, { name: "malicious-insider", zone: ["inner", "field", "outer"], type: ["permission", "credential", "visibility", "transfer"] }, { name: "supply-chain", zone: ["field", "inner"], type: ["integrity", "version"] }, ]; // SC9: a triple is valid only if the vulnerability reaches that zone and the threat can use // that vulnerability type. export const valid = (v, t) => t.zone.includes(v.zone) && t.type.includes(v.type); export const TRIPLE = VULN.flatMap((v) => THREAT.filter((t) => valid(v, t)) .map((t) => ({ asset: v.asset, vuln: v.name, threat: t.name }))); export const vulnCount = (a) => VULN.filter((v) => v.asset === a).length; export const tripleCount = (field, name) => TRIPLE.filter((u) => u[field] === name).length; // SC10: an asset's zone can be narrowed by one step (outer -> field -> inner); narrowing // changes which vulnerabilities in that asset are reachable, it does not close them. export const NARROW = { outer: "field", field: "inner", inner: "inner" }; export function narrowedTriple(asset) { return VULN.reduce((n, v) => n + THREAT.filter((t) => t.zone.includes(v.asset === asset ? NARROW[v.zone] : v.zone) && t.type.includes(v.type)).length, 0); }
// security/count.mjs — three lists in one inventory: how many assets, threats, vulns, and valid triples. import { ASSET, VULN, THREAT, TRIPLE, NARROW, vulnCount, tripleCount, narrowedTriple } from "./inventory.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 cartesian = ASSET.length * THREAT.length * VULN.length; console.log(`${ASSET.length} assets, ${THREAT.length} threats, ${VULN.length} vulns; ` + `cartesian product ${cartesian}, since each vuln is already tied to one asset the ` + `candidates are ${VULN.length * THREAT.length}, valid triples ${TRIPLE.length}`); const A = [-21, 8, 8, 9, 14]; console.log("\n1. vulns and valid triples per asset"); write(A, "asset", "zone", "vulns", "triples", "triples/vuln"); for (const v of ASSET) write(A, v.name, v.zone, vulnCount(v.name), tripleCount("asset", v.name), vulnCount(v.name) ? (tripleCount("asset", v.name) / vulnCount(v.name)).toFixed(1) : "-"); write(A, "total", "-", VULN.length, TRIPLE.length, (TRIPLE.length / VULN.length).toFixed(1)); const B = [-26, 15, 8, 9]; console.log("\n2. how many threats can use each vuln"); write(B, "vuln", "type", "zone", "triples"); for (const v of [...VULN].sort((x, y) => tripleCount("vuln", y.name) - tripleCount("vuln", x.name))) write(B, v.name, v.type, v.zone, tripleCount("vuln", v.name)); const C = [-23, 8, 7, 9, 10]; console.log("\n3. reach and use per threat"); write(C, "threat", "zones", "types", "triples", "assets"); for (const t of THREAT) write(C, t.name, t.zone.length, t.type.length, tripleCount("threat", t.name), new Set(TRIPLE.filter((u) => u.threat === t.name).map((u) => u.asset)).size); const D = [-48, 12, 9]; console.log("\n4. where the product narrows"); write(D, "step", "candidates", "left %"); const pct = (n) => ((n / cartesian) * 100).toFixed(1); for (const [name, n] of [["asset x threat x vuln", cartesian], ["vuln already tied to one asset (vuln x threat)", VULN.length * THREAT.length], ["threat can reach that zone", VULN.reduce((a, x) => a + THREAT.filter((t) => t.zone.includes(x.zone)).length, 0)], ["threat can use that vuln type", TRIPLE.length]]) write(D, name, n, pct(n)); const sorted = [...VULN].map((v) => tripleCount("vuln", v.name)).sort((x, y) => y - x); const running = sorted.map(((s) => (n) => (s += n))(0)); console.log(`\nvuln order by triples carried: ${sorted.join(" ")}`); console.log(`closing sum: ${running.join(" ")} (5 of 12 vulns close ${running[4]}/${TRIPLE.length}` + ` triples); vuln carrying zero triples: ` + VULN.filter((v) => tripleCount("vuln", v.name) === 0).map((v) => v.name).join(", ")); const E = [-21, 8, 9, 9, 9]; console.log("\n5. narrowing an asset's zone by one step (vuln count unchanged)"); write(E, "asset", "zone", "new", "triples", "closed"); for (const v of ASSET) write(E, v.name, v.zone, NARROW[v.zone], narrowedTriple(v.name), TRIPLE.length - narrowedTriple(v.name));
8 assets, 6 threats, 12 vulns; cartesian product 576, since each vuln is already tied to one asset the candidates are 72, valid triples 19 1. vulns and valid triples per asset asset zone vulns triples triples/vuln reading-data inner 1 2 2.0 billing-record outer 1 2 2.0 work-order-queue field 1 3 3.0 identity-store inner 1 1 1.0 configuration inner 2 2 1.0 audit-log inner 2 1 0.5 meter-firmware field 2 4 2.0 field-app outer 2 4 2.0 total - 12 19 1.6 2. how many threats can use each vuln vuln type zone triples unsigned-update integrity field 3 unencrypted-transfer transfer field 3 outdated-library-version version outer 2 broad-read-permission permission inner 2 unvalidated-input input outer 2 outdated-agent-version version outer 2 default-password credential inner 1 open-management-endpoint configuration inner 1 shared-account credential inner 1 unretained-log visibility inner 1 physical-access-point physical field 1 unvalidated-field input inner 0 3. reach and use per threat threat zones types triples assets external-opportunist 1 4 3 2 targeted-external 2 6 5 4 field-proximate 1 3 3 2 internal-user-error 2 2 2 2 malicious-insider 3 4 5 5 supply-chain 2 2 1 1 4. where the product narrows step candidates left % asset x threat x vuln 576 100.0 vuln already tied to one asset (vuln x threat) 72 12.5 threat can reach that zone 42 7.3 threat can use that vuln type 19 3.3 vuln order by triples carried: 3 3 2 2 2 2 1 1 1 1 1 0 closing sum: 3 6 8 10 12 14 15 16 17 18 19 19 (5 of 12 vulns close 12/19 triples); vuln carrying zero triples: unvalidated-field 5. narrowing an asset's zone by one step (vuln count unchanged) asset zone new triples closed reading-data inner inner 19 0 billing-record outer field 19 0 work-order-queue field inner 17 2 identity-store inner inner 19 0 configuration inner inner 19 0 audit-log inner inner 19 0 meter-firmware field inner 16 3 field-app outer field 18 1
Numbers belong to the measurement class; their inputs are SC6–SC9.
The Product Is Not a Path Count
The fourth table is this lesson’s central finding. Multiplying the lengths of the three lists gives 576. That number measures nothing, because the lists are not independent. The first narrowing is definitional: every vulnerability already lives in exactly one asset, so the asset dimension is not a separate factor — 72 candidates remain, 12.5% of the product. The second narrowing comes from reach: the threat has to reach the zone the vulnerability lives in, and the candidates drop to 42. The third comes from capability: the threat has to be able to use that vulnerability’s type, leaving 19 triples — 3.3% of the starting product.
The gap is not a difference of scale but of kind. The sentence “we have eight assets, six threat types, and twelve vulnerabilities” is an inventory; it says nothing about what could happen from where. Multiplying the three produces a scare number. What is actually countable is where the three intersect, and that intersection narrows under two conditions: reach and capability.
Still, the number 19 is not yet a path count. Every triple assumes the threat can touch the asset directly; the zone is given as a label, not counted as a gate to pass through. Reaching an asset in the inner zone actually requires passing over other assets first, and each of those crossings is a separate step. What this lesson leaves behind is an intersection count; the path count will be done in this topic’s last lesson.
Same Number of Vulnerabilities, a Fourfold Difference
The first table shows why vulnerability count alone is not an indicator. audit-log and
field-app both carry two vulnerabilities each. The first produces 1 triple, the second 4.
The ratio per vulnerability is 0.5 against 2.0 — a fourfold difference at the same
vulnerability count.
The source of the difference shows up in the second table. field-app is in the outer zone and
its vulnerabilities are of type input and version; two of the three threats that reach the outer
zone can use those types. audit-log is in the inner zone and its vulnerabilities are of type
visibility and input; of the three threats that reach the inner zone, only one uses visibility,
and none use the input type.
The last row is this lesson’s sharpest item: the unvalidated-field vulnerability carries zero
triples. The vulnerability is real, it sits in the list, it gets flagged in an audit — but no
threat can reach it and use it. That does not mean the vulnerability should be left unpatched; if
the zone boundary changes, the triple count stops being zero. What it means is this: vulnerability
count is not an exposure measure. One of twelve vulnerabilities is exposed to nothing, and
another is exposed to three at once.
A Threat’s Two Separate Widths
The third table measures threats on two axes: how many zones they reach and how many
vulnerability types they can use. targeted-external reaches two zones but uses six types;
malicious-insider reaches three zones and uses four types. Both produce five triples — the
same number, by two different routes.
The difference is in the last column: targeted-external touches four separate assets,
malicious-insider touches five. Reach width and capability width give the same triple total, but
their spread differs. Capability width makes multiple vulnerabilities on the same asset usable;
reach width touches more assets. A defense decision cannot be written without knowing which of
the two it is narrowing.
supply-chain sits at the opposite end: it reaches two zones, uses two types, produces a single
triple, and touches a single asset. This row records that the threat list’s length is not an
indicator either — one of six threats carries one nineteenth of the inventory.
The last two lines rank the return on closing. When the twelve vulnerabilities are ordered by
triple count, the sequence 3 3 2 2 2 2 1 1 1 1 1 0 comes out. Closing the five vulnerabilities
that carry the most closes 12 of the 19 triples; the remaining seven triples need seven more
separate vulnerabilities closed, and the twelfth adds nothing. Closing order is set not by
vulnerability count but by triple count carried.
Which of the Three Lists Is Yours to Move
The fifth table tries a third lever. Closing a vulnerability shortens one list; the threat list cannot be shortened, because who is capable of what is not the organization’s decision to make. What remains is reach: narrowing an asset’s zone by one step changes the set of threats that can reach its vulnerabilities without closing any of them.
The results show four separate behaviors. When meter-firmware is pulled from field to inner,
3 triples close — equal to the highest return of closing a single vulnerability, and both of
its vulnerabilities stay exactly where they are. work-order-queue closes 2, field-app closes
- The four assets already in the inner zone have no further step left to narrow, and the triples they close are zero.
The second row is the table’s most instructive item: when billing-record is pulled from outer to
field, the triples closed are zero. The item has moved to a narrower zone, but its
version-type vulnerability can be used both by a threat that reaches the outer zone and by another
that reaches the field zone; a new triple opens exactly where one closed. The return on
narrowing does not depend on how hidden the item now is, but on which set of threats is left
outside.
The three levers are therefore not the same thing: closing a vulnerability deletes a row from the list, narrowing a zone leaves the same row in place and changes the set of threats that reach it, and the threat list is not yours to move at all. This is the last thing this lesson can count; the second lever’s real measure cannot be given without knowing how many steps sit between zones.
Summary
- Asset is the thing protected, threat is the agent capable of causing harm, and vulnerability is the weakness in the asset; the three are separate lists and are counted separately.
- The product of eight assets, six threats, and twelve vulnerabilities is 576; the valid-triple count is 19, or 3.3% of the product. The narrowing comes from two conditions: does the threat reach that zone, can it use that vulnerability type.
- Nineteen triples are still not a path count; every triple assumes the threat touches the asset directly and does not count zone-to-zone crossings as steps.
- Two assets carrying two vulnerabilities each produce 1 and 4 triples; one vulnerability carries zero triples, another carries three. Vulnerability count alone is not an exposure measure.
- Of two threats that produce five triples each, one touches four assets and the other five; reach width and capability width give the same total with a different spread.
- Narrowing an asset’s zone by one step, without closing any vulnerability, closes 3, 2, 1, or 0 triples; when other threats already reach the narrower zone, the return stays zero.
Next Step
This lesson counted nineteen triples and wrote every one of them at equal weight. But the single
triple carried through physical-access-point does not occur at the same frequency as the three
carried through unencrypted-transfer, nor does it cost the same when it does occur. In this
lesson, closing order was set purely by triple count carried; yet an item that carries few triples
can still be far heavier. The next lesson combines these two dimensions — probability and
impact — over a set of items, and shows how two different scoring schemes rank the same list
differently.
To keep your progress and take notes, Log in
My notes
Log in to take notes.