---
title: 'Security Frameworks'
source: 'https://academia.sh/en/courses/governance-risk-and-compliance/security-frameworks'
course: 'Governance, Risk and Compliance'
language: en
updated: '2026-09-26T03:02:52+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Breaks a function-based framework into function, category, and control layers and defines the maturity level'
    - 'Calculates function maturity on a fictional control set and counts what the average hides: level-0 controls and a category with no working control'
    - 'Spends the same budget by gap priority and by yield priority and shows where the two orders diverge'
---

# Security Frameworks

The structure of a function-based framework and a maturity calculation on a fictional control set: an overall maturity of 1.35 across 5 functions, 14 categories, and 31 control items; 5 controls sitting at level 0 and one category with no working control at all; 15 of 31 controls producing evidence for 25 evidence items; a 45 person-day budget where gap priority and yield priority rank all five functions differently; and a yield-first order that raises the average by 0.07 while leaving the weakest function 0.33 behind.

The previous topic left behind a list: items were ranked, options were pitted against each other
by cost, and the case for spending a scarce budget on one item over another was made with
numbers. That list has a flaw invisible from inside the organization — it is written entirely in
the organization's own language. The organization chose the metrics, the organization assigned
the weights, the organization produced the ranking. When an outsider — a buyer, an insurer, or
another organization purchasing a service — says "show me your security posture," this list is
not an answer: there is no common axis on which the other party can lay it next to their own
list.

A **security framework** is that axis. It is a common classification that breaks down the
security work an organization does into countable headings, independent of who does it or with
what tool. This lesson treats the framework by its **structure** and ties it to a single
question: when the same control set is placed into this structure, what number comes out, what
does that number show, and what does it hide. No framework, standard, or organization name
appears anywhere in this lesson; what is measured is not a name but the structure itself.

## Function, Category, Control

A **function-based framework** has three layers. At the top sits the **function**, which splits
security work into five sequential headings: **identify** (what do I have, what risk am I
carrying), **protect** (what narrows access), **detect** (how do I see what is happening),
**respond** (what do I do once I see it), and **recover** (how do I get back on my feet
afterward). Each function is split into **categories**, and each category into individual
**controls**. The point of this breakdown is that the work two different organizations do with
two different tools falls under the same five headings, so the two organizations become
comparable in the same table.

The second layer is the **maturity level**, and it measures not whether a control exists but
**how institutionalized it is**. This distinction is the lesson's central concept: a control
existing and a control operating in an auditable way are not the same thing. The model below uses
five levels — nonexistent, performed without documentation, documented, measured, and
continuously improved.

- **FW1.** The example organization is the fictional **regional measurement network** carried
  through nine courses: endpoints, network, identity, leased accounts, and three suppliers. The
  control set and levels are **fictional**.
- **FW2.** The framework consists of five functions, fourteen categories, and thirty-one control
  items.
- **FW3.** The maturity level ranges from 0 to 4: 0 nonexistent, 1 performed without
  documentation, 2 documented, 3 measured, 4 continuously improved.
- **FW4.** Category maturity is the **unweighted average** of its controls' maturity, and
  function maturity is the average of its categories; overall maturity is the average of the
  five functions.
- **FW5.** Raising a control by one level costs `2 + 3 × current level` person-days: climbing out
  of a low level is cheap, carrying a high level is expensive.
- **FW6.** Evidence production depends on the level: from level 2 onward, one document is
  produced; from level 3 onward, two more measurement log entries are produced. Levels 0 and 1
  produce no evidence item at all.

```js
// framework/maturity.mjs — calculating the maturity level in a function-based framework.
// MODEL: regional measurement network (fictional); the control set and levels are fictional.
export const row = (s) => s.trim().split("\n").map((l) => l.trim().split("|"));
export const gen = (B) => (...h) => h.map((v, j) =>
  (j ? String(v).padStart(B[j]) : String(v).padEnd(B[0]))).join("");

// function | category | maturity levels of the controls in the category (0-4)
export const CATEGORY = row(`
  identify|asset inventory|3 2 1
  identify|business context|2 2
  identify|risk assessment|3 2
  protect|access control|3 3 2
  protect|data security|3 2 2
  protect|maintenance and patching|2 1
  detect|continuous monitoring|2 1 1
  detect|event detection|1 0
  respond|response plan|2 1
  respond|notification|1 0
  respond|mitigation|1 1
  recover|recovery plan|1 0
  recover|improvement|0 0
  recover|communications|1`)
  .map(([fn, name, d]) => ({ fn, name, level: d.split(" ").map(Number) }));

export const FUNCTION = ["identify", "protect", "detect", "respond", "recover"];
export const categoriesOf = (i) => CATEGORY.filter((k) => k.fn === i);
export const catAvg = (k) => k.level.reduce((a, b) => a + b, 0) / k.level.length;
export const fnAvg = (i) => categoriesOf(i).reduce((a, k) => a + catAvg(k), 0) / categoriesOf(i).length;
// Raising a control by one level costs 2 + 3 x current level person-days (fictional).
export const days = (d) => 2 + 3 * d;
// Evidence: one more document from level 2 onward, two more measurement log entries from level 3 onward.
export const evidence = (d) => (d >= 2 ? 1 : 0) + (d >= 3 ? 2 : 0);

if (import.meta.url === `file://${process.argv[1]}`) {
  const control = CATEGORY.flatMap((k) => k.level);
  console.log(`model: ${FUNCTION.length} functions, ${CATEGORY.length} categories, ` +
    `${control.length} control items`);

  const y = gen([10, 9, 8, 10, 30, 9]);
  console.log("\n" + y("function", "category", "control", "maturity", "weakest", "level 0"));
  for (const i of FUNCTION) {
    const c = categoriesOf(i), d = c.flatMap((k) => k.level);
    const weakest = c.reduce((a, k) => (catAvg(k) < catAvg(a) ? k : a));
    console.log(y(i, c.length, d.length, fnAvg(i).toFixed(2),
      `${weakest.name} ${catAvg(weakest).toFixed(1)}`, d.filter((x) => x === 0).length));
  }
  const all = CATEGORY.flatMap((k) => k.level);
  const overallAvg = FUNCTION.reduce((a, i) => a + fnAvg(i), 0) / FUNCTION.length;
  const noWorking = CATEGORY.filter((k) => k.level.every((d) => d === 0));
  console.log(`\noverall maturity ${overallAvg.toFixed(2)}; level 0 controls ` +
    `${all.filter((d) => d === 0).length}/${all.length}; ` +
    `categories with no working control ${noWorking.length} ` +
    `(${noWorking.map((k) => k.name).join(", ")})`);
  console.log(`controls producing evidence ${all.filter((d) => d >= 2).length}/${all.length}; ` +
    `evidence items produced ${all.reduce((a, d) => a + evidence(d), 0)}`);

  // Gain from raising one level toward function maturity: 1 / (categories x controls).
  const z = gen([10, 9, 14, 33, 11]);
  console.log("\n" + z("function", "maturity", "maturity rank", "cheapest +1", "yield rank"));
  const yieldList = FUNCTION.map((i) => {
    const c = categoriesOf(i);
    const best = c.flatMap((k) => k.level.map((d) =>
      ({ name: k.name, gain: 1 / (c.length * k.level.length) / days(d), d })))
      .reduce((a, b) => (b.gain > a.gain ? b : a));
    return { i, avg: fnAvg(i), best };
  });
  const sM = [...yieldList].sort((a, b) => a.avg - b.avg).map((v) => v.i);
  const sY = [...yieldList].sort((a, b) => b.best.gain - a.best.gain).map((v) => v.i);
  for (const v of yieldList) {
    console.log(z(v.i, v.avg.toFixed(2), sM.indexOf(v.i) + 1,
      `${v.best.name} ${days(v.best.d)} days`, sY.indexOf(v.i) + 1));
  }
  const same = sM.filter((i, k) => i === sY[k]).length;
  console.log(`\nmaturity rank: ${sM.join(" > ")}`);
  console.log(`yield rank   : ${sY.join(" > ")}`);
  console.log(`functions in the same rank: ${same}/${FUNCTION.length}`);
}
```

```
model: 5 functions, 14 categories, 31 control items

function   category control  maturity                       weakest  level 0
identify          3       7      2.17           asset inventory 2.0        0
protect           3       8      2.17  maintenance and patching 1.5        0
detect            2       5      0.92           event detection 0.5        1
respond           3       6      1.00              notification 0.5        1
recover           3       5      0.50               improvement 0.0        3

overall maturity 1.35; level 0 controls 5/31; categories with no working control 1 (improvement)
controls producing evidence 15/31; evidence items produced 25

function   maturity maturity rank                      cheapest +1 yield rank
identify       2.17             4           asset inventory 5 days          5
protect        2.17             5  maintenance and patching 5 days          4
detect         0.92             2           event detection 2 days          1
respond        1.00             3              notification 2 days          2
recover        0.50             1             recovery plan 2 days          3

maturity rank: recover > detect > respond > identify > protect
yield rank   : detect > respond > recover > protect > identify
functions in the same rank: 0/5
```

The first table does the framework's real job: thirty-one scattered control items collapse into
five numbers, and the five numbers are **comparable**. The organization's distribution is skewed
— protect and identify sit at 2.17, recover at 0.50. This is exactly what nine courses have left
behind: a great deal of work has gone into narrowing access, changing what data shows when it is
read, and drawing the boundary, and very little work into getting back on your feet after an
event. This skew is only visible once the five headings are written out separately.

Overall maturity is 1.35, and this number is misleading when it stands alone. For two reasons:
first, **the average swallows the weakest link** — the recover function sits at about a third of
the average, while the overall number lands somewhere in the middle. Second, the average does not
distinguish between a control that is **entirely absent** and one that is **present at a low
level**. The last column makes that distinction: five of the thirty-one controls are at level 0,
and one category — improvement — has no working control at all. That single line produces a
different decision than the sentence "our recovery maturity is 0.50," because a category being
completely empty is not a slowdown, it is a **gap**.

The third number opens up the evidence side and feeds the next two lessons. Fifteen of the
thirty-one controls produce evidence, and twenty-five evidence items are produced in total. The
sixteen controls at level 0 and 1, on the other hand, **can show nothing** in an audit: the work
may be happening, but there is no item that demonstrates it happened. The maturity level being so
tightly bound to evidence is no accident — the level definitions themselves are written in terms
of documents and measurement log entries.

The second table carries the course's measurement axis into the framework. The left side ranks
the functions by maturity: recover, the weakest, comes first. The right side ranks the same
functions by **maturity gain per unit of effort**. The two rankings do not agree on a single
function. The reason is structural: a contribution to a function's maturity is divided by that
function's number of categories and the number of controls in the category. Detect has two
categories, so one upgrade there moves the needle more than one upgrade in recover — which is why
recover, the lowest function, ranks third in the yield order.

## The Same Budget, Two Orders

The real difference between the two rankings shows up once a fixed budget is spent. The file
below raises the same control set under two separate rules: **gap first** takes, at every step,
the cheapest item belonging to the lowest-maturity function; **yield first** takes the item that
contributes the most to overall maturity per day. The budget is forty-five person-days and is
fictional.

```js
// framework/budget.mjs — the same framework raised by two different orders (fictional budget).
import { CATEGORY, FUNCTION, gen, days, evidence } from "./maturity.mjs";

const BUDGET = 45;                                   // person-days (fictional unit)
const clone = () => CATEGORY.map((k) => ({ ...k, level: [...k.level] }));
const avg = (K, i) => { const c = K.filter((k) => k.fn === i);
  return c.reduce((a, k) => a + k.level.reduce((x, d) => x + d, 0) / k.level.length, 0) / c.length; };
const overall = (K) => FUNCTION.reduce((a, i) => a + avg(K, i), 0) / FUNCTION.length;
const evidenceCount = (K) => K.flatMap((k) => k.level).reduce((a, d) => a + evidence(d), 0);
const zeroCount = (K) => K.flatMap((k) => k.level).filter((d) => d === 0).length;

// Upgrade candidate: category, control, cost, and gain toward overall maturity.
const candidates = (K) => K.flatMap((k, ki) => k.level.map((d, di) => ({ ki, di, d, k,
  cost: days(d), gain: 1 / (FUNCTION.length * K.filter((x) => x.fn === k.fn).length
    * k.level.length) })).filter((a) => a.d < 4));

function run(pick) {
  const K = clone(); let left = BUDGET, steps = 0;
  for (;;) {
    const eligible = candidates(K).filter((a) => a.cost <= left);
    if (!eligible.length) break;
    const a = pick(eligible, K);
    K[a.ki].level[a.di]++; left -= a.cost; steps++;
  }
  return { K, left, steps };
}

const ROUTE = [
  ["gap first", (u, K) => { const lowest = [...FUNCTION].sort((a, b) => avg(K, a) - avg(K, b))[0];
    const within = u.filter((a) => a.k.fn === lowest);
    return (within.length ? within : u).sort((a, b) => a.cost - b.cost || a.ki - b.ki)[0]; }],
  ["yield first", (u) => [...u].sort((a, b) =>
    b.gain / b.cost - a.gain / a.cost || a.ki - b.ki)[0]],
];

const y = gen([13, 9, 7, 8, 9, 9, 17]);
console.log(`budget ${BUDGET} person-days; starting overall maturity ` +
  `${overall(clone()).toFixed(2)}, level 0 controls ${zeroCount(clone())}, ` +
  `evidence items ${evidenceCount(clone())}`);
console.log("\n" + y("run", "raised", "left", "overall", "level 0", "evidence", "weakest function"));
const result = {};
for (const [name, pick] of ROUTE) {
  const { K, left, steps } = run(pick);
  const weakest = [...FUNCTION].sort((a, b) => avg(K, a) - avg(K, b))[0];
  result[name] = { K, weakest };
  console.log(y(name, steps, left, overall(K).toFixed(2), zeroCount(K), evidenceCount(K),
    `${weakest} ${avg(K, weakest).toFixed(2)}`));
}

const z = gen([11, 12, 14, 14, 12]);
console.log("\n" + z("function", "baseline", "gap first", "yield first", "diff"));
for (const i of FUNCTION) {
  const a = avg(result["gap first"].K, i), b = avg(result["yield first"].K, i);
  console.log(z(i, avg(clone(), i).toFixed(2), a.toFixed(2), b.toFixed(2), (b - a).toFixed(2)));
}
const v = result["yield first"].K;
const untouched = FUNCTION.filter((i) => avg(v, i) === avg(clone(), i));
console.log(`\nyield first never raised ${untouched.length} function(s): ` +
  `${untouched.join(", ")}`);
console.log(`overall maturity difference ${(overall(v) - overall(result["gap first"].K)).toFixed(2)}; ` +
  `weakest function difference ${(avg(v, result["yield first"].weakest) -
    avg(result["gap first"].K, result["gap first"].weakest)).toFixed(2)}`);
```

```
budget 45 person-days; starting overall maturity 1.35, level 0 controls 5, evidence items 25

run             raised   left overall  level 0 evidence weakest function
gap first           12      0    1.77        0       32      detect 1.50
yield first         11      2    1.83        0       32     respond 1.17

function       baseline     gap first   yield first        diff
identify           2.17          2.17          2.17        0.00
protect            2.17          2.17          2.33        0.17
detect             0.92          1.50          1.83        0.33
respond            1.00          1.50          1.17       -0.33
recover            0.50          1.50          1.67        0.17

yield first never raised 1 function(s): identify
overall maturity difference 0.07; weakest function difference -0.33
```

Both rows spend the same forty-five days, and both bring the five level-0 controls up out of
zero; evidence items rise from twenty-five to thirty-two in both. Where they diverge is the last
two columns. The yield order carries overall maturity to 1.83, while the gap order stays at 1.77
— a difference of 0.07, and read on its own, the yield order looks like the winner. But look at
the weakest function and the picture reverses: the gap order brings the weakest function up to
1.50, the yield order leaves it at 1.17. The difference is 0.33 — roughly five times what was
gained on average piles up as a loss in the weakest link.

The table below shows why. The yield order leaves the respond function 0.33 behind the gap order
and never touches the identify function at all. The rule does this knowingly: an upgrade in
identify costs five days and moves little, one in detect costs two days and moves a lot. **The
yield measure does not ask how critical an item is, only how much it moves the metric.** For this
lesson, the conclusion is structural: once a maturity number becomes a target, the order that
raises the number most cheaply and the order that closes the gap diverge, and the same budget
produces two different organizations.

A third observation follows from this, and it concerns the framework itself. The unweighted
average (FW4) is a choice, not an inevitability: a function with fewer categories moves more per
item. The rule that constructs the metric also decides which item the metric favors. If a
framework report shows only five numbers, how those five numbers were assembled must also be
asked.

## Summary

- A function-based framework has three layers — function, category, control — and reduces
  thirty-one scattered items to five comparable numbers: identify and protect at 2.17, respond at
  1.00, detect at 0.92, recover at 0.50.
- Overall maturity is 1.35 and hides two things: it swallows the weakest function, and it does
  not distinguish "absent" from "low." Five of the thirty-one controls are at level 0, and one
  category has no working control at all.
- Evidence production depends on the level: only fifteen of the thirty-one controls produce
  evidence, yielding twenty-five items in total; the sixteen controls at level 0 and 1 can show
  nothing in an audit.
- Ranking the functions by maturity and ranking them by gain per unit of effort do not agree on a
  single one of the five functions; recover, the lowest, ranks third in the yield order.
- On a forty-five person-day budget, the yield order raises overall maturity by an extra 0.07 but
  leaves the weakest function 0.33 behind and never touches one function at all: raising the
  number and closing the gap are not the same job.

## Next Step

The framework has established a common axis and reduced scattered controls to comparable numbers.
All of these numbers share one property: **the organization itself calculated them.** The
organization assigned the levels, the organization took the average, the organization decided
which item to raise. For an outsider, this is only one step ahead of the list from the previous
topic — the language became shared, but the party producing the number did not change.

The next lesson takes up the structure that takes that step: a management system model with a
declared scope, a written record of which control is applied and why or why not, an internal
audit, and **certification by an outside party**. What gets measured is not the certificate
itself but the scope statement: narrowing the scope makes the certificate cheaper, and, by the
same move, leaves a certain number of items outside it.
