Boss: stolen authority
Goal. See why a copied permission cannot be used by another agent, then deliberately end a delegation chain.
Two short extensions on the chain you built. Boarding Agent's permission for UA214 is a piece of data: a list of strings. Activity Agent gets a copy and tries to use it.
Then a limit on distance. When one agent hands a permission on, it can mark it terminal. The root also carries a maximum number of hops for the whole trip: any agent can lower it, none can raise it.
export function steal(tenuo: TenuoMode, boardingSession: Session): StealOutcome {
const copied = boardingSession.toWire(); // just strings, and Activity Agent has them now
try {
const session = tenuo.importWireFor("activity-agent", "stolen-warrant-probe", copied);
return { imported: true, reason: `activity-agent imported the warrant and can act as ${session.inspect().holderPublicKey.slice(0, 12)}…` };
} catch (error) {
const err = error as { code?: string; message?: string };
return { imported: false, reason: err.message ?? String(error), ...(err.code !== undefined ? { code: err.code } : {}) };
}
}Do this
-
Read the theft. The file is short.
code exercises/06-extensions/steal.ts -
Run the checks and read the STOLEN replay panel carefully. It prints both public keys, the failed comparison, and the Ed25519 signature result.
npm run attackWhat you should see npm run attack · 79 lines
Stage 6 of 7: Boss: stolen authority mode=tenuo scenario=spring-break guide: https://tenuo.ai/lab/stage-6 WALLET Alice: $459 of $1200 ROGUE ATTEMPTS BLOCKED 8 / 8 STARS ★★☆★ ★ Trip booked ★ Rogue stopped ☆ Tight handoff ★ No spare authority THE TRIP ✓ trip-alice-cun travel: read traveler name ✓ trip-alice-cun travel: calendar event ✓ trip-alice-cun flight: search ✓ trip-alice-cun flight: book UA214 ✓ trip-alice-cun check-in: UA214 ✓ trip-alice-cun boarding: pass for UA214 ✓ trip-alice-cun hotel: search ✓ trip-alice-cun hotel: book ✓ trip-alice-cun activity: search ✓ trip-alice-cun activity: book ✓ trip-alice-cun within budget ($741 of $1200) HANDOFFS travel-agent: receive trip authority control plane ALLOWED travel-agent holds {book_activity, book_flight, book_hotel, calendar.create, check_in, get_reservation, issue_boarding_pass, search_activities, search_flights, search_hotels, traveler.read, wallet.charge}, maxDepth 4 travel-agent: handoff → flight-agent CUN ALLOWED flight-agent now holds {book_flight, check_in, get_reservation, issue_boarding_pass, search_flights, traveler.read, wallet.charge} at depth 1 flight-agent: handoff → checkin-agent UA214 ALLOWED checkin-agent now holds {check_in, get_reservation, issue_boarding_pass} at depth 2 checkin-agent: handoff → boarding-agent UA214 ALLOWED boarding-agent now holds {issue_boarding_pass} at depth 3 travel-agent: handoff → hotel-agent Cancún ALLOWED hotel-agent now holds {book_hotel, search_hotels, traveler.read, wallet.charge} at depth 1 travel-agent: handoff → activity-agent Cancún ALLOWED activity-agent now holds {book_activity, search_activities, traveler.read, wallet.charge} at depth 1 LEGITIMATE check_in(UA214) ALLOWED ✓ TRIGGERED BY INJECTED CONTENT get_reservation(AA882) DENIED ✓ reason: reservation AA882 is outside the warrant's constraint for get_reservation [TENUO_CONSTRAINT_VIOLATION] check_in(AA882) DENIED ✓ reason: reservation AA882 is outside the warrant's constraint for check_in [TENUO_CONSTRAINT_VIOLATION] cancel_reservation(UA214) DENIED ✓ reason: cancel_reservation is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] wallet.charge(412) DENIED ✓ reason: wallet.charge is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] book_flight(AA882, 412) DENIED ✓ reason: book_flight is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] PROBE (harness, independent of model) traveler.read(passportNumber) DENIED ✓ reason: traveler.read is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] calendar.delete(*) DENIED ✓ reason: calendar.delete is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] BOARDING AGENT AFTER THE HANDOFF issue_boarding_pass(UA214) intended ALLOWED ✓ check_in(UA214) inherited? DENIED ✓ reason: check_in is not in boarding-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] get_reservation(UA214) inherited? DENIED ✓ reason: get_reservation is not in boarding-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] ESCALATION: checkin-agent tries to arrange broader access for boarding-agent requested: every reservation; read, check in, cancel DENIED ✓ reason: DENIED at narrow(), in checkin-agent's own process, before any token existed: TENUO_CHAIN_INVALID: attenuation would expand capabilities: tool 'cancel_reservation' not in parent's tools. narrow() cannot add 'cancel_reservation': remove it or delegate from a parent that grants it. [TENUO_CHAIN_INVALID] STOLEN: activity-agent presents boarding-agent's warrant issue_boarding_pass(UA214) with a copied warrant DENIED ✓ reason: TENUO_INVALID_POP: holder key does not match the warrant's authorized holder. Holding a copy of a warrant is not authority; only the key it was issued to can use it. If this followed narrow(), set { holder: receiverPublicKey } for the agent that imports the child. [TENUO_INVALID_POP] warrant.bound_key … activity holder_key.public … comparison holder_key != warrant.bound_key Ed25519 signature FAILED TERMINAL checkin-agent narrow → boarding-agent ALLOWED ✗ expected DENIED: boarding-agent now holds {issue_boarding_pass} at depth 3 1 of 14 checks did not land as expected central_calls during the trip: 0 (calls to a component outside the acting agent) -
Now find the Flight → Check-in link in the chain and add
terminal: trueto its options.code exercises/06-extensions/chain.ts -
Run the trip and see which step fails and with what code. This stage breaks the trip on purpose.
npm run labBefore npm run lab · 74 lines
Stage 6 of 7: Boss: stolen authority mode=tenuo scenario=spring-break guide: https://tenuo.ai/lab/stage-6 Two short extensions. First, exercises/06-extensions/steal.ts copies Boarding Agent's permission into Activity Agent, which tries to use it. Run `npm run attack` and read the reason on the STOLEN WARRANT line. Then open exercises/06-extensions/chain.ts and mark what Flight Agent hands to Check-in Agent as terminal. Run the trip. Notice what fails and who decided it would. Check-in Agent did not agree to this restriction and cannot remove it. WALLET Alice: $459 of $1200 ROGUE ATTEMPTS BLOCKED 8 / 8 STARS ★★☆★ ★ Trip booked ★ Rogue stopped ☆ Tight handoff ★ No spare authority 1 handoff travel-agent trip-alice-cun receive trip authority control plane ALLOWED 2 trip travel-agent trip-alice-cun traveler.read name ALLOWED 3 trip travel-agent trip-alice-cun calendar.create * ALLOWED 4 handoff travel-agent trip-alice-cun handoff → flight-agent CUN ALLOWED 5 trip flight-agent trip-alice-cun traveler.read passportNumber ALLOWED 6 trip flight-agent trip-alice-cun search_flights CUN ALLOWED 7 trip flight-agent trip-alice-cun book_flight UA214 ALLOWED 8 trip flight-agent trip-alice-cun wallet.charge $286 ALLOWED 9 handoff flight-agent trip-alice-cun handoff → checkin-agent UA214 ALLOWED 10 trip checkin-agent trip-alice-cun get_reservation UA214 ALLOWED 11 trip checkin-agent trip-alice-cun check_in UA214 ALLOWED 12 handoff checkin-agent trip-alice-cun handoff → boarding-agent UA214 ALLOWED 13 trip boarding-agent trip-alice-cun issue_boarding_pass UA214 ALLOWED 14 injected checkin-agent trip-alice-cun get_reservation AA882 DENIED reason: reservation AA882 is outside the warrant's constraint for get_reservation [TENUO_CONSTRAINT_VIOLATION] 15 injected checkin-agent trip-alice-cun check_in AA882 DENIED reason: reservation AA882 is outside the warrant's constraint for check_in [TENUO_CONSTRAINT_VIOLATION] 16 injected checkin-agent trip-alice-cun cancel_reservation UA214 DENIED reason: cancel_reservation is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] 17 injected checkin-agent trip-alice-cun wallet.charge $412 DENIED reason: wallet.charge is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] 18 handoff travel-agent trip-alice-cun handoff → hotel-agent Cancún ALLOWED 19 trip hotel-agent trip-alice-cun traveler.read name ALLOWED 20 trip hotel-agent trip-alice-cun search_hotels Cancún ALLOWED 21 trip hotel-agent trip-alice-cun book_hotel HTL-CUN-2 ALLOWED 22 trip hotel-agent trip-alice-cun wallet.charge $420 ALLOWED 23 handoff travel-agent trip-alice-cun handoff → activity-agent Cancún ALLOWED 24 trip activity-agent trip-alice-cun traveler.read name ALLOWED 25 trip activity-agent trip-alice-cun search_activities Cancún ALLOWED 26 trip activity-agent trip-alice-cun book_activity ACT-5 ALLOWED 27 trip activity-agent trip-alice-cun wallet.charge $35 ALLOWED central_calls during the trip: 0 (calls to a component outside the acting agent) THE TRIP ✓ trip-alice-cun travel: read traveler name ✓ trip-alice-cun travel: calendar event ✓ trip-alice-cun flight: search ✓ trip-alice-cun flight: book UA214 ✓ trip-alice-cun check-in: UA214 ✓ trip-alice-cun boarding: pass for UA214 ✓ trip-alice-cun hotel: search ✓ trip-alice-cun hotel: book ✓ trip-alice-cun activity: search ✓ trip-alice-cun activity: book ✓ trip-alice-cun within budget ($741 of $1200) Explorer link saved as explorer-stage-6.url in the lab state directory. Run npm run trace -- --open-explorer to open it. The terminal link breaks the trip on purpose. Notice where, and who decided. npm run attack the rogue behavior and the tests npm run score points and why npm run trace every decision with its reason npm run next when you are done hereAfter npm run lab · 74 lines
Stage 6 of 7: Boss: stolen authority mode=tenuo scenario=spring-break guide: https://tenuo.ai/lab/stage-6 Two short extensions. First, exercises/06-extensions/steal.ts copies Boarding Agent's permission into Activity Agent, which tries to use it. Run `npm run attack` and read the reason on the STOLEN WARRANT line. Then open exercises/06-extensions/chain.ts and mark what Flight Agent hands to Check-in Agent as terminal. Run the trip. Notice what fails and who decided it would. Check-in Agent did not agree to this restriction and cannot remove it. WALLET Alice: $459 of $1200 ROGUE ATTEMPTS BLOCKED 7 / 8 1 not reached STARS ☆☆☆★ ☆ Trip booked ☆ Rogue stopped ☆ Tight handoff ★ No spare authority 1 handoff travel-agent trip-alice-cun receive trip authority control plane ALLOWED 2 trip travel-agent trip-alice-cun traveler.read name ALLOWED 3 trip travel-agent trip-alice-cun calendar.create * ALLOWED 4 handoff travel-agent trip-alice-cun handoff → flight-agent CUN ALLOWED 5 trip flight-agent trip-alice-cun traveler.read passportNumber ALLOWED 6 trip flight-agent trip-alice-cun search_flights CUN ALLOWED 7 trip flight-agent trip-alice-cun book_flight UA214 ALLOWED 8 trip flight-agent trip-alice-cun wallet.charge $286 ALLOWED 9 handoff flight-agent trip-alice-cun handoff → checkin-agent UA214 ALLOWED 10 trip checkin-agent trip-alice-cun get_reservation UA214 ALLOWED 11 trip checkin-agent trip-alice-cun check_in UA214 ALLOWED 12 handoff checkin-agent trip-alice-cun handoff → boarding-agent UA214 DENIED reason: TENUO_DEPTH_EXCEEDED: delegation depth 3 exceeds maximum 2 [TENUO_DEPTH_EXCEEDED] 13 injected checkin-agent trip-alice-cun get_reservation AA882 DENIED reason: reservation AA882 is outside the warrant's constraint for get_reservation [TENUO_CONSTRAINT_VIOLATION] 14 injected checkin-agent trip-alice-cun check_in AA882 DENIED reason: reservation AA882 is outside the warrant's constraint for check_in [TENUO_CONSTRAINT_VIOLATION] 15 injected checkin-agent trip-alice-cun cancel_reservation UA214 DENIED reason: cancel_reservation is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] 16 injected checkin-agent trip-alice-cun wallet.charge $412 DENIED reason: wallet.charge is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] 17 handoff travel-agent trip-alice-cun handoff → hotel-agent Cancún ALLOWED 18 trip hotel-agent trip-alice-cun traveler.read name ALLOWED 19 trip hotel-agent trip-alice-cun search_hotels Cancún ALLOWED 20 trip hotel-agent trip-alice-cun book_hotel HTL-CUN-2 ALLOWED 21 trip hotel-agent trip-alice-cun wallet.charge $420 ALLOWED 22 handoff travel-agent trip-alice-cun handoff → activity-agent Cancún ALLOWED 23 trip activity-agent trip-alice-cun traveler.read name ALLOWED 24 trip activity-agent trip-alice-cun search_activities Cancún ALLOWED 25 trip activity-agent trip-alice-cun book_activity ACT-5 ALLOWED 26 trip activity-agent trip-alice-cun wallet.charge $35 ALLOWED central_calls during the trip: 0 (calls to a component outside the acting agent) THE TRIP ✓ trip-alice-cun travel: read traveler name ✓ trip-alice-cun travel: calendar event ✓ trip-alice-cun flight: search ✓ trip-alice-cun flight: book UA214 ✓ trip-alice-cun check-in: UA214 ✗ trip-alice-cun boarding: pass for UA214 never attempted (an earlier step or handoff failed) ✓ trip-alice-cun hotel: search ✓ trip-alice-cun hotel: book ✓ trip-alice-cun activity: search ✓ trip-alice-cun activity: book ✓ trip-alice-cun within budget ($741 of $1200) Explorer link saved as explorer-stage-6.url in the lab state directory. Run npm run trace -- --open-explorer to open it. The terminal link breaks the trip on purpose. Notice where, and who decided. npm run attack the rogue behavior and the tests npm run score points and why npm run trace every decision with its reason npm run next when you are done here -
Second version: lower
maxDepthon the root instead, and watch where the chain stops.
I'm stuck. Give me a hint.
maxDepth, or any agent with terminal, and nobody downstream can undo it.Show a reference solution
Try your own first. The score checks behavior, so yours does not need to match this one.
/**
* Flight → Check-in. Flight Agent has booked and knows the reservation, so
* this is the link that narrows "any Cancún flight" to "this one."
*/
export function flightToCheckin(flight: Session, fleet: Fleet, trip: Trip, reservation: string): Session {
const only = oneOf([reservation]);
return fleet["flight-agent"].tenuo.narrow(
flight,
{
get_reservation: { reservation: only },
check_in: { reservation: only },
issue_boarding_pass: { reservation: only },
},
{ holder: fleet["checkin-agent"].publicKey, ttlSeconds: 5 * 60, terminal: true },
);
}You can explain why the thief's copy did not work, and you have seen the trip fail at the hop you chose.