Skip to content

Route computer and playwright calls directly to the VM - #157

Open
tnsardesai wants to merge 7 commits into
mainfrom
hypeship/tier1-direct-allowlist
Open

Route computer and playwright calls directly to the VM#157
tnsardesai wants to merge 7 commits into
mainfrom
hypeship/tier1-direct-allowlist

Conversation

@tnsardesai

@tnsardesai tnsardesai commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Default direct-to-VM routing now includes computer and playwright in addition to curl and telemetry/stream.

computer/screenshot and playwright/execute rewrite to {base_url}/... with the session JWT and drop Authorization. process/*, fs/*, and telemetry/events stay on the API origin.

KERNEL_BROWSER_ROUTING_SUBRESOURCES still overrides the default list. An empty value still disables routing.

Metro-api activity recording for /browser/kernel/* has been merged in kernel/kernel#3417. That was the hard gate for this allowlist flip. The kill switch is leaving base_url unset or setting KERNEL_BROWSER_ROUTING_SUBRESOURCES to empty.

Latency (eu-west)

Same hop skip as the Node/Go SDKs. Comparison script: https://gist.github.com/tnsardesai/6704dfc86e2c0785e03beaf274aa672d

Same eu-west session. Published SDK still hits api.onkernel.com; this allowlist rewrite hits metro-api.

creating eu-west browser…
session  rao32cls5rhduxg9n1a732i5
region   eu-west
base_url https://proxy.dub-unruffled-kowalevski.onkernel.com:8443/browser/kernel
cdp host proxy.dub-unruffled-kowalevski.onkernel.com:8443
rounds   warmup=2 timed=8

control plane (@onkernel/sdk@0.93.0)
    control-plane screenshot    498ms  api.onkernel.com
    control-plane screenshot    884ms  api.onkernel.com
    control-plane screenshot    647ms  api.onkernel.com
    control-plane screenshot    508ms  api.onkernel.com
    control-plane screenshot    614ms  api.onkernel.com
    control-plane screenshot    512ms  api.onkernel.com
    control-plane screenshot    509ms  api.onkernel.com
    control-plane screenshot    408ms  api.onkernel.com
    control-plane screenshot    622ms  api.onkernel.com
    control-plane screenshot    590ms  api.onkernel.com
  min 410ms  p50 513ms  p95 650ms  max 650ms

direct to VM (kernel-node-sdk#hypeship/tier1-direct-allowlist)
    direct-to-vm  screenshot    648ms  proxy.dub-unruffled-kowalevski.onkernel.com:8443
    direct-to-vm  screenshot    623ms  proxy.dub-unruffled-kowalevski.onkernel.com:8443
    direct-to-vm  screenshot    321ms  proxy.dub-unruffled-kowalevski.onkernel.com:8443
    direct-to-vm  screenshot    335ms  proxy.dub-unruffled-kowalevski.onkernel.com:8443
    direct-to-vm  screenshot    332ms  proxy.dub-unruffled-kowalevski.onkernel.com:8443
    direct-to-vm  screenshot    329ms  proxy.dub-unruffled-kowalevski.onkernel.com:8443
    direct-to-vm  screenshot    345ms  proxy.dub-unruffled-kowalevski.onkernel.com:8443
    direct-to-vm  screenshot    344ms  proxy.dub-unruffled-kowalevski.onkernel.com:8443
    direct-to-vm  screenshot    339ms  proxy.dub-unruffled-kowalevski.onkernel.com:8443
    direct-to-vm  screenshot    341ms  proxy.dub-unruffled-kowalevski.onkernel.com:8443
  min 324ms  p50 337ms  p95 347ms  max 347ms

p50 delta  control-plane 513ms  vs  direct 337ms  (-176ms)

Test plan

  • default allowlist includes curl, telemetry/stream, computer, playwright
  • computer screenshot and playwright execute rewrite to the VM and drop Authorization
  • process, fs, and telemetry/events stay on the API origin

Note

Medium Risk
Changes default request routing and retry/auth fallback for browser subresources, including JWT eviction on 401/403. Misrouting or retry bugs could send traffic to the wrong origin or leak/drop auth headers.

Overview
Default direct-to-VM routing now includes computer and playwright alongside curl and telemetry/stream. Screenshot and Playwright execute rewrite to the session base_url with JWT and drop Authorization. process, fs, and telemetry/events stay on the API origin. KERNEL_BROWSER_ROUTING_SUBRESOURCES still overrides; empty still disables.

On 401/403 from a JWT-query VM request, the client evicts the cached route and retries so the follow-up hits the control plane with the API key. Successful delete/release still evicts as before.

Reviewed by Cursor Bugbot for commit 728330d. Bugbot is set up for automated code reviews on this repo. Configure here.

Add computer and playwright to the default browser routing allowlist
so screenshot and execute traffic can skip the control plane once
metro-api records activity on the kernel proxy.
The default now includes computer and playwright, not just curl.
The asserted list can grow; the test name should not.
@tnsardesai
tnsardesai marked this pull request as ready for review August 20, 2026 23:06
sjmiller609
sjmiller609 previously approved these changes Aug 21, 2026
rgarcia
rgarcia previously approved these changes Aug 21, 2026

@rgarcia rgarcia 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.

QA’d commit 1b28d62 across sync and async clients.

  • Browser-routing suite: 26 passed; PR CI is green.
  • Focused experiments confirmed computer/batch, clipboard, screenshot, and Playwright calls route directly with the session JWT and without Authorization.
  • process/*, fs/*, and telemetry/events retain API-origin routing and API authorization.
  • Missing base_url, custom allowlists, and the empty-value kill switch behave correctly.
  • Retry rewriting is idempotent and preserves request parameters.

Two non-blocking notes:

  1. The computer prefix routes all computer/* endpoints, while the description and tests focus on screenshot/execute. Please consider one nested computer endpoint test.
  2. A stale session JWT produces a direct-VM 401 with no automatic cache refresh or control-plane fallback. A follow-up could evict the route on 401/403 or document client.browsers.get(id) as the refresh path.

No blocking findings.

tnsardesai and others added 2 commits August 21, 2026 10:35
A direct-to-VM 401/403 now evicts the cached route and retries the
original request against the API.
@tnsardesai

Copy link
Copy Markdown
Contributor Author

addressed the stale JWT note: a direct-VM 401/403 now evicts the cached route and retries the original request on the control plane. no get() refresh — if the jwt is stale the API path still works, and a later create/retrieve will recache.

@tnsardesai
tnsardesai dismissed stale reviews from sjmiller609 and rgarcia via f525a24 August 21, 2026 18:04

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f525a24. Configure here.

Comment thread src/kernel/lib/browser_routing/routing.py Outdated
Lint failed on the stale-JWT retry change.
A metro 401/403 with a jwt query param is enough to fall back to the
control plane, so concurrent requests still retry after the first
eviction.
@tnsardesai

Copy link
Copy Markdown
Contributor Author

addressed the concurrent stale-JWT race: retry now keys off the request jwt query param, not a still-present cache entry. first eviction no longer blocks other in-flight 401/403s from falling back to the control plane. same fallback is now in node #170 and go #164.

@tnsardesai
tnsardesai requested a review from rgarcia August 21, 2026 19:28

@rgarcia rgarcia 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.

The stale-JWT fallback still has the cache invalidation race now covered in the Node and Go reviews.

_session_id_from_direct_vm_response() identifies the current cached route by base URL, then cache.delete(session_id) removes it without checking whether its JWT is the one that failed. If request A leaves with jwt-old, another request refreshes the same base URL to jwt-new, and A later returns 401, the fresh jwt-new route is deleted. I reproduced this against head 728330d: after replacing the cached route before processing the old 401, cache.get("sess-1") returns None.

The latest change correctly fixes the separate race where concurrent stale 401s stopped retrying after the first eviction, but it does not protect a newer cache entry from an older response.

Please make eviction conditional on the current route’s base URL and JWT matching the failed request, and add a regression test that refreshes the route while the stale request is in flight and asserts the fresh route survives. The existing routing suite passes, but does not cover this interleaving.

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.

3 participants