server/src/computer/routes.ts:310 checks :kind against the four gestures a person's mouse and keyboard produce, and then builds the call to the gateway as:
await gateway.humanInput(context.req.param("botId"), {
kind,
...(body ?? {}),
} as Parameters<typeof gateway.humanInput>[1]),
The spread comes second, so a body carrying its own kind replaces the value that was just checked. humanInput (gateway.ts:625) puts that value straight into the path it calls:
const { kind, ...payload } = input;
return post<HumanInputResult>(botId, `/human/${kind}`, payload);
and the transport concatenates it onto the computer's address (client.ts:129), so URL parsing inside fetch resolves the .. away.
Reproduced
Against a listening server standing in for agent-computer, driving the real router through the real gateway and transport at main 37b576c. The paths below are what the server received:
POST to /bot-1/human/click with body |
arrived at |
{"x":10,"y":20} |
/human/click |
{"kind":"../computers/reset"} |
/computers/reset |
{"kind":"../exec","command":"cat /workspace/notes"} |
/exec, body {"command":"cat /workspace/notes"} |
{"kind":"../../health"} |
/health |
client.ts:134 attaches x-openbot-computer-token to everything the transport sends, so each of those arrives authenticated. /computers/reset wipes the profile and every login in it; /exec runs a command on the Bot's computer.
Why this route in particular
Three defences are stepped around rather than defeated, because the request never enters the paths that carry them:
- The policy decision and the audit row. This route skips both deliberately (
routes.ts:305-308) and correctly: a takeover exists so a person can enter a password, and recording keystrokes would defeat the point. It does mean a redirected call leaves nothing behind that says where it went.
- The computer's own
humanMayDrive() check (agent-computer/src/index.ts:624) is keyed to the four /human/* paths. A request routed elsewhere never reaches it, so the takeover requirement does not apply to it.
Who can reach it
The only gate is the Bot-access middleware every route under /:botId/* has (routes.ts:59). There is no requireAdmin. Since canRunAgent is canAccessAgent, that is any signed-in person who may act as the Bot, and for a Bot whose visibility is public that is everyone in the deployment.
Test plan
A PR follows.
server/src/computer/routes.ts:310checks:kindagainst the four gestures a person's mouse and keyboard produce, and then builds the call to the gateway as:The spread comes second, so a body carrying its own
kindreplaces the value that was just checked.humanInput(gateway.ts:625) puts that value straight into the path it calls:and the transport concatenates it onto the computer's address (
client.ts:129), so URL parsing insidefetchresolves the..away.Reproduced
Against a listening server standing in for
agent-computer, driving the real router through the real gateway and transport atmain37b576c. The paths below are what the server received:/bot-1/human/clickwith body{"x":10,"y":20}/human/click{"kind":"../computers/reset"}/computers/reset{"kind":"../exec","command":"cat /workspace/notes"}/exec, body{"command":"cat /workspace/notes"}{"kind":"../../health"}/healthclient.ts:134attachesx-openbot-computer-tokento everything the transport sends, so each of those arrives authenticated./computers/resetwipes the profile and every login in it;/execruns a command on the Bot's computer.Why this route in particular
Three defences are stepped around rather than defeated, because the request never enters the paths that carry them:
routes.ts:305-308) and correctly: a takeover exists so a person can enter a password, and recording keystrokes would defeat the point. It does mean a redirected call leaves nothing behind that says where it went.humanMayDrive()check (agent-computer/src/index.ts:624) is keyed to the four/human/*paths. A request routed elsewhere never reaches it, so the takeover requirement does not apply to it.Who can reach it
The only gate is the Bot-access middleware every route under
/:botId/*has (routes.ts:59). There is norequireAdmin. SincecanRunAgentiscanAccessAgent, that is any signed-in person who may act as the Bot, and for a Bot whose visibility ispublicthat is everyone in the deployment.Test plan
{"kind":"../computers/reset"}to/human/clickfor a Bot you can access. Expect the profile to survive.{"kind":"../exec","command":"id"}. Expect nothing to run.A PR follows.