This starts chapter B2. Its course guide is B2 in the Function B course guide →
Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.
recordings/B2.1.mp4A shared service account answers 'what ran' and destroys 'for whom' — so no later control can be conditioned on the caller.
A distinct identity per workload, carrying the human principal alongside it, asserted on every call.
Separate the three identities and show a downstream service authorising on the agent while attributing to the human.
Three identities are present every time an agent acts: the person who asked, the workload that runs, and the agent instance doing the work. Collapse any two of them and you lose the ability to answer the only question that matters after an incident.
set in cybertravels/config.py. You replace it with cybertravels/registry.py: identities as records with an approver, a registration time and a state. identity.py stops asking "is this one of ours" and starts asking "is this still active".Replacing a hand-edited set of agent names with a registry: identities as records that carry an approver, a registration time and a state, so the system can answer when an identity started existing and whether it still should.
A shared service account answers "what ran" and destroys "for whom", so no later control can be conditioned on the caller.
One identity per workload, carrying the human principal alongside it, asserted on every call.
Workloads holding a distinct identity, as a share of the fleet.
Measure this
| what to measure | denominator or target |
|---|---|
| workloads holding a distinct identity | share of the fleet |
who asked what runs what acted
+----------+ +-----------+ +--------------+
| human | --> | workload | --> | agent |
| dana@.. | | pod/task | | instance #7 |
+----------+ +-----------+ +--------------+
| | |
consent attestation the actor in the log
collapse any two and the post-incident question loses its answerMitigates: T9 Identity Spoofing · T3 Privilege Compromise · T1 Memory Poisoning.
Three identities are in play whenever an agent acts, and B1.6 and B1.7 were both caused by collapsing them into one.
The user. The human who asked. Carries the business authority and is the answer to "on whose behalf".
The workload. The process that runs — a container, a function, a pod. It has its own identity, derived from the platform, not from a secret someone pasted.
The agent instance. This particular run, of this particular agent, for this particular task. It is what you revoke when one agent misbehaves.
The control is to keep all three, and to use them for different things:
The failure mode to watch for is a system that authenticates the agent and then forgets the human, because it produces logs that are complete and useless.
What this control closes. Answers who is calling, so every later control has a subject. Without it, default-deny has nothing to deny and the audit trail has nobody to name.
Keeping the three identities apart is a review you will run against every agent CyberTravels ships, not a decision you make once. So it is written down as a procedure: which principal authorises, which one is attributed, which one scopes memory — and the two narrowing rules a delegated token has to satisfy before any of it means anything. This is the file in this repository, embedded verbatim:
The skill — skills/identity/agent-identity-review/SKILL.md
name: agent-identity-review
description: >-
Review how an agent authenticates and how a user's authority is delegated to
it, including token exchange, on-behalf-of chains, service accounts and
non-human identity. Use when asked who an agent is calling as, whether a
delegation is auditable, why a downstream system sees the wrong principal, or
how to scope an agent's credentials.
allowed-tools: Read, Grep, GlobThree identities are in play whenever an agent acts, and most incidents come from collapsing them:
A service account shared by every agent answers "what ran" and destroys "for whom". That is the gap non-human identity exists to close.
Reviewing an agent's auth design, an OBO/token-exchange implementation, a gateway that fronts an agent, or any log where you cannot tell which user caused an action.
1 — Name the three identities. For the flow under review, write down the user principal, the workload identity, and where each is asserted. If the workload identity is a long-lived shared secret, that is finding one.
2 — Check the delegation narrows. In RFC 8693 token exchange the issued token must satisfy both rules:
Either rule alone is insufficient. Subset-only lets a highly privileged user hand an agent authority the agent should never hold; ceiling-only lets an agent exceed the user who asked. Test both directions explicitly.
3 — Check the chain is preserved and not duplicated. The act chain should read user → agent, once. A chain that repeats the principal (dana → dana → agent) usually means the head was appended twice, and it breaks any audit query that counts hops.
4 — Find where OBO stops. Some downstream systems cannot consume a delegated token — legacy databases, vendor APIs, anything with a static credential. Identify each, and require a choke point: a gateway that holds the credential, enforces per-user authorisation before the call, and logs the original principal. The credential must not be reachable by the agent directly.
5 — Check expiry and revocation. How long is the delegated token valid, and what stops it being replayed after the user's session ends? Just-in-time authority that outlives the task is standing authority with extra steps.
6 — Check the log answers the audit question. Pick a real question — "which user caused this row to be deleted?" — and try to answer it from the logs alone. If you cannot, the delegation is not auditable regardless of how it is built.
Input — the fixture committed at the top of scripts/agent_identity_review.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:
request authorized? attributed to
dana@corp -> reports:read True dana@corp via reports-agent
dana@corp -> db:admin False dana@corp via reports-agent
memory keys - the same workspace, two users:
dana -> acme:dana@corp
priya -> acme:priya@corp
shared? False
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.
{
"identities": {"user": "str", "workload": "str", "assertion": "str"},
"delegation": {"mechanism": "obo|impersonation|shared_service_account|none",
"subset_of_presented": true, "within_actor_ceiling": true,
"chain": ["principal", "..."], "chain_wellformed": true,
"ttl_seconds": 0, "revocable": true},
"chokepoints": [{"downstream": "str", "reason": "str",
"enforced_at": "gateway|service|none",
"credential_reachable_by_agent": false}],
"audit": {"question": "str", "answerable_from_logs": true},
"findings": [{"issue": "str", "severity": "critical|high|medium|low", "fix": "str"}]
}
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 B2.1Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.
Then pick b2-1-agent-identity:
| agent | install just for this agent | start it |
|---|---|---|
| Claude Code | python3 scripts/install_skills.py --tool claude --lessons B2.1 | type /b2-1-agent-identity |
| GitHub Copilot (VS Code) | python3 scripts/install_skills.py --tool copilot --lessons B2.1 | type /b2-1-agent-identity in Chat |
| Cursor | python3 scripts/install_skills.py --tool cursor --lessons B2.1 | type / in Agent chat and search for it |
| Codex | python3 scripts/install_skills.py --tool agents --lessons B2.1 | type $b2-1-agent-identity, 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 B2.1Neither 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 B2.1 --out work/cybertravels
python3 scripts/checkpoint.py --at B2.1 --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.
The skill loads and reports its own shape: a routing description an agent reads to decide whether this review applies, the tools it is allowed to use, and a procedure long enough to separate user, workload and agent instance and to check both narrowing rules — scope is a subset of what was presented, and within the actor's own ceiling.
For one agent, write down its three identities. If the workload and the user are the same value, you have inherited credentials; if the instance does not exist, you cannot revoke one run.
Tools used
Skill source: skills/identity/agent-identity-review