---
title: 'Security Champions'
source: 'https://academia.sh/en/courses/secure-lifecycle/security-champions'
course: 'The Secure Development Lifecycle'
language: en
updated: '2026-09-26T03:02:40+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Measures the distribution of security competence across teams as a count of covered and uncovered reviews'
    - "Compares a champion's capacity against team load: calculates how many teams it covers and how many reviews are left to the center"
    - 'Shows how depth falls as coverage grows, and counts the items an escalation path gains'
---

# Security Champions

The distribution of security competence is measured over five teams' 35 weekly reviews: the central team alone covers 16 reviews and 19 go through without a security eye, and coverage saturates with four champions. A single champion covers an average of 0.86 teams, and even with a champion on every team, 6 reviews a week are left to the center; when a fifth champion is added, catching items that require expertise drops from 2 to 1, because the center no longer sees those reviews.

The previous three lessons, in turn, spread checks across stages, computed the cost per stage, and
turned requirements into a testable form. All three called on a person but never gave that person
a name: the one who notices an unobservable predicate in a design session, the one who separates
the six real items out of static analysis's ninety warnings, the one who runs the abuse case. This
person was assumed to be ready and waiting on every team.

This lesson measures that assumption. A **security champion** is a developer who works inside
their own team and sets aside part of their time for security review; not an alternative to a
central security team, but an extension of its capacity. There are three things to measure: how
many reviews have a security eye, how many are waiting, and how many teams a champion's capacity
covers.

- **DA17.** The teams, developer counts, and weekly review counts are fictional; the total review
  load is consistent with the previous lessons' change flow.
- **DA18.** A champion only looks at **their own team's** reviews; context knowledge is a resource
  that cannot be transferred. A central specialist can look at any team, and their capacity sits
  in a shared pool.
- **DA19.** Item difficulty has three levels: 1 is seen with a checklist, 2 with a trained eye, 3
  with expertise. A champion catches at most level 2; level 3 is the central specialist's job.
- **DA20.** An item is only caught in a review that has a security eye. An item that passes
  through an uncovered review falls, in the previous lessons' language, into the late flow.
- **DA21.** The escalation rate is the share of cases where a champion still forwards an item they
  cannot see to the center, and it is a model value. When it is zero, the champion model's depth
  ceiling becomes visible.

## Covered and Uncovered Reviews

The model ranks teams by review load and separates two sources of capacity: the time champions
spend within their own teams, and the central team's shared pool.

```js
// team.mjs — five teams, review load, and the distribution of security competence (model)
// row: team | developer | weekly review
export const TEAM = `
  subscriber-portal 6  10
  meter-collection  5   8
  billing           4   6
  field-app         4   5
  shared-library    3   6`
  .trim().split('\n').map((r) => {
    const [name, g, i] = r.trim().split(/\s+/);
    return { name, developer: +g, review: +i };
  });
export const CHAMPION_SHARE = 0.2;   // the share a champion sets aside from their own time
export const CHAMPION_REVIEWS = 6;   // the number of reviews they can handle per week with that share
export const CENTRAL_PEOPLE = 2, CENTRAL_REVIEWS = 8; // central security team
// Item difficulty: 1 is seen with a checklist, 2 with a trained eye, 3 with expertise.
// The distribution is modeled on the previous lessons' 23-item set.
export const DIFFICULTY = [[1, 11], [2, 7], [3, 5]];
export const CHAMPION_DEPTH = 2; // the highest difficulty a champion can catch
export const TOTAL_REVIEWS = TEAM.reduce((s, e) => s + e.review, 0);
export const TOTAL_DEVELOPERS = TEAM.reduce((s, e) => s + e.developer, 0);
```

```js
// run1.mjs — as the champion count rises: reviews with a security eye and reviews left waiting
import { TEAM, CHAMPION_SHARE, CHAMPION_REVIEWS, CENTRAL_PEOPLE, CENTRAL_REVIEWS,
  TOTAL_REVIEWS, TOTAL_DEVELOPERS } from './team.mjs';

const central = CENTRAL_PEOPLE * CENTRAL_REVIEWS;
// A champion only looks at their own team's reviews; the central pool can look at any team.
const sorted = [...TEAM].sort((a, b) => b.review - a.review);
console.log(`${TEAM.length} teams, ${TOTAL_DEVELOPERS} developers, ` +
  `${TOTAL_REVIEWS} reviews/week; central ${central} reviews/week`);
console.log('\nchampion  by champions      central used       central idle  covered   waiting   person-equiv');
for (let s = 0; s <= TEAM.length; s++) {
  const coverage = sorted.slice(0, s).reduce((a, e) => a + Math.min(e.review, CHAMPION_REVIEWS), 0);
  const remaining = TOTAL_REVIEWS - coverage, used = Math.min(remaining, central);
  const personEquiv = (s * CHAMPION_SHARE + CENTRAL_PEOPLE).toFixed(1);
  console.log(String(s).padEnd(10) + String(coverage).padEnd(18) + String(used).padEnd(19) +
    String(central - used).padEnd(14) + String(coverage + used).padEnd(10) +
    String(remaining - used).padEnd(10) + personEquiv);
}

console.log('\ndoes a single champion cover their own team');
console.log('team              review    champion capacity    gap   % left to central');
for (const e of sorted)
  console.log(e.name.padEnd(18) + String(e.review).padEnd(10) +
    String(CHAMPION_REVIEWS).padEnd(21) +
    String(Math.max(0, e.review - CHAMPION_REVIEWS)).padEnd(6) +
    ((Math.max(0, e.review - CHAMPION_REVIEWS) / e.review) * 100).toFixed(0));
const gap = sorted.reduce((a, e) => a + Math.max(0, e.review - CHAMPION_REVIEWS), 0);
console.log(`even with a champion on every team, ${gap} reviews/week are left to the center; ` +
  `a single champion covers an average of ${(CHAMPION_REVIEWS / (TOTAL_REVIEWS / TEAM.length)).toFixed(2)} teams`);
```

```
5 teams, 22 developers, 35 reviews/week; central 16 reviews/week

champion  by champions      central used       central idle  covered   waiting   person-equiv
0         0                 16                 0             16        19        2.0
1         6                 16                 0             22        13        2.2
2         12                16                 0             28        7         2.4
3         18                16                 0             34        1         2.6
4         24                11                 5             35        0         2.8
5         29                6                  10            35        0         3.0

does a single champion cover their own team
team              review    champion capacity    gap   % left to central
subscriber-portal 10        6                    4     40
meter-collection  8         6                    2     25
billing           6         6                    0     0
shared-library    6         6                    0     0
field-app         5         6                    0     0
even with a champion on every team, 6 reviews/week are left to the center; a single champion covers an average of 0.86 teams
```

Row zero gives the starting condition: the two-person central team can handle 16 reviews a week,
and 19 reviews go through without a security eye. The covered share is 46 percent, and that number
has nothing to do with the central team's diligence — 16 units of capacity have been allocated to
35 reviews.

As champions are added, the covered count rises from 16 to 22, to 28, to 34, and **saturates at
the fourth champion**. The fifth champion does not change the covered count at all; it only raises
the center's idle capacity from 5 to 10. The person-equivalent column gives the cost: with four
champions, total security competence adds up to 2.8 full-time people, and 0.8 of that comes out of
the twenty-two existing developers. Getting the same coverage by growing the central team would
require enough specialists to handle 35 reviews; the champion model is a way to saturate coverage
without hiring anyone new.

## How Many Teams Does One Champion Cover

The second table disproves a common design assumption with a number. The sentence "one champion
per team" implies that coverage is complete; the numbers do not say that. A champion's weekly
capacity is six reviews, the average team load is seven reviews — meaning a single champion
covers an average of **0.86 teams**.

The distribution is sharper than the average. The subscriber portal produces ten reviews a week; the
champion handles six of them, four are left to the center, and that is 40 percent of that team's
reviews. For the meter collection service the share is 25 percent. The three smaller teams have a
zero gap, and the field app's champion has one review's worth of idle capacity, but that capacity
cannot be moved to another team — **context does not travel**.

The result is this: even with a champion on every team, six reviews a week are left to the center.
The champion count saturates coverage **at the team boundary**, not organization-wide; a central
capacity to cover the remaining load is needed in every case. And if capacity is not distributed
according to team load, the team that produces the most reviews carries the biggest gap — that is
the column the table is sorted by.

## Depth Does Not Grow With Coverage

A covered review count is not the same thing as a caught item count. Who sits in a review
determines which difficulty of item gets seen.

```js
// run2.mjs — what happens to depth as coverage grows: whose eyes do difficulty-3 items pass through
import { TEAM, CHAMPION_REVIEWS, CENTRAL_PEOPLE, CENTRAL_REVIEWS, DIFFICULTY,
  CHAMPION_DEPTH, TOTAL_REVIEWS } from './team.mjs';

const central = CENTRAL_PEOPLE * CENTRAL_REVIEWS;
const sorted = [...TEAM].sort((a, b) => b.review - a.review);
const shallow = DIFFICULTY.filter(([z]) => z <= CHAMPION_DEPTH).reduce((a, [, n]) => a + n, 0);
const deep = DIFFICULTY.filter(([z]) => z > CHAMPION_DEPTH).reduce((a, [, n]) => a + n, 0);
const ESCALATION = 0.6; // the share of deep items the champion flags as "not sure" and forwards to the center

console.log(`${shallow + deep} items: ${shallow} visible to a champion, ${deep} requiring expertise`);
console.log('\nchampion  by champions      by central      shallow found  deep found       + escalation  total');
for (let s = 0; s <= TEAM.length; s++) {
  const coverage = sorted.slice(0, s).reduce((a, e) => a + Math.min(e.review, CHAMPION_REVIEWS), 0);
  const centralCoverage = Math.min(TOTAL_REVIEWS - coverage, central);
  const shallowCaught = Math.round((shallow * (coverage + centralCoverage)) / TOTAL_REVIEWS);
  const deepCaught = Math.round((deep * centralCoverage) / TOTAL_REVIEWS);
  const withEscalation = Math.round((deep * (centralCoverage + ESCALATION * coverage)) / TOTAL_REVIEWS);
  console.log(String(s).padEnd(10) + String(coverage).padEnd(18) + String(centralCoverage).padEnd(16) +
    String(shallowCaught).padEnd(15) + String(deepCaught).padEnd(17) + String(withEscalation).padEnd(14) +
    (shallowCaught + withEscalation));
}
console.log(`\nescalation's extra load on the center at 5 champions is ` +
  `${(deep * ESCALATION * 29 / TOTAL_REVIEWS).toFixed(1)} items; ` +
  `the center's idle capacity is ${central - 6} reviews/week`);
```

```
23 items: 18 visible to a champion, 5 requiring expertise

champion  by champions      by central      shallow found  deep found       + escalation  total
0         0                 16              8              2                2             10
1         6                 16              11             2                3             14
2         12                16              14             2                3             17
3         18                16              17             2                4             21
4         24                11              18             2                4             22
5         29                6               18             1                3             21

escalation's extra load on the center at 5 champions is 2.5 items; the center's idle capacity is 10 reviews/week
```

The shallow-item column behaves as expected: it rises from eight to eighteen and completes at the
fourth champion. The deep-item column does not behave. The central team alone catches two deep
items, and that number **does not change at all** until the champion count reaches four; at the
fifth champion it **drops to one**.

The reason for the drop sits in the neighboring column. When champions cover 29 reviews, only 6
reviews are left to the center; as the number of reviews the center looks at falls, so does the
odds of it seeing an item that requires expertise. The champion is sitting in those reviews but is
not at the level to see a deep item. **The decision that grew coverage shrank depth.** The total
column gives this in a single number: the best result is 22 items at four champions, 21 items at
five.

The last column measures the countermeasure. If there is an **escalation path** through which a
champion can still forward an item they cannot see to the center, the deep-item count at five
champions rises from 1 to 3. The cost of this is the 2.5-item extra load it brings to the center,
and the center's 10 reviews/week of idle capacity covers it. Whether the champion model works
depends not on distributing competence but on **being able to report insufficiency**: a champion
who can say "I'm not sure" gets more items caught than a champion trying to be sure.

A limitation has to be stated. The model holds the champion's depth ceiling fixed; in reality the
ceiling rises with training, and that is outside this measurement. What is measured is how
coverage behaves at a given ceiling; training's contribution is a separate decision with its own
cost.

## What Happens to an Uncovered Review

What happens to an uncovered review is not a question of capacity, it is a question of **rule**.
If the security review is mandatory, an uncovered change waits; if it is not mandatory, it goes
through and the item it carries slips past.

```js
// run3.mjs — an uncovered review can be tied to two different rules: blocking and non-blocking
import { TEAM, CHAMPION_REVIEWS, CENTRAL_PEOPLE, CENTRAL_REVIEWS, DIFFICULTY,
  CHAMPION_DEPTH, TOTAL_REVIEWS } from './team.mjs';

const WEEKS = 12, central = CENTRAL_PEOPLE * CENTRAL_REVIEWS;
const sorted = [...TEAM].sort((a, b) => b.review - a.review);
const shallow = DIFFICULTY.filter(([z]) => z <= CHAMPION_DEPTH).reduce((a, [, n]) => a + n, 0);
const deep = DIFFICULTY.filter(([z]) => z > CHAMPION_DEPTH).reduce((a, [, n]) => a + n, 0);

console.log('           gap     blocking rule               non-blocking rule');
console.log('champions  /week   changes waiting      slip   waiting   items slipped');
for (let s = 0; s <= TEAM.length; s++) {
  const coverage = sorted.slice(0, s).reduce((a, e) => a + Math.min(e.review, CHAMPION_REVIEWS), 0);
  const centralCoverage = Math.min(TOTAL_REVIEWS - coverage, central), gap = TOTAL_REVIEWS - coverage - centralCoverage;
  const caught = Math.round((shallow * (coverage + centralCoverage)) / TOTAL_REVIEWS) +
    Math.round((deep * (centralCoverage + 0.6 * coverage)) / TOTAL_REVIEWS);
  console.log(String(s).padEnd(11) + String(gap).padEnd(8) + String(gap * WEEKS).padEnd(21) +
    '0'.padEnd(7) + '0'.padEnd(10) + (shallow + deep - caught));
}
console.log('\nunder the blocking rule no item slips through, and up to 228 changes wait over 12 weeks;');
console.log('under the non-blocking rule no change waits, and up to 13 items go through unseen.');
```

```
           gap     blocking rule               non-blocking rule
champions  /week   changes waiting      slip   waiting   items slipped
0          19      228                  0      0         13
1          13      156                  0      0         9
2          7       84                   0      0         6
3          1       12                   0      0         2
4          0       0                    0      0         1
5          0       0                    0      0         2

under the blocking rule no item slips through, and up to 228 changes wait over 12 weeks;
under the non-blocking rule no change waits, and up to 13 items go through unseen.
```

The same gap count produces two completely different outcomes. With competence not distributed at
all, the blocking rule holds back **228 changes** over twelve weeks and slips no item through; the
non-blocking rule holds back no change at all, and **13 items** pass through without a security
eye. This is the same trade-off as the first lesson's gate table, but this time the reason for the
hold is not the gate's capacity, it is **the absence of competence to review**.

The direction of the decision depends on the champion count. At zero and one champion, the
blocking rule cannot actually be run — the 228 and 156 accumulated changes stop the teams, the
rule turns into an exception list within a few weeks, and it effectively degrades into the
non-blocking rule. After the third champion, the gap falls to one review a week; it holds back 12
changes over twelve weeks, and the blocking rule becomes workable. **A blocking rule cannot be
written before competence is distributed**; if it is written anyway, it will not be enforced, and
an unenforced rule leaves even the reviews it was supposed to cover uncovered.

The last row shows the previous table's finding once more. At the fifth champion the gap is zero,
the waiting-changes count is zero, and everything looks covered; and yet the slipped-item count
still rises from 1 at four champions to **2**. A measurement that only looks at the coverage
column never sees this increase.

## Summary

- 5 teams produce 35 reviews a week; the two-person central team covers 16 of them, and 19
  reviews go through without a security eye.
- As champions are added, covered reviews rise from 16 to 34 and saturate at 35 with the fourth
  champion; the fifth champion does not change coverage, it only raises the center's idle
  capacity from 5 to 10.
- A champion's capacity is 6 reviews, the average team load is 7 reviews: a single champion
  covers 0.86 teams. Even with a champion on every team, 6 reviews a week are left to the center,
  and 4 of those come from a single team.
- A four-champion arrangement costs 2.8 person-equivalents, and 0.8 of that comes out of the
  existing 22 developers; coverage saturates without hiring anyone new. For uncovered reviews,
  the blocking rule holds back 228 changes over 12 weeks and slips no item through, while the
  non-blocking rule holds back nothing and slips 13 items through; the blocking rule only becomes
  workable after the third champion.
- Catching the 5 items that require expertise does not rise with the champion count: it stays
  flat at 2 up to four champions, and drops to 1 at the fifth, because the center no longer sees
  those reviews.
- An escalation path raises the deep-item count at five champions from 1 to 3; its cost is a
  2.5-item extra load on the center, and the center's 10 reviews of idle capacity cover it.

## Next Step

This lesson measured where to place competence and found that a four-champion arrangement catches
22 of the 23 items. Being caught does not close an item. Every previous lesson assumed that a
found item gets fixed; even shift left's 250-hour gain stood on top of that assumption, and it was
noted there that a ceiling exists. The same twenty-two developers are the ones who will fix a
found item, and their time is shared with feature work. The next lesson distributes limited
development capacity across the item set: how many items a sprint's capacity takes, how many
items close per unit of capacity, how many items are left out when the ranking criterion changes,
and how much the set of items left out changes when the criterion changes.
