· about 35 minutes · runs on ubuntu-26.04-claude · unrated
An unrated lab. It runs on your machine with no account and no network, and everything about it — the faults, the checks, the hints and the reference solution — is in the repository. An attempt on it is recorded on your profile and never moves a rating: only rated labs do. Rated and unrated labs.
The weekly fleet report, ~/bin/inventory-report, has Claude Code, in ~/inventory-report, ask
the inventory MCP server at http://127.0.0.1:8931/mcp for the host list. The server wants a
bearer token signed by this machine's identity provider, lab-idp; the job reads its token from
~/.config/inventory/env. The server's settings are in /etc/inventory-mcp/config.json, and
sudo inventory-mcp restart makes it read them again.
The report works. An audit found out why: the job's token is a copy of the billing sync's, and the inventory server takes it — along with any other token the provider ever signed, expired or not. One leaked token opens every service that trusts the provider.
This lab runs in a container, as learner with sudo. claude here is the real Claude Code
2.1.270, talking to a scripted model on this machine; it connects to the server and calls it for
real. The job's JSON result is ~/inventory-report.json.
What is expected, and graded — the grader mints tokens of its own with lab-idp:
This lab runs in a container — Docker or Podman, no VM — so there is no boot to survive: the checks run once, against the state you left.
| Check | Objective |
|---|---|
| 01_a_token_for_another_service_is_refused | Make an MCP server accept only tokens issued for it |
| 02_an_expired_token_is_refused | Make an MCP server accept only tokens issued for it |
| 03_the_job_uses_its_own_token | Give an MCP client a token for the server it talks to |
Where the lab's hints send you, level by level, as you ask for them (h, then l opens a journal section in the TUI).
3 questions on the same topic, in the lab's Theory tab. They never affect the lab's grade. Here they are, to answer in place:
An MCP server verifies that bearer tokens are signed by the company's identity provider and not expired, and nothing else. What can an attacker who stole the billing API's token do?
Call the MCP server with it, because the server never checks which service the token was issued for
Every service trusting the issuer accepts its signature; only the audience (aud) says which service a token is for. The MCP authorization specification requires servers to accept only tokens issued for themselves, so that one leaked token does not open every service.
https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization · https://www.rfc-editor.org/rfc/rfc8707
What does this print — the audience of a JWT, read without its signing key?
import base64, json
claims = {"iss": "https://idp.lab", "aud": "https://billing.example/api", "sub": "sync"}
part = base64.urlsafe_b64encode(json.dumps(claims).encode()).rstrip(b"=").decode()
token = "eyJhbGciOiJIUzI1NiJ9." + part + ".c2ln"
payload = token.split(".")[1]
print(json.loads(base64.urlsafe_b64decode(payload + "=" * (-len(payload) % 4)))["aud"])
https://billing.example/api
A JWT's claims are base64url-encoded JSON, signed but not encrypted: anyone holding the token can read them. That is how a client or an operator checks which service a token was issued for — and why a token's claims must never hold secrets.
https://www.rfc-editor.org/rfc/rfc9068 · executed in a sandbox
A Claude Code job's remote MCP server starts answering 401 to its token. What does the job's model see?
None of the server's tools: the server is not connected, so calling one reports no such tool
A 401 at connection time makes Claude Code treat the server as needing authentication; it does not connect and offers the model none of its tools. The reason is in the server's WWW-Authenticate header and its log, not in anything the model receives.
https://code.claude.com/docs/en/mcp