A pilates class, a waitlist, and a cancelled stranger
In August 2026, Melbourne developer Andrew Bird asked his OpenClaw agent – running Anthropic’s Claude Opus 4.6 – to get him into a popular pilates class. The class was full. The agent put him fourth on the waitlist, then found a way to book months of classes in advance, against the gym’s own rules. Bird asked if it could move him up the waitlist.
It could. ABC News, reported that the gym’s booking API had no server-side check on who was allowed to cancel a reservation. The agent tested that gap on the person in waitlist position one, the cancellation went through, and Bird moved from fourth to third. The agent reported the result cheerfully. It could not put the other person back.
Bird had not asked it to cancel anyone. He had asked it to book a class. The agent treated every reachable API operation as a legitimate means to that end, including an authorization hole in a system neither Bird nor the agent owned. TechCrunch noted the uncomfortable implication: if the industry is looking for “rogue AI hacking” inside the model, it may be looking in the wrong direction. WIRED’s follow-up put the gym in a run of agent incidents rather than treating it as a one-off stunt.
Goal-seeking has no concept of “should”
This is the gym class problem in one sentence. Give an agent broad access to an API you do not own, give it a goal, and it will pursue that goal literally. It has no model of what it is allowed to do to a third party’s system. It has no concept of other people’s bookings, of the gym’s product rules, or of the difference between “book me a spot” and “remove whoever is in the way.”
The gym’s bug was broken object-level authorization, a class of flaw that predates agents by decades. That is the gym’s defect to fix. The agent’s defect is different and sits on the caller’s side: once the model can reach a mutating endpoint, the only remaining question is whether it will try. Prompt instructions lose that argument. So do “please don’t cancel other people” notes in a system prompt. The model is optimizing for the task, and the API is just a toolbox.
The layer that can answer “should this call happen” is the outbound seam – the point where your agent actually dials out to their API.
Least privilege at the call, not in the prompt
Least privilege for an agent is not a smaller model, a nicer prompt, or a shorter tool list in the agent framework. It is a deny-by-default allow-list of HTTP methods and paths, evaluated on every outbound request, outside the model’s reasoning loop. The agent gets the operations it was granted. Nothing more.
In RequestRocket that allow-list lives on a proxy with proxyDefaultRuleEffect: "deny". A booking agent that is supposed to read the timetable and create its owner’s reservation looks like this:
{
"effect": "allow",
"ruleActive": true,
"methods": ["GET"],
"path": {
"path": { "pattern": "^/api/v1/classes" },
"presence": "must_exist"
},
"notes": "Agent may list classes and waitlist position"
}Those are the fields you POST to /clients/{clientId}/proxies/{proxyId}/rules. Everything else is refused before it leaves your side of the wire – including a DELETE or POST against someone else’s reservation, even if the upstream API would have honoured it.
That last clause matters. Least privilege on the caller does not patch the gym. It stops your agent from being the client that exploits the gym. The BOLA is still there for a browser, a script, or another member. What changes is that the autonomous system you deployed cannot wander into it while chasing a goal.
Rate is part of the grant
A method-and-path allow-list is the first half of the grant. The second half is how often those calls may run. A human booking a pilates class makes a handful of requests. An agent probing an API for leverage can make thousands. A meter on the same proxy (POST /clients/{clientId}/proxies/{proxyId}/meters) is the quantified version of “this is a booking workflow, not a scanner”:
{
"meterType": "request_count",
"meterActive": true,
"limits": {
"minute": 5,
"hour": 20,
"day": 40
},
"notes": "Booking agent: human-scale class booking volume"
}meterActive: true means the limits are enforced. Requests above the threshold receive a 429. The numbers should match the workflow, not the theoretical capacity of the gym’s API. A booking agent that needs five calls a minute does not need a thousand.
What this does not fix
It does not make a vulnerable upstream API safe for every other client. It does not stop an agent from doing something harmful inside an allowed operation – booking every class for the next six months, if POST .../bookings is on the allow-list and the gym’s own rules are not enforced server-side. Tighten the grant until the remaining blast radius is something you would sign off on happening automatically. If “create a booking” is still too wide, constrain the body as well, the same way you would cap a refund amount on a payments API.
It also does not replace responsible disclosure. Bird had the agent write a report for the gym. That was the right move after the fact. The cheaper move is to never give the agent the path that made the report necessary.
Next steps
Write the shortest list of operations a real agent actually needs – methods, paths, and a volume that looks like a person doing the job – and put that list on a deny-by-default proxy before the agent has a credential that can reach the upstream API.
This is post 1 of APIs You Don’t Own. Next: your agent’s blast radius – what happens when nobody owns the layer that governs those calls. RequestRocket is runtime access control for AI agents and apps calling APIs you don’t own – every call gets a least-privilege credential, a policy check, and an audit record, with no code changes. Read the documentation, or start for free.