Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.
recordings/B1.11.mp4An agent nobody approved receives delegated work and delegated authority, and the orchestrator has no way to tell it apart from a legitimate worker.
A registry of approved agents with identity-bound admission (B2.5) and an audit trail per hop (B2.7).
Introduce an unregistered agent into the topology and have it receive delegated work.
The orchestrator delegates to whatever agents it discovers. Something joined the pool this morning that nobody registered, and it has been receiving work ever since, with the same standing as the agents you wrote.
What it covers. Introduce an unregistered agent into the topology and have it receive delegated work.
Why a security engineer needs it. An agent nobody approved receives delegated work and delegated authority, and the orchestrator has no way to tell it apart from a legitimate worker. The control it builds is: a registry of approved agents with identity-bound admission (B2.5) and an audit trail per hop (B2.7).
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.
An agent nobody approved receives delegated work and delegated authority, and the orchestrator cannot tell it apart from one you chose.
A registry of approved agents, admission bound to a registered identity, and an audit trail per hop.
Agents observed in the topology that appear in the registry. The gap is the finding, not the percentage.
Measure this
| what to measure | denominator or target |
|---|---|
| agents observed in the topology that appear in the registry | count against observed; the gap is the finding, not the percentage |
registered discovered at runtime
+---------+ +---------+ +---------+
| agent A | | agent B | | ??? | <- joined this morning
+----+----+ +----+----+ +----+----+
| | |
+-------- orchestrator delegates ---+
the pool is a trust boundary. most orchestrators treat it as a config file.OWASP T13 — Rogue Agents in Multi-Agent Systems.
The orchestrator delegates work to agents. The question this risk asks is disarmingly simple: how does it know which agents are allowed to receive that work?
In most deployments the answer is configuration — a list in a file, an env var, a service discovery lookup. None of those is an identity check. An agent that appears in the right place, answering the right protocol, is treated as a legitimate worker.
Two ways one arrives:
A compromised legitimate agent. It was registered and approved; it is now executing someone else's instructions after B1.3 or B1.10. Nothing about its registration is wrong, which is why registration alone does not solve this.
An unregistered agent. A developer stood one up to test something, or an attacker with a foothold registered a service. It receives delegated work and delegated authority because the topology admits by convention rather than by identity.
The consequence specific to multi-agent systems: delegated authority flows to it. The orchestrator does not just send a task, it sends the context and often a token. So an agent nobody approved ends up holding a credential that narrows from a real user's, and the audit trail — if it records agent names at all — records a name the attacker chose.
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 ``
An orchestrator that admits workers by configuration.
CyberTravels discovers its workers. The skill compares what is present against what anybody registered, then follows a delegation to see what the unregistered agent is handed — including the narrowed traveller token.
The skill — skills/threats/agent-registry-gap-check/SKILL.md
name: agent-registry-gap-check
description: >-
Compare the agents actually present in an environment against the agents
anybody registered, and establish what work and which tokens the unregistered
ones are handed. Use when agents are created programmatically, when a
discovery protocol is in play, or when asked how a worker joins a fleet.
allowed-tools: Read, Grep, GlobA topology that discovers its workers will discover whatever answers. The defect is not that an unknown agent exists — it is that the orchestrator hands it the same delegated work, and the same narrowed user token, as a registered one. The unregistered agent then acts as the user against anything downstream that honours the token.
Any fleet where agents register themselves, are discovered over a network, or are spawned by other agents. Also after adding a marketplace, a plugin mechanism or an A2A-style protocol.
1 — Enumerate what is present. Ask the runtime, not the design document: running workloads, connected clients, queue consumers, whatever the discovery mechanism itself returns.
2 — Enumerate what is registered. The list somebody maintains, with owners. Both lists in hand, the gap is arithmetic.
3 — Follow a delegation. For one task, record which agents received work and what credential travelled with it. The question is whether registration is consulted before delegation or only reported afterwards.
4 — Establish what the token permits. A narrowed on-behalf-of token in the hands of an unregistered agent is a user session with no login. Name the downstreams that would honour it.
5 — Separate admission from identity. Registration answers "should this agent exist"; workload identity answers "is this the agent it claims to be". Report which of the two is missing, because they are different projects.
Input — the fixture committed at the top of scripts/agent_registry_gap_check.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:
agent in registry? approved? received work?
pricing-agent True True yes
billing-agent True True yes
reporting-agent-v2 False False yes
agents that received delegated work : 3
of which unregistered : 1
reporting-agent-v2 now holds obo:dana@corp:reports:read,reports:write
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.
{
"present": ["str"],
"registered": ["str"],
"gap": ["str"],
"delegations": [{"agent": "str", "registered": false, "credential": "str"}],
"token": {"kind": "str", "acts_as_user": true, "honoured_by": ["str"]},
"missing": {"admission": true, "workload_identity": true}
}
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.
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.11Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.
Then pick b1-11-rogue-agents-in-a-multi-agent-system:
| agent | install just for this agent | start it |
|---|---|---|
| Claude Code | python3 scripts/install_skills.py --tool claude --lessons B1.11 | type /b1-11-rogue-agents-in-a-multi-agent-system |
| GitHub Copilot (VS Code) | python3 scripts/install_skills.py --tool copilot --lessons B1.11 | type /b1-11-rogue-agents-in-a-multi-agent-system in Chat |
| Cursor | python3 scripts/install_skills.py --tool cursor --lessons B1.11 | type / in Agent chat and search for it |
| Codex | python3 scripts/install_skills.py --tool agents --lessons B1.11 | type $b1-11-rogue-agents-in-a-multi-agent-system, or run /skills |
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.11Neither 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.11 --out work/cybertravels
python3 scripts/checkpoint.py --at B1.11 --diffOn 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.
Three agents are discovered, two are in the registry, and all three receive delegated work — including the narrowed user token. The unregistered agent can now act as the requesting user against any downstream that honours it.
Ask how your orchestrator decides which agents may receive work. If the answer is a config list or service discovery, write down what would have to be true for an extra entry to be noticed.
Tools used
Skill source: skills/threats/agent-registry-gap-check