---
title: 'Zero Trust'
source: 'https://academia.sh/en/courses/introduction-to-cybersecurity/zero-trust'
course: 'Introduction to Cybersecurity'
language: en
updated: '2026-09-13T05:08:03+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Turns location-based trust into step ownership and derives which identity each step belongs to'
    - 'Measures three numbers together as verification scope widens: remaining path, daily verification, and extra step per request'
    - 'Compares how many paths verification placed at different locations closes for the same cost'
    - 'Ties the paths left open under zero trust to internal flows losing identity and counts the cost of the fix'
---

# Zero Trust

As location-based trust is removed, verification scope is widened in five steps, and at each step the path count, daily verification, and extra step per request are counted together. The reason the paths left open under zero trust stay open is found, and how many internal flows carrying identity end to end would change is measured.

The previous lesson divided the network into compartments and counted the reached item when a
node falls. Every scheme ran on the same silent rule: movement inside a compartment is free.
This is also why the reached item count started at thirteen; adding compartments did not remove
the rule, it only shrank the area where it applies. This lesson removes the rule itself.

**Zero trust** is giving up trust based on where a request comes from entirely and verifying
every step separately. Measuring the principle requires something to stand in for "being
inside": which identity each step **belongs to**. A step passes when it is taken by the identity
that owns it; when another identity tries to take the same step, it stops no matter where on the
network it happens. The three things measured are path count, daily verification, and **extra
step per request**.

- **DP17.** Step ownership is derived from the legitimate flow list: if a flow starts from an
  entry point, that flow's steps belong to that identity.
- **DP18.** Four legitimate flows do not start from an entry point; they are triggered from the
  queue or the management station. These flows' steps are treated as **shared**, meaning which
  identity triggered them is unknown. This is the model's most decisive choice.
- **DP19.** Verification scope is the set of steps where identity is checked. A step outside the
  scope trusts location and lets it through; a step inside the scope checks the ownership set.
- **DP20.** One verification is one extra step. The verification's own latency, duration, and
  failure rate are not modeled; only how many times it happens is counted.
- **DP21.** Verification always gives the correct result. Forged identity, a stolen token, and
  replay are not modeled; these would lower the count of closed paths, not change the direction
  of the measure.

## Identity in Place of Location

The graph stays the same; the only thing that changes is the condition under which a step is
crossed.

```js
// network.mjs — fictional regional measurement network's access graph and legitimate flows (model)
export const ENTRY = ['field-endpoint', 'branch-network', 'public-endpoint', 'maintenance-endpoint'];
export const ASSET = ['measurement-store', 'invoice-db', 'subscriber-registry', 'configuration'];
export const EDGE = [
  ['field-endpoint', 'collector'], ['field-endpoint', 'work-order'], ['branch-network', 'collector'],
  ['branch-network', 'work-order'], ['branch-network', 'management'], ['public-endpoint', 'collector'],
  ['maintenance-endpoint', 'management'], ['maintenance-endpoint', 'queue'], ['collector', 'queue'],
  ['collector', 'measurement-store'], ['queue', 'validator'], ['queue', 'billing'],
  ['validator', 'measurement-store'], ['validator', 'invoice-db'], ['billing', 'invoice-db'],
  ['billing', 'subscriber-registry'], ['work-order', 'queue'], ['work-order', 'subscriber-registry'],
  ['management', 'collector'], ['management', 'validator'], ['management', 'work-order'],
  ['management', 'configuration'],
];
// Legitimate flow: work that runs daily; the step sequence passes through the same graph's edges.
export const FLOW = [
  ['field-reading', ['field-endpoint', 'collector', 'queue'], 1440],
  ['validation', ['queue', 'validator', 'measurement-store'], 1440],
  ['public-reading', ['public-endpoint', 'collector', 'measurement-store'], 300],
  ['branch-query', ['branch-network', 'collector', 'measurement-store'], 210],
  ['invoice-write', ['queue', 'billing', 'invoice-db'], 48],
  ['work-order-opening', ['branch-network', 'work-order', 'subscriber-registry'], 32],
  ['work-order-queuing', ['branch-network', 'work-order', 'queue'], 32],
  ['subscriber-update', ['billing', 'subscriber-registry'], 6],
  ['configuration-change', ['branch-network', 'management', 'configuration'], 2],
  ['maintenance-session', ['maintenance-endpoint', 'management', 'collector'], 1],
  ['queue-maintenance', ['maintenance-endpoint', 'queue'], 1],
  ['reconciliation', ['management', 'validator', 'invoice-db'], 1],
];
export const edgeName = (a, b) => `${a}>${b}`;
export const steps = (d) => d.slice(0, -1).map((x, i) => edgeName(x, d[i + 1]));
export function paths(closed = new Set()) {
  const neighbor = new Map();
  for (const [a, b] of EDGE) if (!closed.has(edgeName(a, b))) neighbor.set(a, [...(neighbor.get(a) || []), b]);
  const out = [];
  const walk = (d, y) => {
    if (ASSET.includes(d)) return void out.push(y);
    for (const k of neighbor.get(d) || []) if (!y.includes(k)) walk(k, [...y, k]);
  };
  for (const g of ENTRY) walk(g, [g]);
  return out;
}
export const blocked = (closed) => FLOW.filter(([, d]) => steps(d).some((k) => closed.has(k)));
```

```js
// identity.mjs — step ownership and verification scopes (model)
import { EDGE, FLOW, ENTRY, ASSET, edgeName, steps } from './network.mjs';
export const EDGE_NAME = EDGE.map(([a, b]) => edgeName(a, b));
// Internal flows do not start from an entry point; their steps are shared by every identity.
const SHARED = new Set(FLOW.filter(([, d]) => !ENTRY.includes(d[0])).flatMap(([, d]) => steps(d)));
const own = (g) => FLOW.filter(([, d]) => d[0] === g).flatMap(([, d]) => steps(d));
export const OWNER = Object.fromEntries(ENTRY.map((g) => [g, new Set([...SHARED, ...own(g)])]));
export const END_TO_END = Object.fromEntries(ENTRY.map((g) => [g, new Set(own(g))]));
const SERVICE = ['collector', 'queue', 'validator', 'billing', 'work-order'];
const compartment = (d) => (ENTRY.includes(d) ? 'outer'
  : SERVICE.includes(d) ? 'service' : d === 'management' ? 'management' : 'data');
export const PERIMETER = EDGE_NAME.filter((k) => ENTRY.includes(k.split('>')[0]));
export const TO_ASSET = EDGE_NAME.filter((k) => ASSET.includes(k.split('>')[1]));
export const GATE = EDGE_NAME.filter((k) => compartment(k.split('>')[0]) !== compartment(k.split('>')[1]));
const neighbor = new Map();
for (const [a, b] of EDGE) neighbor.set(a, [...(neighbor.get(a) || []), b]);
// A step in scope checks identity; a step outside scope trusts location and lets it through.
export function path(g, scope, owner = OWNER) {
  const s = new Set(scope), out = [];
  const walk = (d, y) => {
    if (ASSET.includes(d)) return void out.push(y);
    for (const k of neighbor.get(d) || []) {
      const e = edgeName(d, k);
      if (s.has(e) && !owner[g].has(e)) continue;
      if (!y.includes(k)) walk(k, [...y, k]);
    }
  };
  walk(g, [g]);
  return out;
}
```

```js
// run1.mjs — as verification scope widens, path count and extra step per request
import { FLOW, ENTRY, steps } from './network.mjs';
import { EDGE_NAME, PERIMETER, TO_ASSET, GATE, path } from './identity.mjs';

const unique = (a) => [...new Set(a)];
const SCOPE = [['none', []], ['perimeter', PERIMETER], ['perimeter+asset', unique([...PERIMETER, ...TO_ASSET])],
  ['perimeter+gate', unique([...PERIMETER, ...GATE])], ['every-step', EDGE_NAME]];
const daily = FLOW.reduce((s, [, , n]) => s + n, 0);
console.log('scope            checked  path  closed  daily verification  extra step per request');
for (const [nm, scope] of SCOPE) {
  const y = ENTRY.reduce((s, g) => s + path(g, scope).length, 0), s = new Set(scope);
  const ver = FLOW.reduce((t, [, d, c]) => t + c * steps(d).filter((k) => s.has(k)).length, 0);
  console.log(`${nm.padEnd(17)}${String(scope.length).padEnd(12)}${String(y).padEnd(6)}` +
    `${String(55 - y).padEnd(9)}${String(ver).padEnd(18)}${(ver / daily).toFixed(2)}`);
}
```

```
scope            checked  path  closed  daily verification  extra step per request
none             0           55    0        0                 0.00
perimeter        8           50    5        2018              0.57
perimeter+asset  15          46    9        4057              1.15
perimeter+gate   18          32    23       4059              1.16
every-step       22          20    35       7019              2.00
```
The second row is the measure of location-based trust. Identity is checked at eight entry steps
and nowhere on the inside. Only five of the fifty-five paths close. The cost is low — two
thousand eighteen daily verifications, zero point five seven extra step per request — but what it
buys is low too: ninety percent of the paths stay open. A control sitting at the perimeter never
sees a request again once it has passed the perimeter. This does not mean perimeter verification
is worthless; it is the control that closes the first five paths most cheaply. It means the
measurement cannot stop there — the path count a perimeter control closes is small regardless of
the shape of the network behind it.

The third and fourth rows have to be read together, because their cost is nearly identical: four
thousand fifty-seven and four thousand fifty-nine daily verifications, one point one five and one
point one six extra step per request. But the paths they close are nine and twenty-three. A
two-and-a-half-fold difference for the same cost. Putting verification in front of the asset
means checking the last step of the path, and the request that reaches that step has already
crossed every intermediate node; putting it at the compartment gates cuts the path through its
middle instead. **Verification's cost is set by how many times it runs, its value by where it
runs.**

The last row is zero trust in full. Identity is checked at all twenty-two steps, thirty-five of
the fifty-five paths close and twenty stay open. The cost is seven thousand nineteen daily
verifications, exactly two extra steps per request: every legitimate request shows its identity
twice on average. Thirty more paths closed compared to location-based trust, and the verification
count rose to three and a half times as much.

Dividing the two columns by each other gives an unexpected result. Perimeter verification costs
four hundred four verifications per closed path; verification at every step costs two hundred
one. Per closed path, zero trust is **cheaper** than location-based trust. The most efficient row
is the combination of perimeter and compartment gates: one hundred seventy-six verifications per
closed path. The least efficient is the combination of perimeter and asset entry: four hundred
fifty-one. Looking at total cost and saying "zero trust is expensive" reads only one column of
the table; read together, what is expensive is not the number of verifications but where they
are placed.

The two in the last column also needs explaining. Verification runs at all twenty-two steps, but
the extra step per request comes out to two, because most legitimate flows are two steps long.
Widening the verification scope grows the number of checked steps; what determines the cost a
request pays is how many steps that request passes through. Long flows grow the cost of zero
trust linearly.

## Per Entry Point

The total does not show which identity narrows. When the same measurement is done per entry
point, the width of the ownership set becomes decisive.

```js
// run2.mjs — path per entry point; what is left once identity is carried end to end
import { FLOW, ENTRY, ASSET } from './network.mjs';
import { EDGE_NAME, PERIMETER, OWNER, END_TO_END, path } from './identity.mjs';

console.log('entry                 owned step  location-based  zero trust  end-to-end identity');
for (const g of ENTRY)
  console.log(`${g.padEnd(22)}${String(OWNER[g].size).padEnd(12)}${String(path(g, PERIMETER).length).padEnd(16)}` +
    `${String(path(g, EDGE_NAME).length).padEnd(13)}${path(g, EDGE_NAME, END_TO_END).length}`);
const open = ENTRY.flatMap((g) => path(g, EDGE_NAME));
const count = open.reduce((m, p) => m.set(p.at(-1), (m.get(p.at(-1)) || 0) + 1), new Map());
console.log(`\nasset reached by the ${open.length} paths left open under zero trust:`);
for (const v of ASSET) console.log(`  ${v.padEnd(20)}${count.get(v) || 0}`);
const internal = FLOW.filter(([, d]) => !ENTRY.includes(d[0]));
console.log(`\ninternal flow that would need to change to carry identity end to end ${internal.length}, ` +
  `daily request ${internal.reduce((s, [, , n]) => s + n, 0)}`);
```

```
entry                 owned step  location-based  zero trust  end-to-end identity
field-endpoint        9           5               4            0
branch-network        14          23              9            3
public-endpoint       9           5               1            1
maintenance-endpoint  10          17              6            0

asset reached by the 20 paths left open under zero trust:
  measurement-store   7
  invoice-db          8
  subscriber-registry 4
  configuration       1

internal flow that would need to change to carry identity end to end 4, daily request 1495
```
The maintenance endpoint drops from seventeen paths to six. The branch network drops from
twenty-three to nine, and it is the entry point that leaves the most paths open after zero
trust, because it has the widest ownership set — fourteen steps. The identity whose
legitimate work is wide is the identity that benefits least from zero trust; we saw the same
thing for the management node in the previous lesson, a different measure, the same result.

The public endpoint sees the sharpest narrowing: from five paths to one. Even though its
ownership set is nine steps, its own flow is only two steps; the remaining seven steps come from
the shared set. This row is the clue to the next finding.

The lower table shows how the narrowing spreads across the assets, and the spread is not even. In
the first lesson, twenty-two paths went to the invoice database, seventeen to the measurement
store, fourteen to the subscriber registry, and two to the configuration store. After zero trust
these numbers are eight, seven, four, and one. The asset carrying the most paths narrowed the most
in absolute terms — from twenty-two to eight, sixty-four percent. The configuration store dropped
from two to one, fifty percent. The large proportional narrowing is a result of starting from a
high number; the remaining absolute number is still highest at the invoice database. When a
defense report is written by percentage, the item that looks most protected is still the most open
item when written by absolute number. Which column to read depends on what the decision is:
investment priority is read from the absolute number, progress is read from the ratio.

## Where Identity Gets Lost

Why are twenty paths still open under zero trust? The answer is in the shared steps. Four
legitimate flows do not start from an entry point: validation, invoice writing, subscriber
update, and reconciliation. These are triggered from the queue or the management station, and
who triggered them is not written into the step itself. In the model these seven steps stayed
open, shared by every identity; in reality, too, work taken off a queue does not carry the
identity of whoever put it there.

The last column is the measure of this. If identity were carried end to end, meaning every
internal flow also carried the identity of the request that triggered it, the remaining path
would drop from twenty to four. No path remains from the field endpoint or the maintenance
endpoint; three remain from the branch network, one from the public endpoint. All sixteen paths
come from identity being dropped at the queue.

The price of these sixteen paths can also be written down: rebuilding four internal flows and
changing one thousand four hundred ninety-five daily requests to carry identity. This is not
adding a control, it is a design change; every job the queue delivers has to carry the identity
of its caller too. This is the main reason zero trust is usually left half implemented in
practice: of the fifty-one closable paths, thirty-five close by adding verification, sixteen
close only once the flows are rewritten. The first is a configuration job, the second is a
software job, and the two do not come out of the same budget.

The whole measurement also has a limit. The model assumed verification always gives the correct
result; a stolen identity is valid at every step it owns. Zero trust does not prevent an identity
from being stolen, it shrinks **how many steps a stolen identity is valid for**. The difference
between nine and fourteen in the ownership column is the difference in the area opened up if that
identity is stolen. For the same reason, zero trust should be counted not as an end state but as
an axis of measure: as ownership sets narrow, a stolen identity's value falls, and as they widen,
it rises. What should be measured is not "has zero trust been adopted" but **how many steps are
open per identity**.

## Summary

- Location-based trust checks identity at eight entry steps and nowhere on the inside: only 5 of
  the 55 paths close, at a cost of 0.57 extra step per request.
- Putting verification in front of the asset and putting it at the compartment gates carry
  nearly the same cost (4,057 and 4,059 daily verifications) but close 9 and 23 paths. Cost is
  set by how many times it runs, value by where it runs.
- Verification at every step closes 35 paths and leaves 20; its cost is 7,019 daily
  verifications, exactly 2 extra steps per request.
- The identity with the widest ownership set narrows less: the branch network, with 14
  steps, drops from 23 paths to 9, while the maintenance endpoint, with 10 steps, drops from 17
  to 6.
- The source of the 20 paths left open is the shared steps of 4 internal flows that do not start
  from an entry point. If identity were carried end to end, the remaining path would be 4.
- The price of the last 16 paths is design, not verification: rebuilding 4 internal flows and
  changing 1,495 daily requests to carry identity.

## Next Step

This lesson checked identity at every step and found where identity gets lost. The question
asked was always the same: who sent this request? But crossing a step requires answering two
separate questions, and this lesson only asked the first. A request whose identity is verified is
not authorized just because it is verified; authorization is a separate question asked in a
separate place. What happens when the two questions are collapsed into the same layer, or one
substitutes for the other? That is what the next lesson measures: at how many items a
verified-but-unauthorized request gets through, how many paths open when the control is placed in
the wrong layer, and how many items asking the two questions separately means adding extra
verification to.
