Skip to content

Validate timeoutMs on POST /computers/:botId/exec - #427

Merged
davidmckayv merged 2 commits into
CopilotKit:mainfrom
Ayush7614:fix/computer-exec-timeout-validation
Sep 8, 2026
Merged

Validate timeoutMs on POST /computers/:botId/exec#427
davidmckayv merged 2 commits into
CopilotKit:mainfrom
Ayush7614:fix/computer-exec-timeout-validation

Conversation

@Ayush7614

Copy link
Copy Markdown
Contributor

What

POST /:botId/exec in server/src/computer/routes.ts passed any numeric timeoutMs straight to gateway.runCommand, whose 615s transport backstop exists to outlast the shell rather than enforce the limit. A NaN, Infinity, negative, fractional, or ten-hour value travelled to the computer, where AbortSignal.timeout threw a RangeError 500 or the run outlasted the backstop the gateway documents.

This validates timeoutMs against the shell's own bounds (whole milliseconds, 1000..600000) and answers 400 { error } before the gateway decides and records — so the refusal is a caller error, not a failed action on the audit trail. Omitted timeouts still run with the computer default.

Why it matters

Reliability and cost: unbounded shell runs are the most expensive thing a Bot can start, and a malformed timeout previously surfaced as a 500 or a run that outlived its transport. Twelve route-level cases now pin the contract.

Verification

  • New suite server/tests/computer-exec-timeout.test.ts: valid 5000 reaches the gateway; absent timeout runs with default; NaN, Infinity, negatives, zero, 999, 600001, 36M, fractions, strings, and null each answer 400 without reaching the gateway.
  • bun test server/tests/computer-exec-timeout.test.ts — 12 pass.
  • bun run --filter server typecheck — clean. Biome format + lint — clean.

A NaN, Infinity, negative, fractional, or ten-hour timeout travelled to
the computer, where AbortSignal.timeout threw a RangeError 500 or the
run outlasted the 615s transport backstop the gateway documents. The
shell's real bounds are 1s to 600s.

Reject anything outside a whole number of milliseconds in 1000..600000
with 400 before the gateway decides and records, so the refusal is a
caller error rather than a failed action on the trail.

@davidmckayv davidmckayv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep-reviewed against live code (correctness, governance, no vendor/secret/scale issues). Composed build+tests green. CHANGELOG/format rebase on CI-validated substance.

@davidmckayv
davidmckayv merged commit 7b33ba6 into CopilotKit:main Sep 8, 2026
14 checks passed
davidmckayv added a commit that referenced this pull request Sep 9, 2026
`POST /:botId/scroll` and `POST /:botId/human/scroll` guarded `deltaY` with
`typeof value === "number"`, which is true of `Infinity`. `1e999` is valid JSON
and parses to exactly that, so the value passed the check, travelled to the
gateway, and was written by `JSON.stringify` as `null` on the hop to the Bot's
computer -- where `typeof body.deltaY === "number"` is now false and the
computer scrolls its own default instead (600 pixels for the tool path, 400 for
a person's). The caller got 200 and a scroll it did not ask for.

Every other number on this surface is already checked at the edge: `timeoutMs`
on `exec` is rejected unless it is a whole number in range (#427), and the
coordinates behind `human/click` are rejected unless `Number.isFinite` accepts
them. `deltaY` was the one that was not, so it is checked the same way, at the
same place, with the same 400.

Wrong types are refused rather than silently dropped, which is what `exec`
already does with a `timeoutMs` of `"3000"`.

Measured: with the routes unchanged and only the new test applied, 10 of 14
cases fail -- every rejection case on both paths answers 200 and reaches the
gateway. With the change, 14 pass.

Co-authored-by: David McKay <david@copilotkit.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants