From 81368eb302c1206b3a0625b009d4962525ce8cde Mon Sep 17 00:00:00 2001 From: tnsardesai <18272584+tnsardesai@users.noreply.github.com> Date: Tue, 18 Aug 2026 01:40:36 +0000 Subject: [PATCH 1/3] Route computer and playwright calls directly to the VM 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. --- browser_routing.go | 2 +- browser_routing_test.go | 167 ++++++++++++++++++++++++- lib/browserrouting/route_cache_test.go | 5 +- 3 files changed, 170 insertions(+), 4 deletions(-) diff --git a/browser_routing.go b/browser_routing.go index 34acc0a..a8ab1d4 100644 --- a/browser_routing.go +++ b/browser_routing.go @@ -86,7 +86,7 @@ func browserRoutingSubresourcesFromEnv() []string { // Path prefixes eligible for direct-to-VM routing. "telemetry/stream" is // the live SSE endpoint (served by the VM); "telemetry/events" is a // historical read served by the control plane (S2) and must NOT be here. - return []string{"curl", "telemetry/stream"} + return []string{"curl", "telemetry/stream", "computer", "playwright"} } if strings.TrimSpace(raw) == "" { return []string{} diff --git a/browser_routing_test.go b/browser_routing_test.go index c28a355..9bbefb1 100644 --- a/browser_routing_test.go +++ b/browser_routing_test.go @@ -266,8 +266,8 @@ func TestBrowserRoutingSubresourcesFromEnvDefaultsToCurl(t *testing.T) { } _ = os.Setenv(browserRoutingSubresourcesEnv, original) }) - if got := browserRoutingSubresourcesFromEnv(); len(got) != 2 || got[0] != "curl" || got[1] != "telemetry/stream" { - t.Fatalf("expected default subresources [curl telemetry/stream], got %#v", got) + if got := browserRoutingSubresourcesFromEnv(); len(got) != 4 || got[0] != "curl" || got[1] != "telemetry/stream" || got[2] != "computer" || got[3] != "playwright" { + t.Fatalf("expected default subresources [curl telemetry/stream computer playwright], got %#v", got) } t.Setenv(browserRoutingSubresourcesEnv, "") @@ -287,3 +287,166 @@ func TestBrowserRoutingSubresourcesFromEnvDefaultsToCurl(t *testing.T) { } } } + +func TestBrowserRoutingDefaultsComputerAndPlaywrightToVM(t *testing.T) { + original, ok := os.LookupEnv(browserRoutingSubresourcesEnv) + if err := os.Unsetenv(browserRoutingSubresourcesEnv); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if !ok { + _ = os.Unsetenv(browserRoutingSubresourcesEnv) + return + } + _ = os.Setenv(browserRoutingSubresourcesEnv, original) + }) + + var calls []struct { + Path string + Auth string + } + var srv *httptest.Server + srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls = append(calls, struct { + Path string + Auth string + }{Path: r.URL.Path + "?" + r.URL.RawQuery, Auth: r.Header.Get("Authorization")}) + + switch r.URL.Path { + case "/browsers": + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{ + "session_id": "sess-1", + "base_url": srv.URL + "/browser/kernel", + "cdp_ws_url": "wss://browser-session.test/browser/cdp?jwt=token-abc", + }) + case "/browser/kernel/computer/screenshot": + w.Header().Set("Content-Type", "image/png") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte{0x89, 0x50, 0x4e, 0x47}) + default: + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{"success": true}) + } + })) + defer srv.Close() + + client := NewClient( + option.WithBaseURL(srv.URL), + option.WithAPIKey("sk_test"), + option.WithHTTPClient(srv.Client()), + ) + + if _, err := client.Browsers.New(context.Background(), BrowserNewParams{}); err != nil { + t.Fatal(err) + } + screenshot, err := client.Browsers.Computer.CaptureScreenshot(context.Background(), "sess-1", BrowserComputerCaptureScreenshotParams{}) + if err != nil { + t.Fatal(err) + } + if screenshot != nil { + _ = screenshot.Body.Close() + } + if _, err := client.Browsers.Playwright.Execute(context.Background(), "sess-1", BrowserPlaywrightExecuteParams{Code: "return 1"}); err != nil { + t.Fatal(err) + } + + if len(calls) != 3 { + t.Fatalf("expected 3 calls, got %d %#v", len(calls), calls) + } + if calls[1].Path != "/browser/kernel/computer/screenshot?jwt=token-abc" { + t.Fatalf("expected direct VM screenshot path, got %q", calls[1].Path) + } + if calls[1].Auth != "" { + t.Fatalf("expected authorization header removed, got %q", calls[1].Auth) + } + if calls[2].Path != "/browser/kernel/playwright/execute?jwt=token-abc" { + t.Fatalf("expected direct VM playwright path, got %q", calls[2].Path) + } + if calls[2].Auth != "" { + t.Fatalf("expected authorization header removed, got %q", calls[2].Auth) + } +} + +func TestBrowserRoutingDefaultsKeepProcessFsAndTelemetryEventsOnAPI(t *testing.T) { + original, ok := os.LookupEnv(browserRoutingSubresourcesEnv) + if err := os.Unsetenv(browserRoutingSubresourcesEnv); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if !ok { + _ = os.Unsetenv(browserRoutingSubresourcesEnv) + return + } + _ = os.Setenv(browserRoutingSubresourcesEnv, original) + }) + + var paths []string + var srv *httptest.Server + srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + paths = append(paths, r.URL.Path) + switch { + case r.URL.Path == "/browsers": + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{ + "session_id": "sess-1", + "base_url": srv.URL + "/browser/kernel", + "cdp_ws_url": "wss://browser-session.test/browser/cdp?jwt=token-abc", + }) + case r.URL.Path == "/browsers/sess-1/fs/read_file": + w.Header().Set("Content-Type", "application/octet-stream") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("x")) + case r.URL.Path == "/browsers/sess-1/telemetry/events": + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode([]any{}) + default: + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{ + "duration_ms": 1, + "exit_code": 0, + "stderr_b64": "", + "stdout_b64": "", + }) + } + })) + defer srv.Close() + + client := NewClient( + option.WithBaseURL(srv.URL), + option.WithAPIKey("sk_test"), + option.WithHTTPClient(srv.Client()), + ) + + if _, err := client.Browsers.New(context.Background(), BrowserNewParams{}); err != nil { + t.Fatal(err) + } + if _, err := client.Browsers.Process.Exec(context.Background(), "sess-1", BrowserProcessExecParams{Command: "echo"}); err != nil { + t.Fatal(err) + } + fsResp, err := client.Browsers.Fs.ReadFile(context.Background(), "sess-1", BrowserFReadFileParams{Path: "/tmp/x"}) + if err != nil { + t.Fatal(err) + } + if fsResp != nil { + _ = fsResp.Body.Close() + } + if _, err := client.Browsers.Telemetry.Events(context.Background(), "sess-1", BrowserTelemetryEventsParams{}); err != nil { + t.Fatal(err) + } + + want := []string{ + "/browsers", + "/browsers/sess-1/process/exec", + "/browsers/sess-1/fs/read_file", + "/browsers/sess-1/telemetry/events", + } + if len(paths) != len(want) { + t.Fatalf("expected paths %v, got %v", want, paths) + } + for i := range want { + if paths[i] != want[i] { + t.Fatalf("expected paths %v, got %v", want, paths) + } + } +} diff --git a/lib/browserrouting/route_cache_test.go b/lib/browserrouting/route_cache_test.go index 33aaee7..3304bfe 100644 --- a/lib/browserrouting/route_cache_test.go +++ b/lib/browserrouting/route_cache_test.go @@ -67,6 +67,9 @@ func TestDirectVMRoutingMiddlewareAllowlistMatching(t *testing.T) { {"telemetry streaming-config not a stream prefix", "/browsers/sess-1/telemetry/streaming-config", false}, {"bare telemetry -> control plane", "/browsers/sess-1/telemetry", false}, {"curl proxy -> VM", "/browsers/sess-1/curl/raw", true}, + {"computer screenshot -> VM", "/browsers/sess-1/computer/screenshot", true}, + {"playwright execute -> VM", "/browsers/sess-1/playwright/execute", true}, + {"process exec -> control plane", "/browsers/sess-1/process/exec", false}, {"non-allowlisted subresource -> control plane", "/browsers/sess-1/fs/read", false}, } @@ -78,7 +81,7 @@ func TestDirectVMRoutingMiddlewareAllowlistMatching(t *testing.T) { BaseURL: "https://browser.example/browser/kernel", JWT: "jwt-123", }) - middleware := DirectVMRoutingMiddleware(cache, []string{"curl", "telemetry/stream"}) + middleware := DirectVMRoutingMiddleware(cache, []string{"curl", "telemetry/stream", "computer", "playwright"}) reqURL, err := url.Parse("https://api.example" + tc.path) if err != nil { From 60e276adc73b7efa3193f46b72d4756a6ebc0914 Mon Sep 17 00:00:00 2001 From: tnsardesai <18272584+tnsardesai@users.noreply.github.com> Date: Tue, 18 Aug 2026 20:44:25 +0000 Subject: [PATCH 2/3] Rename default-allowlist test to match the new list The default now includes computer and playwright, not just curl. --- browser_routing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser_routing_test.go b/browser_routing_test.go index 9bbefb1..234a5cc 100644 --- a/browser_routing_test.go +++ b/browser_routing_test.go @@ -254,7 +254,7 @@ func TestBrowserRoutingSkipsSubresourcesOutsideConfiguredAllowlist(t *testing.T) } } -func TestBrowserRoutingSubresourcesFromEnvDefaultsToCurl(t *testing.T) { +func TestBrowserRoutingSubresourcesFromEnvDefaultsToCurlTelemetryComputerAndPlaywright(t *testing.T) { original, ok := os.LookupEnv(browserRoutingSubresourcesEnv) if err := os.Unsetenv(browserRoutingSubresourcesEnv); err != nil { t.Fatal(err) From 2e664a95c766f8df018bc36fa4da4b9d69a87d64 Mon Sep 17 00:00:00 2001 From: tnsardesai <18272584+tnsardesai@users.noreply.github.com> Date: Tue, 18 Aug 2026 21:02:54 +0000 Subject: [PATCH 3/3] Shorten default-allowlist test name The asserted list can grow; the test name should not. --- browser_routing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser_routing_test.go b/browser_routing_test.go index 234a5cc..82d3e1e 100644 --- a/browser_routing_test.go +++ b/browser_routing_test.go @@ -254,7 +254,7 @@ func TestBrowserRoutingSkipsSubresourcesOutsideConfiguredAllowlist(t *testing.T) } } -func TestBrowserRoutingSubresourcesFromEnvDefaultsToCurlTelemetryComputerAndPlaywright(t *testing.T) { +func TestBrowserRoutingSubresourcesFromEnvDefaults(t *testing.T) { original, ok := os.LookupEnv(browserRoutingSubresourcesEnv) if err := os.Unsetenv(browserRoutingSubresourcesEnv); err != nil { t.Fatal(err)