All lessons › Function B — Securing AI Architectures › CyberTravels' Architecture, and Every Risk It Carries
B1.15

Overwhelming the human in the loop

B1
▶
Lightboard recording

Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.

Video placeholderpublishes automatically from recordings/B1.15.mp4
Risk

The approval gate is recorded as a control and operates as a click. At volume it approves everything, including the one request that mattered.

Control

Approval reserved for irreversible actions, with everything else bounded by policy (B3.6).

The lab

Push approval volume up and measure the point at which review quality collapses.

◉Use case relevance

Approval is a genuine control at four requests a day. At four hundred it is a person clicking approve, and the control has quietly become a log of things somebody scrolled past.

At CyberTravels. Approval on every refund is a real control at four a day. CyberTravels generates four hundred, and the control quietly becomes a log of things somebody scrolled past. R2.

◴What this lesson is — Day 0, Day 1, Day 2

What it covers. Push approval volume up and measure the point at which review quality collapses.

Why a security engineer needs it. The approval gate is recorded as a control and operates as a click. At volume it approves everything, including the one request that mattered. The control it builds is: approval reserved for irreversible actions, with everything else bounded by policy (B3.6).

This is a risk lesson: it shows the failure happening before anything tries to stop it, so the control that follows is answering something you have already watched go wrong.

Day 0 — why

The approval gate is recorded as a control and operates as a click. At volume it approves everything, including the one that mattered.

Day 1 — how

Reserve approval for irreversible actions and bound everything else by policy.

Day 2 — measure

Queue depth and median decision time per reviewer. A control that degrades with volume has to be measured against volume.

Measure this

what to measuredenominator or target
queue depth per reviewercount
median decision time per reviewerminutes, measured against volume

▦The framework, and how it works

   requests/hour     4        40       400
   read carefully   yes      some      no
   approval is    control  friction  a log

   the control does not fail loudly. it degrades into a click.

OWASP T10 — Overwhelming Human-in-the-Loop.

Human approval is the control everyone reaches for first. It is placed at the tool call — the right place — and it is genuinely strong for rare, consequential decisions.

Then the system scales, and the arithmetic turns on it.

An agent generates approval requests at machine speed. A human reads them at human speed. When the queue exceeds what a person can actually consider, the behaviour does not degrade gracefully into "slower but careful". It degrades into approving without reading, because the alternative is being the reason nothing shipped.

The failure is invisible from inside the system. Every approval is recorded. The audit trail shows a human decision on every action. The control appears to be operating at 100%, and the thing being measured — that a human clicked — is not the thing anyone cared about.

There is a second-order effect worth naming: an attacker who wants one approval can manufacture the volume that makes it likely. Generate two hundred benign requests, put the one that matters at position 173, and the control has been defeated by arithmetic rather than by cleverness.

Approval is a control for irreversible actions. Used as a general-purpose gate it becomes a click, and the risk register still counts it.

Where this lands on the reference architecture. `` ingress -> orchestrator -> agent_runtime -> model | | messaging tools / mcp | | knowledge / memory egress identity + policy wrap every call · observability records it ``

2 · The risk, realised

Approval quality against volume, and the position an attacker chooses.

3 · The check, as a skill

Approval coverage at CyberTravels reads 100% at every volume, because coverage measures whether a human was asked. The skill models what reading does instead, and finds the volume at which the gate stops being one.

▶Real time execution as skill

The skill — skills/threats/approval-queue-saturation-model/SKILL.md

name: approval-queue-saturation-model
description: >-
  Model what happens to an approval gate as volume rises — coverage staying at
  100% while actual review collapses — and find the volume at which the control
  stops working. Use when reviewing human-in-the-loop design, approval fatigue,
  or a gate that has never been measured.
allowed-tools: Read, Grep, Glob

Coverage stays at 100%; reading does not

An approval gate reports the same number at every volume, because coverage measures whether a human was asked. What degrades is whether they read it, and an attacker who can choose position only has to generate enough requests to sit behind.

When to use this

Any control whose enforcement is a person: approvals, exception reviews, alert triage sign-off, change advisory.

Procedure

1 — Measure current volume, per reviewer per day. Not the design volume — the observed one, at peak rather than mean.

2 — Establish the reading budget. How many items can one reviewer consider properly in a shift? Ask them; the number is usually between 20 and 30 and it is always far below the queue.

3 — Model detection against position. Place a malicious item at various depths and compute the probability it is actually read. The curve falls off a cliff at the reading budget, not gradually.

4 — Note who controls position. If a requester can generate the items in front of theirs, depth is attacker-chosen and the average case is irrelevant.

5 — Report the two numbers that change the design. Items per reviewer per day, and the reading budget. The gap between them is the finding, and it points at routing by reversibility rather than at hiring.

Example

Input — the fixture committed at the top of scripts/approval_queue_saturation_model.py. Edit it and re-run: the buckets, counts and verdicts below are derived from it, not hard-coded.

Output — the opening lines of a real run:

 daily volume  considered  stamped  caught  missed
           10          10        0       1       0
           25          25        0       1       0
          100          25       75       0       1
          500          25      475       0       1

At every volume the audit trail shows a human approval on 100% of
actions. The control reports full coverage in all four rows.

The run continues past this. test_skills.py executes the script on every build, with no model configured — so what CI proves is that it runs and refuses legibly, not that it prints these lines. The output above is a recorded run against a real model, and nothing re-diffs it: a model's answer is not reproducible, and this repository does not claim otherwise anywhere it can be checked.

Output contract

{
  "volume": {"per_reviewer_per_day": 0, "measured_at": "peak|mean"},
  "reading_budget": 0,
  "coverage_reported": 1.0,
  "detection_by_depth": [{"depth": 0, "read_probability": 0.0}],
  "position_attacker_controlled": true,
  "gap": 0
}

Failure modes

  • Reporting coverage. It is 100% by construction and means nothing.
  • Using mean volume. The gate fails at peak.
  • Recommending more reviewers. The fix is fewer items, chosen by reversibility.

Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.

1In your AI agent — you are taught

The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.

Install it, once. From the folder you cloned. --all does every agent it finds on your machine:

python3 scripts/install_skills.py --all --lessons B1.15

Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.

Then pick b1-15-overwhelming-the-human-in-the-loop:

agentinstall just for this agentstart it
Claude Codepython3 scripts/install_skills.py --tool claude --lessons B1.15type /b1-15-overwhelming-the-human-in-the-loop
GitHub Copilot (VS Code)python3 scripts/install_skills.py --tool copilot --lessons B1.15type /b1-15-overwhelming-the-human-in-the-loop in Chat
Cursorpython3 scripts/install_skills.py --tool cursor --lessons B1.15type / in Agent chat and search for it
Codexpython3 scripts/install_skills.py --tool agents --lessons B1.15type $b1-15-overwhelming-the-human-in-the-loop, or run /skills

2As one Python command — you just run it

No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.

python3 scripts/lesson.py B1.15

3Read the code, and the difference — nothing runs

Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.

python3 scripts/checkpoint.py --at B1.15 --out work/cybertravels
python3 scripts/checkpoint.py --at B1.15 --diff

On Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.

Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.

Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.

If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.

✓What you just proved

Approval coverage reads 100% at every volume while the malicious request is caught only when the queue is small enough to be read — and an attacker choosing the position needs only to generate the requests in front of it.

✎Your turn

Count how many approval requests one of your agents generates per day and ask the person approving them how many they read in full. The gap between those two numbers is the control's real coverage.

Skill source: skills/threats/approval-queue-saturation-model

← PreviousB1.14 · Repudiation and untraceability