Skip to content

Commit 9bcc418

Browse files
IlyaasKclaude
andcommitted
fix: don't misroute telemetry/events to the browser VM
The direct-to-VM routing allowlist matched on the subresource SEGMENT ("telemetry"), so the new historical GET /browsers/{id}/telemetry/events endpoint (served by the control plane from S2) was routed to the session VM — which only serves the live telemetry/stream SSE — once a session was route-cached, yielding failures / wrong data. Fix the granularity: - Allowlist entries are now full path-prefixes ("curl", "telemetry/stream"). - Matching is segment-boundary aware: "telemetry/stream" matches "telemetry/stream[/...]" but NOT "telemetry/events" or "telemetry/streamfoo". - Safe by default: any path not in the allowlist (including future browser sub-endpoints) goes to the control plane — slower, never misrouted. All in src/kernel/lib/ (Stainless-preserved), so durable across regens. Adds a regression vector and updates the default-config assertion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4506e68 commit 9bcc418

2 files changed

Lines changed: 37 additions & 3 deletions

File tree

src/kernel/lib/browser_routing/routing.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ class BrowserRoutingConfig:
4141
def browser_routing_config_from_env() -> BrowserRoutingConfig:
4242
raw = os.environ.get("KERNEL_BROWSER_ROUTING_SUBRESOURCES")
4343
if raw is None:
44-
return BrowserRoutingConfig(subresources=("curl", "telemetry"))
44+
# Path prefixes eligible for direct-to-VM routing. "telemetry/stream" is
45+
# the live SSE endpoint (VM); "telemetry/events" is a historical read
46+
# served by the control plane (S2) and must NOT be here.
47+
return BrowserRoutingConfig(subresources=("curl", "telemetry/stream"))
4548
if raw.strip() == "":
4649
return BrowserRoutingConfig()
4750

@@ -188,6 +191,21 @@ def _session_id_from_browser_pool_release_request(request: httpx.Request, path:
188191
return normalized or None
189192

190193

194+
def _matches_direct_vm_prefix(tail: str, prefixes: tuple[str, ...]) -> bool:
195+
"""Whether tail (the path after browsers/{id}/) is covered by an allow prefix,
196+
matching on segment boundaries: "telemetry/stream" matches "telemetry/stream"
197+
and "telemetry/stream/...", but not "telemetry/events" or "telemetry/streamfoo".
198+
Keeps historical control-plane reads (e.g. telemetry/events, served from S2)
199+
off the VM.
200+
"""
201+
tail = tail.strip("/")
202+
for prefix in prefixes:
203+
prefix = prefix.strip("/")
204+
if prefix and (tail == prefix or tail.startswith(prefix + "/")):
205+
return True
206+
return False
207+
208+
191209
def rewrite_direct_vm_options(
192210
options: FinalRequestOptions,
193211
*,
@@ -199,7 +217,7 @@ def rewrite_direct_vm_options(
199217
return options
200218

201219
session_id, subresource, suffix = match
202-
if subresource not in set(config.subresources):
220+
if not _matches_direct_vm_prefix(f"{subresource}{suffix}", config.subresources):
203221
return options
204222

205223
route = cache.get(session_id)

tests/test_browser_routing.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,23 @@ def test_browser_route_from_browser_requires_base_url_and_jwt() -> None:
337337

338338
def test_browser_routing_config_from_env_defaults_to_curl(monkeypatch: pytest.MonkeyPatch) -> None:
339339
monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
340-
assert browser_routing_config_from_env().subresources == ("curl", "telemetry")
340+
assert browser_routing_config_from_env().subresources == ("curl", "telemetry/stream")
341+
342+
343+
def test_direct_vm_routing_allowlist_segment_boundary() -> None:
344+
# Pins the fix: telemetry/stream (live SSE) routes to the VM; telemetry/events
345+
# (historical, served by the control plane from S2) does NOT; and a
346+
# stream-prefixed-but-different path is not matched.
347+
from kernel.lib.browser_routing.routing import _matches_direct_vm_prefix
348+
349+
prefixes = ("curl", "telemetry/stream")
350+
assert _matches_direct_vm_prefix("telemetry/stream", prefixes) is True
351+
assert _matches_direct_vm_prefix("telemetry/stream/x", prefixes) is True
352+
assert _matches_direct_vm_prefix("telemetry/events", prefixes) is False
353+
assert _matches_direct_vm_prefix("telemetry/streaming-config", prefixes) is False
354+
assert _matches_direct_vm_prefix("telemetry", prefixes) is False
355+
assert _matches_direct_vm_prefix("curl/raw", prefixes) is True
356+
assert _matches_direct_vm_prefix("fs/read", prefixes) is False
341357

342358

343359
def test_browser_routing_config_from_env_empty_string_disables_routing(monkeypatch: pytest.MonkeyPatch) -> None:

0 commit comments

Comments
 (0)