---
title: 'The Difference Between DevSecOps and DevOps'
source: 'https://academia.sh/en/courses/secure-lifecycle/devsecops-and-devops-difference'
course: 'The Secure Development Lifecycle'
language: en
updated: '2026-09-26T03:02:39+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Measures the difference, in stages, between the stage where a security item is born and the stage where it is caught'
    - "Counts the separate gate's queueing behavior: pending changes, average wait, and the legitimate changes that batching sends back"
    - 'Calculates together the extra items caught by spreading the check across stages and the false-positive load it produces in return'
---

# The Difference Between DevSecOps and DevOps

The same 23-item set is run through two flows: a single security gate standing before deployment catches 10 items, never sees 5 of them, and leaves 120 changes waiting in the queue by day 60; once the check is spread across six stages, all 23 items are caught and the average delay drops from 1.94 stages to 0.13 stages. The cost of spreading it out is that 177 of 200 alerts are false positives.

The previous curriculum spent ten courses counting the paths that defense closed, and at its close
asked which item a scarce budget should go to. All of those measurements shared one silent
assumption: **the software was already written.** The path set was extracted from a running system;
the check was placed on a running system. This course removes that assumption and takes up the same
measurement network's software while it is being built.

The unit of measure changes. Instead of a path, a **stage** is counted: in which stage a
vulnerability or a wrong decision is born, in which stage it can be caught, and how many stages
apart the two are. This first lesson measures the crudest form of that difference — the difference
between security being **built into** the flow and standing at the end of the flow as a **separate
gate**. Both setups are run on the same item set; three columns come out: how many items are
caught, how many stages late they are caught, and what is paid in return.

- **DA1.** The network and its software are fictional: the regional measurement network's
  subscriber portal, meter collection service, billing, and field application are developed by five
  teams. The change flow and the item set are modeled with `node`; what is measured is this model.
- **DA2.** The stage set has six members: requirement, design, code, build, deployment, runtime. An
  item is born in exactly one stage and cannot be caught before the stage it is born in.
- **DA3.** The visibility vector is a modeling decision: it says whether a check placed at that
  stage can see the item or not. In reality this mapping is worked out by trial; the point of the
  lesson is not the mapping but the count run on top of it.
- **DA4.** A check either sees an item or it does not. Partial visibility and the probability of
  missing it are not modeled; both would lower the number of items caught without changing the
  direction of the result.
- **DA5.** Gate review happens at the batch level. If a batch contains an item the gate can see, the
  whole batch is sent back; the other changes in the batch wait along with it.
- **DA6.** The alert counts and minute values are given model values. The false-positive count is
  not given directly; it is found by subtracting the items actually caught from the alert count.

## The Item Set and the Change Flow

The model keeps two things separate: the **changes** that pass through the flow, and the **items**
that a portion of those changes carry. An item is defined by the stage it is born in and a
six-digit visibility vector.

```js
// flow.mjs — the change flow and item set of the fictional measurement network's software (model)
export const STAGE = ['requirement', 'design', 'code', 'build', 'deployment', 'runtime'];
export const TEAM = [['subscriber-portal', 10], ['meter-collection', 8], ['billing', 6],
  ['field-app', 5], ['shared-library', 6]];
export const DAY = 60; // 12-week measurement window, business days
// item: name | stage born in | visibility vector (1/0 per stage: can a check placed
// there see this item?). The vectors are a modeling decision, not a measurement.
export const ITEM = `
  no-data-classification        0 100000
  undefined-retention-period    0 100001
  unclear-personal-data-scope   0 100010
  unauthenticated-internal-call 1 011110
  weak-session-model            1 010011
  trust-boundary-free-flow      1 010000
  missing-role-in-authz-model   1 011001
  missing-authorization-check   2 001101
  embedded-secret-key           2 001110
  unvalidated-input             2 001001
  personal-data-in-logs         2 001001
  incomplete-error-path         2 001000
  weak-randomness                2 001000
  unbounded-query-size          2 000001
  outdated-dependency           3 000110
  unsigned-build-output         3 000110
  unnecessary-build-tool        3 000100
  default-password              4 000011
  overly-broad-network-rule     4 000011
  unencrypted-backup            4 000010
  open-admin-endpoint           4 000011
  race-under-scale              5 000001
  third-party-disclosed-flaw    5 000001`
  .trim().split('\n').map((r) => {
    const [name, d, g] = r.trim().split(/\s+/);
    return { name, born: +d, g };
  });
export const DAILY = TEAM.reduce((s, [, n]) => s + n, 0) / 5;
export const TOTAL_CHANGES = DAILY * DAY;
// The change that carries an item is placed at even intervals in the flow (model).
export const CARRIER = new Map(ITEM.map((k, i) =>
  [Math.floor((i * TOTAL_CHANGES) / ITEM.length), k]));
// First visible stage: the first stage at or after birth where the visibility vector is 1.
export const firstSeen = (k, stages) =>
  stages.find((i) => i >= k.born && k.g[i] === '1') ?? null;
```

The vectors carry the lesson's real information. `no-data-classification` can be seen only in the
requirement stage; no later check can find it, because there is no faulty line to find, only a
missing decision. `unbounded-query-size` is born in the code stage but becomes visible only at
runtime: there is nothing wrong in the code, only the absence of a limit that shows up once the
data grows.

## The Separate Gate's Queue

The first setup puts security at a single point: a review gate standing before deployment. The
gate's daily capacity is limited, while the arrival rate is the sum of the teams.

```js
// run1.mjs — single gate: changes queue at one point before deployment
import { DAY, DAILY, TOTAL_CHANGES, CARRIER, ITEM } from './flow.mjs';

const CAPACITY = 5; // reviews the gate can finish per day (model)

function run(b) {
  const batch = [];
  for (let i = 0; i < TOTAL_CHANGES; i += b) {
    const last = Math.min(i + b, TOTAL_CHANGES) - 1, contents = [];
    for (let c = i; c <= last; c++) if (CARRIER.has(c)) contents.push(CARRIER.get(c));
    batch.push({ first: i, n: last - i + 1, ready: Math.floor(last / DAILY), contents, done: null });
  }
  for (let d = 0; d < DAY * 20; d++) {
    let slot = CAPACITY;
    for (const p of batch) if (p.done === null && p.ready <= d && slot-- > 0) p.done = d;
  }
  // A change's wait: from the day it arrives to the day its batch finishes.
  let wait = 0;
  for (const p of batch)
    for (let c = p.first; c < p.first + p.n; c++) wait += p.done - Math.floor(c / DAILY);
  const open = batch.filter((p) => p.ready < DAY && p.done >= DAY);
  return { daily: batch.length / DAY, pending: open.reduce((s, p) => s + p.n, 0),
    avg: wait / TOTAL_CHANGES,
    sentBack: batch.filter((p) => p.contents.some((k) => k.g[4] === '1')).reduce((s, p) => s + p.n, 0) };
}

const seen = ITEM.filter((k) => k.g[4] === '1').length;
console.log(`${DAILY} changes/day, ${DAY} days, ${TOTAL_CHANGES} changes; ` +
  `gate ${CAPACITY} reviews/day; items the gate can see ${seen}/${ITEM.length}`);
console.log('\nbatch  reviews/day   changes pending at day 60     average wait days     changes sent back');
for (const b of [1, 2, 3, 5, 7]) {
  const r = run(b);
  console.log(String(b).padEnd(7) + r.daily.toFixed(1).padEnd(14) +
    String(r.pending).padEnd(30) + r.avg.toFixed(1).padEnd(22) + r.sentBack);
}
```

```
7 changes/day, 60 days, 420 changes; gate 5 reviews/day; items the gate can see 10/23

batch  reviews/day   changes pending at day 60     average wait days     changes sent back
1      7.0           120                           12.0                  10
2      3.5           0                             0.1                   20
3      2.3           0                             0.1                   30
5      1.4           0                             0.3                   50
7      1.0           0                             0.0                   70
```

The first row gives the gate's capacity problem: seven changes arrive per day, the gate can finish
five. Because the remaining two pile up, 120 changes are still in the queue on day sixty, with an
average wait of 12 days. This is not a slowdown, it is a **saturated queue**: as long as the gap
does not close, the wait time keeps growing as time passes.

The teams' response to this is batching — piling up changes and sending them into a single review.
The table shows that batching works: once the batch size rises to two, the queue closes and the
average wait drops to a tenth of a day. The last column writes down the cost. While the number of
items the gate actually sees stays fixed at **ten** in every row, the number of changes sent back
because of those ten items rises from 10 to 70. At a batch size of seven, six extra legitimate
changes are held up for the sake of a single item.

The gate's second limit stands in the very first row: of the twenty-three items, only ten are in a
form the gate can look at. The remaining thirteen items pass through the gate without any trouble,
because the gate does not look at a requirements decision or a design gap — the only thing in its
hands is the build itself, about to be deployed.

## The Same Item Set, a Distributed Check

The second setup takes the same item set and spreads the check across six stages. The comparison is
fair for exactly this reason: the only thing that changes is **where the check stands**.

This is where the naming of the two setups earns its meaning. Development and operations merging
into a single flow (DevOps) is not a tool choice, it is a feedback rule: the shorter the loop in
which a decision sees its outcome, the more cheaply it gets fixed. The gate setup applies this rule
to delivery speed but leaves the security item outside the rule; security's feedback loop, however
fast it runs, passes through a single point at the very end of the flow. Building security into the
flow (DevSecOps) consists of applying the same rule to the security item as well. What sets it apart
is not adding a new kind of check — five of the six checks in the table could just as well be set up
in the gate setup. What sets it apart is placing the check into the loop in which the item is born.

```js
// run2.mjs — same item set, two flows: check at a single gate and spread across stages
import { STAGE, ITEM, firstSeen } from './flow.mjs';

// Per-stage output of a check built into the flow (model): unit run, alerts produced,
// developer minutes per alert.
const CHECK = [['requirement review', 18, 5, 25], ['design session', 12, 9, 30],
  ['static code analysis', 420, 96, 6], ['dependency scan', 420, 54, 8],
  ['configuration audit', 60, 22, 12], ['runtime observation', 60, 14, 20]];

const flows = [['single gate', [4, 5]], ['integrated', [0, 1, 2, 3, 4, 5]]];
console.log('flow         caught     never seen      total delay stages    average delay');
const result = new Map();
for (const [name, stages] of flows) {
  const y = ITEM.map((k) => [k, firstSeen(k, stages)]);
  const found = y.filter(([, i]) => i !== null);
  const late = found.reduce((s, [k, i]) => s + i - k.born, 0);
  result.set(name, y);
  console.log(name.padEnd(13) + String(found.length).padEnd(11) +
    String(ITEM.length - found.length).padEnd(16) + String(late).padEnd(22) +
    (late / found.length).toFixed(2));
}

console.log('\nstage           born   caught in gate flow     caught in integrated flow');
for (const [i, a] of STAGE.entries())
  console.log(a.padEnd(16) + String(ITEM.filter((k) => k.born === i).length).padEnd(7) +
    String(result.get('single gate').filter(([, j]) => j === i).length).padEnd(24) +
    result.get('integrated').filter(([, j]) => j === i).length);

console.log('\ncheck                   unit   alert  actual  false positive  developer minutes');
let alertTotal = 0, minuteTotal = 0;
for (const [i, [name, unit, alerts, minutes]] of CHECK.entries()) {
  const actual = result.get('integrated').filter(([, j]) => j === i).length;
  alertTotal += alerts; minuteTotal += alerts * minutes;
  console.log(name.padEnd(24) + String(unit).padEnd(7) + String(alerts).padEnd(7) +
    String(actual).padEnd(8) + String(alerts - actual).padEnd(16) + alerts * minutes);
}
console.log(`\ntotal ${alertTotal} alerts, ${minuteTotal} minutes (${(minuteTotal / 60).toFixed(1)} hours), ` +
  `per item ${(minuteTotal / 60 / 23).toFixed(1)} hours`);
const gateHours = 300 * 3;
console.log(`gate: 300 reviews x 3 hours = ${gateHours} hours, 10 items, ` +
  `per item ${(gateHours / 10).toFixed(0)} hours`);
```

```
flow         caught     never seen      total delay stages    average delay
single gate  18         5               35                    1.94
integrated   23         0               3                     0.13

stage           born   caught in gate flow     caught in integrated flow
requirement     3      0                       3
design          4      0                       4
code            7      0                       6
build           3      0                       3
deployment      4      10                      4
runtime         2      8                       3

check                   unit   alert  actual  false positive  developer minutes
requirement review      18     5      3       2               125
design session          12     9      4       5               270
static code analysis    420    96     6       90              576
dependency scan         420    54     3       51              432
configuration audit     60     22     4       18              264
runtime observation     60     14     3       11              280

total 200 alerts, 1947 minutes (32.5 hours), per item 1.4 hours
gate: 300 reviews x 3 hours = 900 hours, 10 items, per item 90 hours
```

The first table gives two separate numbers, and they must not be mixed up. In the gate flow, **18**
items are caught, but only ten of these are caught at the gate; the remaining eight surface later
through a runtime event. Surfacing is not the same as catching, but at least the item becomes
known. **5** items, on the other hand, are items that neither setup sees: the classification gap in
the requirement, the trust-boundary-free flow in the design, the incomplete error path and the weak
randomness in the code, the unnecessary tool in the build. These are not in any record, do not enter
any ranking, do not take up any budget.

The delay column reduces the difference to a single number. Items caught in the gate flow are found
a total of 35 stages late, 1.94 stages per item. In the integrated flow, total delay is 3 stages,
and all of it comes from a single item: `unbounded-query-size`, born in the code stage, becomes
visible only at runtime.

The second table shows this one item directly. Seven items are born in the code stage; the
integrated flow catches six of them in the code stage. The seventh belongs to a class no early
check can look at, and it stays there no matter how far left you push the check. This course's
second lesson counts exactly that class.

## The Cost of Spreading It Out

The third table is the bill for the integrated flow. Six checks produce a total of 200 alerts; 23
of these are real items, 177 are false positives. The ratio varies from check to check: the
requirement review gets three of its five alerts right, static code analysis gets six of its
ninety-six. As frequency rises, the hit rate falls, because code analysis runs on every one of the
420 changes while the requirement review is convened only eighteen times.

The total load is 1,947 developer-minutes, that is, 32.5 hours. The time the gate flow spends in
the same window is 300 reviews times three hours, that is, 900 hours. The cost paid per item is 1.4
hours in the integrated flow, 90 hours in the gate flow — a sixty-four-fold difference. It should be
noted that the two numbers do not measure the same work: the gate looks at every change, the
integrated check looks only at the ones that produce an alert. That is also where the difference
comes from.

The second item of cost does not show up in the table. 177 false positives means interrupting a
developer's work 177 times; ninety of these come from a single check. A check's real cost is not
the time it runs but how many of the alerts it produces have to be read by a human. When the
false-positive rate rises, alerts stop getting read, and the real items that check catches become
invisible too — this produces the same outcome as never putting the check in place at all, except
the cost keeps being paid.

## Summary

- In the fictional measurement network's software, 420 changes flow over 60 business days and
  carry 23 items; items are born in all six stages, most of all in the code stage (7 items).
- A single gate standing before deployment can see only 10 of the 23 items. 7 changes arrive per
  day, 5 reviews get finished; by day 60, 120 changes are waiting in the queue and the average wait
  is 12 days.
- Batching closes the queue, but its cost is legitimate changes sent back: at batch size 1 it is
  10, at batch size 7 it is 70 changes held up because of the same 10 items.
- When the check is spread across six stages, all 23 items are caught; items never seen drop from
  5 to 0, total delay drops from 35 stages to 3 stages, and delay per item drops from 1.94 to 0.13
  stages.
- The cost of spreading it out is that 177 of 200 alerts are false positives; 576 of the 1,947
  developer-minutes go to the 96 alerts produced by a single check.
- Of the 7 items born in the code stage, 6 are caught in the code stage; one becomes visible only
  at runtime, and an early check does not lower that item's delay.

## Next Step

This lesson moved the check and counted that the number of items caught went up while the delay
went down. The one thing not measured is what that delay actually costs. A delay of thirty-five
stages is a number, but the difference between fixing an item at the design stage and fixing it at
runtime is not a stage count, it is the work spent: changing a design decision is a meeting; taking
the same decision back out of a deployed system is a data migration, a release plan, and an outage
window. The next lesson puts the per-stage fix cost into the model, runs the same item set through
two flows with late and early checks, and calculates the overall multiplier. The same lesson also
asks a second question: for which items that multiplier does not hold — that is, how large is the
class of items for which catching them early gains nothing at all.
