Lesson 04 / 17
Asset and Inventory Management
When six off-inventory assets are added to the list, the 47-item list grows to 69 items and the 5,768.61-unit total rises to 8,045.45 fictional units: off-inventory items carry 28.3 percent of the true risk, 41 recorded items change rank, the largest shift is 22 positions, six shadow items enter the top twenty, 8 of the 30 shadow items can never enter any ranking because their value was never written, and the shadow item that rises to the highest rank comes from an asset no discovery source has ever seen.
Published Updated
In this lesson
- Counts the risk items produced by off-inventory assets and calculates their share of the true total
- Ranks two lists side by side: how many items in the incomplete list end up in the wrong position, and by how many places
- Measures the unit cost of discovery sources and the risk carried by the asset no source ever sees
Contents
Across three lessons the same list was ranked, measured and budgeted: 47 items, ten assets, seven threats. Every calculation assumed this list was complete. Yet the list was derived from an inventory, and an inventory is a set of records — an asset that was never recorded cannot enter the list.
An unrecorded asset’s risk is not zero; it is unwritten. It is invisible in the ranking, takes no place in the budget, is not even counted as accepted — because acceptance requires at least a record. This lesson counts that gap: how many assets sit outside the inventory, how many items they produce, how many ranking positions would change if they entered the list, and what the unit cost of finding them is.
Left Outside the Inventory
An off-inventory asset is not usually a rule violation; more often it is the residue of ordinary operations: a server spun up for a test and never torn down, a cloud account a team opened on its own budget, a transfer service the field team set up itself, a branch endpoint that was decommissioned but never unplugged, a second supplier connection, a backup data copy. Each one exists because it served some purpose, and none of them was set up with updating the inventory in mind.
The way to find them is discovery sources, and each source looks from a different angle. Network scanning sees what is addressable, identity records see what has an account, billing records see what generates a cost, the configuration repository sees what is defined, the endpoint agent sees what has software running on it. An asset is as findable as the number of sources it appears in; if it appears in none, it cannot be found.
- RM17. The seven threats and their coefficients are the same as in the previous lessons; the unit is the fictional unit.
- RM18. The five discovery sources, their costs and source masks are a model; no real scan is performed.
- RM19. The six off-inventory assets are a model. The
?mark shows that the asset has been found but its value has not been written. - RM20. An item’s risk can only be calculated if the asset’s value has been written; an asset whose value is unwritten cannot enter any ranking even if it is discovered.
- RM21. Discovery is one-time and puts the asset it finds into the inventory; entering the inventory does not mean the value has been written.
- RM22. Off-inventory assets are written with higher exposure multipliers than recorded assets; the model’s rationale is that maintenance and hardening work is applied only to recorded assets.
// risk/inventory.mjs — assets in the inventory and off-inventory assets. THIS IS A MODEL; no real // scan is performed, no real account is opened, every value is in "fictional units". // RM17: threat row: name | annual baseline frequency | exposure ratio export const THREAT = ` credential-theft 0.30 0.35 malicious-code 0.50 0.25 misconfiguration 0.80 0.15 insider-misuse 0.10 0.40 service-disruption 0.40 0.20 physical-access 0.06 0.50 supply-chain 0.08 0.45` .trim().split("\n").map((r) => { const [name, s, m] = r.trim().split(/\s+/); return { name, baseFreq: +s, exposureRatio: +m }; }); // RM18: discovery source: name | annual cost. Every asset carries a source mask. export const SOURCE = ` network-scan 35 identity-record 15 billing-record 10 config-repository 25 endpoint-agent 45` .trim().split("\n").map((r) => { const [name, b] = r.trim().split(/\s+/); return { name, cost: +b }; }); // RM19: asset row: name | value ('?' value not written) | exposure | threat mask | // source mask | in inventory. The ten assets in the inventory are already recorded. const ROW = ` field-laptop 900 1.6 1111011 11001 E branch-endpoint 600 1.2 1111110 10011 E measurement-collector 750 1.4 0111110 10010 E processing-server 1800 0.6 1111101 11011 E perimeter-network 1200 1.8 0011100 10010 E identity-directory 2400 1.0 1011100 01010 E cloud-account 2000 1.5 1011100 01100 E subscriber-database 3000 0.4 1011100 00011 E field-application 1400 1.1 1111001 00110 E supplier-connection 1000 1.3 1001101 01100 E temporary-test-server 800 1.7 1111101 11010 H team-cloud-account ? 1.6 1011100 01100 H field-transfer-service 700 1.9 1111011 10001 H retired-branch-endpoint 500 1.5 1111110 10010 H third-supplier-endpoint ? 1.4 1001101 01100 H backup-data-copy 2200 0.8 1011100 00000 H`; export const ASSET = ROW.trim().split("\n").map((r) => { const [name, v, m, mask, km, inv] = r.trim().split(/\s+/); return { name, value: v === "?" ? null : +v, exposureMult: +m, mask, sourceMask: km, inInventory: inv === "E", visible: km.includes("1") }; }); export const RECORDED = ASSET.filter((v) => v.inInventory); export const SHADOW = ASSET.filter((v) => !v.inInventory); // RM20: an item's risk can only be calculated if the asset's value has been written; an asset // whose value is unwritten cannot enter any ranking even if it is discovered. export const ALL = ASSET.flatMap((v) => THREAT .filter((_, j) => v.mask[j] === "1") .map((t) => ({ v, t, risk: v.value === null ? null : +(t.baseFreq * v.exposureMult * v.value * t.exposureRatio).toFixed(2) }))); export const REC = ALL.filter((k) => k.v.inInventory); export const SHA = ALL.filter((k) => !k.v.inInventory); export const rank = (L) => L.filter((k) => k.risk !== null) .map((k, i) => ({ k, i })).sort((a, b) => b.k.risk - a.k.risk || a.i - b.i).map((x) => x.k); export const sum = (L) => +L.reduce((a, k) => a + (k.risk || 0), 0).toFixed(2);
Counting the Two Lists
// risk/missing.mjs — how the ranking would change if off-inventory items entered the list import { RECORDED, SHADOW, SOURCE, ALL, REC, SHA, rank, sum } from "./inventory.mjs"; const y = (g, ...s) => console.log(s.map((v, i) => (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join("").trimEnd()); const S1 = rank(REC), S2 = rank(ALL); const position = (L, k) => L.indexOf(k) + 1; console.log(`${RECORDED.length} assets / ${REC.length} items / ${sum(REC)} fictional units are in the inventory; ` + `${SHADOW.length} off-inventory assets / ${SHA.length} items / ${sum(SHA)} units ` + `(${((sum(SHA) / sum(ALL)) * 100).toFixed(1)} percent of the true total)`); const A = [-23, 7, 7, 10, 10, 10]; console.log("\n1. off-inventory assets"); y(A, "asset", "value", "items", "risk", "sources", "status"); for (const v of SHADOW) { const ks = SHA.filter((k) => k.v === v); y(A, v.name, v.value ?? "?", ks.length, v.value === null ? "-" : sum(ks).toFixed(2), v.sourceMask.split("").filter((c) => c === "1").length, v.value === null ? "valueless" : v.visible ? "visible" : "unseen"); } const scorable = SHA.filter((k) => k.risk !== null); console.log(`${scorable.length} of the ${SHA.length} off-inventory items are scorable ` + `(${sum(scorable).toFixed(2)} units), ${SHA.length - scorable.length} cannot enter any ranking ` + `because their value was never written`); const B = [-38, 9]; console.log("\n2. what changes when the full list is ranked"); const shift = REC.map((k) => Math.abs(position(S1, k) - position(S2, k))); y(B, "inventory list / full list items", `${S1.length} / ${S2.length}`); y(B, "total risk", `${sum(REC).toFixed(2)} / ${sum(ALL).toFixed(2)}`); y(B, "recorded items with a changed rank", shift.filter((x) => x > 0).length); y(B, "largest rank shift", Math.max(...shift)); y(B, "off-inventory items in top ten", S2.slice(0, 10).filter((k) => !k.v.inInventory).length); y(B, "highest off-inventory item's rank", Math.min(...scorable.map((k) => position(S2, k)))); y(B, "off-inventory items in top twenty", S2.slice(0, 20).filter((k) => !k.v.inInventory).length); const C = [-22, -21, 10, 9, 6]; console.log("\n3. the full list's top ten"); y(C, "asset", "threat", "risk", "source", "rank"); for (const k of S2.slice(0, 10)) y(C, k.v.name, k.t.name, k.risk.toFixed(2), k.v.inInventory ? "recorded" : "shadow", position(S1, k) || "-"); const D = [-21, 7, 8, 13, 8, 9]; console.log("\n4. per discovery source (off-inventory assets only)"); y(D, "source", "cost", "assets", "risk opened", "items", "ratio"); const opened = (f) => { const vs = SHADOW.filter(f); const ks = SHA.filter((k) => vs.includes(k.v) && k.risk !== null); return { vs, ks, r: sum(ks) }; }; for (const [i, s] of SOURCE.entries()) { const a = opened((v) => v.sourceMask[i] === "1"); y(D, s.name, s.cost, a.vs.length, a.r.toFixed(2), a.ks.length, (a.r / s.cost).toFixed(3)); } const all5 = opened((v) => v.visible); const cost = SOURCE.reduce((a, s) => a + s.cost, 0); y(D, "all five combined", cost, all5.vs.length, all5.r.toFixed(2), all5.ks.length, (all5.r / cost).toFixed(3)); const blind = SHADOW.filter((v) => !v.visible); console.log(`assets no source ever sees: ${blind.length}, items ` + `${SHA.filter((k) => blind.includes(k.v)).length}, risk ` + `${sum(SHA.filter((k) => blind.includes(k.v))).toFixed(2)} units`);
10 assets / 47 items / 5768.61 fictional units are in the inventory; 6 off-inventory assets / 30 items / 2276.84 units (28.3 percent of the true total) 1. off-inventory assets asset value items risk sources status temporary-test-server 800 6 688.16 3 visible team-cloud-account ? 4 - 2 valueless field-transfer-service 700 6 606.48 2 visible retired-branch-endpoint 500 6 375.00 2 visible third-supplier-endpoint ? 4 - 2 valueless backup-data-copy 2200 4 607.20 0 unseen 22 of the 30 off-inventory items are scorable (2276.84 units), 8 cannot enter any ranking because their value was never written 2. what changes when the full list is ranked inventory list / full list items 47 / 69 total risk 5768.61 / 8045.45 recorded items with a changed rank 41 largest rank shift 22 off-inventory items in top ten 1 highest off-inventory item's rank 7 off-inventory items in top twenty 6 3. the full list's top ten asset threat risk source rank cloud-account misconfiguration 360.00 recorded 1 cloud-account credential-theft 315.00 recorded 2 identity-directory misconfiguration 288.00 recorded 3 perimeter-network misconfiguration 259.20 recorded 4 identity-directory credential-theft 252.00 recorded 5 cloud-account service-disruption 240.00 recorded 6 backup-data-copy misconfiguration 211.20 shadow - field-application malicious-code 192.50 recorded 7 identity-directory service-disruption 192.00 recorded 8 field-application misconfiguration 184.80 recorded 9 4. per discovery source (off-inventory assets only) source cost assets risk opened items ratio network-scan 35 3 1669.64 18 47.704 identity-record 15 3 688.16 6 45.877 billing-record 10 2 0.00 0 0.000 config-repository 25 2 1063.16 12 42.526 endpoint-agent 45 1 606.48 6 13.477 all five combined 130 5 1669.64 18 12.843 assets no source ever sees: 1, items 4, risk 607.20 units
The numbers are in the measurement class; its inputs are RM17–RM21.
The Missing List’s Share
Six off-inventory assets produce 30 risk items, and the risk they carry is 2,276.84 fictional units. The true total is not 5,768.61 but 8,045.45 units; the off-inventory share is 28.3 percent. Every ranking, every budget calculation and every acceptance decision made across three lessons was given under the assumption that this share did not exist.
The number matters not only because it is large but because of its distribution. The backup data copy alone carries 607.20 units and outweighs six of the ten assets in the inventory. The temporary test server, at 688.16 units, outweighs eight of those ten. What they have in common is a low value — but a low value multiplied by a high exposure does not produce a small number.
The exposure multiplier column explains this height, and it is a deliberate choice in the model (RM22): off-inventory assets take values between 0.8 and 1.9, recorded assets between 0.4 and 1.8. The rationale is simple and self-reinforcing — hardening, patching and monitoring work is applied to the list in the inventory, and an asset that is not on the list receives none of it. Being unrecorded means not only invisibility but also neglect; the two effects multiply in the same direction.
Where the Ranking Shifts
The second table answers the real question. The incomplete list ranks 47 items, the full list ranks 69; 41 recorded items change rank and the largest shift is 22 positions. Six shadow items enter the top twenty, and one of them rises to seventh place.
The third table names this item: misconfiguration on the backup data copy, seventh on the list at 211.20 units, and it does not exist in the inventory list at all. When it enters, the malicious-code item on the field application drops from seventh to eighth, and every item below it moves down one place. If the budget handles the first ten items, a program working from the incomplete list never opens this item at all — and it does not even know this is a gap, because the item is not on the list and so does not appear in the “not handled” column either.
The size of the shift is tied to how flat the list’s distribution is. When the risk distribution is flat, 22 new items scatter throughout it and change nearly every item’s position. If there had been a sharp peak, the new items would pile up at the bottom and the top ten would not change. Here the top ten does change, which means the gap reaches the region where decisions are made.
Being Found and Being Counted Are Not the Same
The first table’s last column writes a second distinction. Two of the six assets — the cloud account the team opened and the third supplier endpoint — appear in two separate sources, but their values were never written. These two assets produce 8 risk items, and none of those items can be scored: no value means no impact, and no impact means no risk can be calculated. 22 of the 30 shadow items are scorable, 8 are not.
This is the half of inventory work that gets missed most often. Discovery records that an asset exists; the inventory records what it is — who owns it, what work it carries, what happens if it goes down. The first can be automated, the second requires a person to make a judgment. An asset that has been found but whose value was never written ends up in the same place as an asset that was never found at all: neither enters any ranking. The only difference between them is that the first one’s name is on a list somewhere.
Discovery’s Unit Cost
The fourth table tests discovery against the course’s unit of measure. Network scanning opens three assets and 1,669.64 units of risk for 35 units; its ratio of 47.704 is the highest on the list. Identity records open 688.16 units for 15 units (45.877). Billing records find two assets for 10 units and open 0.00 units — the two assets they find have no value written, so the scorable risk they open is zero.
The last row tests the sum. All five sources together find five assets for 130 units, but the risk they open is still 1,669.64 units — the number network scanning opens on its own. The remaining four sources spend 95 more units and add nothing to the scorable risk; the two assets they add stay valueless. The return per unit falls from 47.704 to 12.843. Adding a discovery source always finds more items, but if the item it finds cannot be scored, it does not enter the ranking and takes no place in the budget.
The final line writes the lesson’s harshest number: there is an asset no source ever sees, carrying 4 items and 607.20 units. Even if all five sources were bought, it would not be found. And the item in seventh place on the full list comes from exactly this asset. Discovery coverage is not a lid, it is a threshold: everything that falls below it goes unmeasured, and everything unmeasured is counted as accepted by default.
Summary
- Six off-inventory assets carry 30 items and 2,276.84 fictional units; the true total is 8,045.45 units and the off-inventory share is 28.3 percent.
- When the full list is ranked, 41 recorded items change position, the largest shift is 22 places; six shadow items enter the top twenty and one rises to seventh.
- A program working from the incomplete list cannot even see that it has not handled this item: because the item is not on the list, it is not in the “not handled” column either.
- Being found and being counted are not the same thing. Two shadow assets appear in two sources but their value was never written; 22 of the 30 shadow items are scorable, 8 can never enter any ranking.
- Network scanning gives a ratio of 47.704 for 35 units; all five sources together open the same risk for 130 units and the ratio falls to 12.843. Adding sources finds more assets, not more scorable risk.
- An asset no source ever sees carries 4 items and 607.20 units; the item in seventh place on the full list comes from this asset.
Next Step
Every discovery source assumes one thing in common: the asset being sought leaves a trace in the organization’s own network, its own identity system, or its own billing. A server sitting in a supplier’s own systems leaves none of these traces. Network scanning never reaches it, the endpoint agent never runs on it, the configuration repository does not recognize it. All that stands in the organization’s place for those assets is a contract clause and an assessment questionnaire. The next lesson measures this across a set of three suppliers: how many controls the contract states, how many the questionnaire actually proves, and how many items remain that look delegated but cannot be verified.
To keep your progress and take notes, Log in
My notes
Log in to take notes.