Stage 7 of 7 · optional boss level · tenuo · about 20 min
Boss: contain the incident
Goal. Contain a compromised Hotel Agent while legitimate bookings keep working.
This stage gives less guidance than the others. The compromised Hotel Agent will try eight things.
1. book the approved Cancún hotel must work
2. book a hotel in Tulum instead must fail
3. book the approved hotel at $320 a night must fail
4. read Alice's passport number must fail
5. book a flight must fail
6. delete the trip's calendar event must fail
7. hand wallet access to Activity Agent must fail
8. import a permission copied from Flight Agent must fail
Do this
-
Open the chain. The Travel → Hotel link is where the incident lives: right now it hands Hotel Agent far more than a hotel booking needs.
code exercises/07-incident/chain.ts -
Run the checks. All eight attempts are listed under INCIDENT. Make one land and seven fail, and keep an eye on the least-privilege row of your score.
npm run attackAs it ships npm run attack · 58 lines
Stage 7 of 7: Boss: contain the incident mode=tenuo scenario=incident guide: https://tenuo.ai/lab/stage-7 WALLET Alice: $459 of $1200 ROGUE ATTEMPTS BLOCKED 3 / 5 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_flight, book_hotel, search_flights, 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 INCIDENT 1. book the approved Cancún hotel ALLOWED ✓ 2. book a hotel in Tulum DENIED ✓ reason: city Tulum is outside the warrant's constraint for book_hotel [TENUO_CONSTRAINT_VIOLATION] 3. book the approved hotel at $320 a night DENIED ✓ reason: nightlyRate 320 is outside the warrant's constraint for book_hotel [TENUO_CONSTRAINT_VIOLATION] 4. read Alice's passport number ALLOWED ✗ expected DENIED: warrant tnu_wrt_… permits traveler.read 5. book a flight ALLOWED ✗ expected DENIED: warrant tnu_wrt_… permits book_flight 6. delete the trip's calendar event DENIED ✓ reason: calendar.delete is not in hotel-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] 7. hand wallet access to Activity Agent ALLOWED ✗ expected DENIED: hotel-agent delegated wallet.charge to activity-agent 8. import a warrant copied from Flight Agent 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] 3 of 8 checks did not land as expected central_calls during the trip: 0 (calls to a component outside the acting agent)When it is fixed npm run attack · 58 lines
Stage 7 of 7: Boss: contain the incident mode=tenuo scenario=incident guide: https://tenuo.ai/lab/stage-7 WALLET Alice: $459 of $1200 ROGUE ATTEMPTS BLOCKED 5 / 5 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, terminal travel-agent: handoff → activity-agent Cancún ALLOWED activity-agent now holds {book_activity, search_activities, traveler.read, wallet.charge} at depth 1 INCIDENT 1. book the approved Cancún hotel ALLOWED ✓ 2. book a hotel in Tulum DENIED ✓ reason: city Tulum is outside the warrant's constraint for book_hotel [TENUO_CONSTRAINT_VIOLATION] 3. book the approved hotel at $320 a night DENIED ✓ reason: nightlyRate 320 is outside the warrant's constraint for book_hotel [TENUO_CONSTRAINT_VIOLATION] 4. read Alice's passport number DENIED ✓ reason: field passportNumber is outside the warrant's constraint for traveler.read [TENUO_CONSTRAINT_VIOLATION] 5. book a flight DENIED ✓ reason: book_flight is not in hotel-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] 6. delete the trip's calendar event DENIED ✓ reason: calendar.delete is not in hotel-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] 7. hand wallet access to Activity Agent DENIED ✓ reason: TENUO_DEPTH_EXCEEDED: delegation depth 2 exceeds maximum 1 [TENUO_DEPTH_EXCEEDED] 8. import a warrant copied from Flight Agent 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] clean: all 8 checks landed as expected central_calls during the trip: 0 (calls to a component outside the acting agent) -
Check the score. Blocking attempt 3 the same way you blocked attempt 2 costs points.
npm run scoreFull marks npm run score · 19 lines
Stage 7 of 7: Boss: contain the incident mode=tenuo scenario=incident guide: https://tenuo.ai/lab/stage-7 WALLET Alice: $459 of $1200 ROGUE ATTEMPTS BLOCKED 5 / 5 STARS ★★★★ ★ Trip booked ★ Rogue stopped ★ Tight handoff ★ No spare authority Trip booked 25 / 25 Rogue stopped 30 / 30 5 of 5 checks Tight handoff 25 / 25 2 of 2 checks No spare authority 20 / 20 0 findings, capped at -5 per agent 100 / 100 Stage 7 done.
I'm stuck. Give me a hint.
Only hotel tools, only Cancún, only the approved nightly rate, only the traveler's name, only the hotel's share of the wallet, and
terminal: true so nothing can be handed on.Show a reference solution
Try your own first. The score checks behavior, so yours does not need to match this one.
export function travelToHotel(travel: Session, fleet: Fleet, trip: Trip): Session {
return fleet["travel-agent"].tenuo.narrow(
travel,
{
"traveler.read": { traveler: exact(trip.traveler), field: oneOf(["name"]) },
search_hotels: { city: exact(trip.city) },
book_hotel: {
hotelId: any(),
city: exact(trip.city),
nightlyRate: max(trip.hotelRateBudget),
nights: max(trip.nights),
guest: exact(trip.traveler),
taskId: exact(trip.taskId),
},
"wallet.charge": { taskId: exact(trip.taskId), amount: max(trip.hotelRateBudget * trip.nights) },
},
{ holder: fleet["hotel-agent"].publicKey, ttlSeconds: 10 * 60, terminal: true },
);
}Done when
One works, seven fail, and the least-privilege row is full marks.