All lessons › Function B — Securing AI Architectures › Securing the Architecture — Identity and Ingress
B2.2

Bootstrapping the first credential

B2
▶
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/B2.2.mp4
Risk

A pre-shared secret in an image or an environment variable is copyable, so possession stops being proof of identity.

Control

Platform attestation exchanged for a short-lived, workload-bound credential.

The lab

Exchange an attestation for a credential, then show a copied secret failing the same exchange.

◉Use case relevance

An agent needs a credential to prove who it is, and it cannot be given one safely without already proving who it is. Every long-lived secret in your estate exists because somebody resolved that circle by giving up.

At CyberTravels. You add registry.attest(). CyberTravels' agents stop being handed IDP_SECRET at deploy time and start presenting what the platform already observed about them — module and image — for a five-minute SVID. The smoke test presents the wrong image and is refused.

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

The bootstrap problem — a workload needs a credential to prove who it is and has to prove who it is to get one — answered by not issuing a first secret at all. Attestation against properties the platform already observes, for a short-lived identity document.

Day 0 — why

A pre-shared secret in an image or an environment variable is copyable, so possession stops being proof of identity.

Day 1 — how

Exchange platform attestation for a short-lived, workload-bound credential.

Day 2 — measure

Credentials issued against attestation rather than pre-shared, and their median lifetime.

Measure this

what to measuredenominator or target
credentials issued against attestation rather than pre-sharedshare of credentials
credential lifetimemedian, in minutes

▦The framework, and how it works

   to get a credential you must prove who you are
   to prove who you are you need a credential
                  |
             attestation breaks the circle
                  v
   platform says "this workload is what it claims"  (hardware/orchestrator)
                  |
                  v
   short-lived identity document ---> rotated automatically, never stored

Mitigates: T9 Identity Spoofing & Impersonation.

B2.1 says the workload needs its own identity. This lesson is about how it gets one, because there is a circularity: to receive a credential securely the workload must already prove who it is.

The wrong answer is a pre-shared secret — a key in the image, a token in an environment variable, a file mounted at deploy time. All of them are copyable, and a copyable secret makes possession the proof of identity. Anyone who reads the image is the agent.

The control is attestation. The platform that started the workload already knows things nobody else can forge: which image ran, in which namespace, under which service account, on which node. It signs a statement to that effect, and an identity service exchanges that statement for a short-lived credential bound to that workload.

Three properties matter:

  • Non-copyable. The attestation describes a running process. Copying the document to another machine produces a claim the platform will not sign.
  • Short-lived. The credential expires in minutes, so theft has a deadline.
  • Bound. It is issued to that workload identity, so presenting it from elsewhere fails.

This is what SPIFFE/SPIRE and every cloud workload-identity system do. The lesson models the exchange, not the product.

What this control closes. Makes possession stop being proof. Without it, B1.7 is unavoidable: a copyable secret means every holder is the agent.

2 · The control

3 · The check, as a skill

Whether CyberTravels' agents hold a credential or a secret is settled by four probes, not by reading the deployment manifest. The skill runs them: an unattested process, a genuine image nobody registered, a credential presented from another node, and one presented after its TTL.

▶Real time execution as skill

The skill — skills/identity/workload-attestation-check/SKILL.md

name: workload-attestation-check
description: >-
  Establish how a workload receives its first credential and whether possession
  of that credential is still proof of identity — testing an unattested process,
  an unregistered image, presentation from another node, and use after expiry.
  Use when reviewing bootstrap, SPIFFE/SPIRE, or any secret mounted at deploy
  time.
allowed-tools: Read, Grep, Glob

Where the first credential comes from

There is a circularity at the start of every workload identity: to receive a credential securely the workload must already prove who it is. A pre-shared secret resolves it by making possession the proof, which means everyone who can read the image is the agent. Attestation resolves it by having the platform sign what only the platform knows.

When to use this

Reviewing how any agent, job or pod authenticates for the first time. The answer decides whether every later identity control rests on something or on a copied file.

Procedure

1 — Find the first credential. Trace back from a downstream call to where the credential entered the process: an environment variable, a mounted file, a secrets manager fetch, or an attestation exchange.

2 — Ask who else can read it. For a secret, that set is the set of people who are currently the agent. Enumerate it — image layers, CI logs, anyone with exec on the namespace, anyone who can read the manifest.

3 — Test the unattested process. A process that is not what the platform started should receive nothing. If it receives a credential, the exchange is authenticating a claim rather than a workload.

4 — Test the unregistered-but-genuine image. A real workload nobody registered is the case people forget: attestation proves what is running, and registration decides whether it should be. Both must refuse.

5 — Test binding and lifetime. Present a legitimately issued credential from a different node, and again after its TTL. Both must fail. A credential that travels is a secret with extra steps.

Example

Input — the fixture committed at the top of scripts/workload_attestation_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:

   proc-1        spiffe://corp/reports-agent     issued
   proc-2        -                               image is not registered to any identity
   proc-stolen   -                               no attestation - unattested process

   presented from n-7 at t=1100: ok (accepted)
   presented from n-9 at t=1100: REFUSED (bound to n-7)
   presented from n-7 at t=2000: REFUSED (expired)

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

{
  "source": "env|file|manager|attestation",
  "readable_by": ["str"],
  "probes": {"unattested": "issued|refused", "unregistered_image": "issued|refused",
             "wrong_node": "accepted|refused", "expired": "accepted|refused"},
  "properties": {"non_copyable": false, "short_lived": false, "bound": false},
  "ttl_seconds": 0
}

All three properties, or the credential is a secret: non-copyable because it describes a running process, short-lived so theft has a deadline, bound so presenting it elsewhere fails.

Failure modes

  • Accepting a secrets manager as attestation. It answers "who may fetch this", which is the same circularity one layer down.
  • Testing only the unattested case. The unregistered genuine workload is the one that gets through.
  • Recording a long TTL as acceptable because rotation exists. Rotation is not a deadline for a copy already taken.

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 B2.2

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

Then pick b2-2-bootstrapping-the-first-credential:

agentinstall just for this agentstart it
Claude Codepython3 scripts/install_skills.py --tool claude --lessons B2.2type /b2-2-bootstrapping-the-first-credential
GitHub Copilot (VS Code)python3 scripts/install_skills.py --tool copilot --lessons B2.2type /b2-2-bootstrapping-the-first-credential in Chat
Cursorpython3 scripts/install_skills.py --tool cursor --lessons B2.2type / in Agent chat and search for it
Codexpython3 scripts/install_skills.py --tool agents --lessons B2.2type $b2-2-bootstrapping-the-first-credential, 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 B2.2

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 B2.2 --out work/cybertravels
python3 scripts/checkpoint.py --at B2.2 --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

An unattested process receives no credential, a genuine but unregistered image receives none either, and a credential issued to a real workload is refused when presented from another node or after its five-minute expiry.

✎Your turn

Find where one of your agents gets its first credential. If the answer is an environment variable or a mounted file, list everyone who can read it — that is the set of people who are currently that agent.

Tools used

SPIFFE/SPIRE

Skill source: skills/identity/workload-attestation-check

← PreviousB2.1 · Agent identity — user, workload, agent