Skip to content

Commit 2af98bf

Browse files
authored
Merge pull request #170 from kernel/hypeship/tier1-direct-allowlist
Route computer and playwright calls directly to the VM
2 parents 2633a71 + 386880d commit 2af98bf

2 files changed

Lines changed: 96 additions & 4 deletions

File tree

‎src/lib/browser-routing.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const BROWSER_ROUTING_SUBRESOURCES_ENV = 'KERNEL_BROWSER_ROUTING_SUBRESOURCES';
3131
// Path prefixes eligible for direct-to-VM routing. "telemetry/stream" is the live
3232
// SSE endpoint (served by the VM); "telemetry/events" is a historical read served
3333
// by the control plane (S2) and must NOT be here.
34-
const DEFAULT_BROWSER_ROUTING_SUBRESOURCES = ['curl', 'telemetry/stream'];
34+
const DEFAULT_BROWSER_ROUTING_SUBRESOURCES = ['curl', 'telemetry/stream', 'computer', 'playwright'];
3535
const BROWSER_ROUTE_CACHEABLE_PATH = /^\/(?:v\d+\/)?browsers(?:\/[^/]+)?\/?$/;
3636
const BROWSER_POOL_ACQUIRE_PATH = /^\/(?:v\d+\/)?browser_pools\/[^/]+\/acquire\/?$/;
3737
const BROWSER_DELETE_BY_ID_PATH = /^\/(?:v\d+\/)?browsers\/([^/]+)\/?$/;

‎tests/lib/browser-routing.test.ts‎

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -444,20 +444,28 @@ describe('browser routing', () => {
444444
).rejects.toThrow(/unsupported HTTP method/i);
445445
});
446446

447-
test('defaults browser routing subresources to curl and telemetry/stream when env is unset', async () => {
447+
test('defaults browser routing subresources when env is unset', async () => {
448448
await withBrowserRoutingEnv(undefined, async () => {
449-
expect(browserRoutingSubresourcesFromEnv()).toEqual(['curl', 'telemetry/stream']);
449+
expect(browserRoutingSubresourcesFromEnv()).toEqual([
450+
'curl',
451+
'telemetry/stream',
452+
'computer',
453+
'playwright',
454+
]);
450455
});
451456
});
452457

453458
test('allowlist matching is segment-boundary aware (telemetry/events stays on the control plane)', () => {
454-
const prefixes = ['curl', 'telemetry/stream'];
459+
const prefixes = ['curl', 'telemetry/stream', 'computer', 'playwright'];
455460
expect(matchesDirectVMPrefix('telemetry/stream', prefixes)).toBe(true);
456461
expect(matchesDirectVMPrefix('telemetry/stream/x', prefixes)).toBe(true);
457462
expect(matchesDirectVMPrefix('telemetry/events', prefixes)).toBe(false);
458463
expect(matchesDirectVMPrefix('telemetry/streaming-config', prefixes)).toBe(false);
459464
expect(matchesDirectVMPrefix('telemetry', prefixes)).toBe(false);
460465
expect(matchesDirectVMPrefix('curl/raw', prefixes)).toBe(true);
466+
expect(matchesDirectVMPrefix('computer/screenshot', prefixes)).toBe(true);
467+
expect(matchesDirectVMPrefix('playwright/execute', prefixes)).toBe(true);
468+
expect(matchesDirectVMPrefix('process/exec', prefixes)).toBe(false);
461469
expect(matchesDirectVMPrefix('fs/read', prefixes)).toBe(false);
462470
});
463471

@@ -498,4 +506,88 @@ describe('browser routing', () => {
498506
expect(browserRoutingSubresourcesFromEnv()).toEqual([]);
499507
});
500508
});
509+
510+
test('routes computer screenshot and playwright execute to the VM by default', async () => {
511+
await withBrowserRoutingEnv(undefined, async () => {
512+
const calls: Array<{ url: string; headers: Headers }> = [];
513+
const kernel = new Kernel({
514+
apiKey: 'k',
515+
baseURL: 'https://api.example/',
516+
fetch: async (input, init?: RequestInit) => {
517+
const url = normalizeURL(input);
518+
const headers = input instanceof Request ? new Headers(input.headers) : new Headers(init?.headers);
519+
calls.push({ url, headers });
520+
if (url === 'https://api.example/browsers') {
521+
return Response.json({
522+
session_id: 'sess-1',
523+
base_url: 'http://browser-session.test/browser/kernel',
524+
cdp_ws_url: 'wss://browser-session.test/browser/cdp?jwt=token-abc',
525+
});
526+
}
527+
if (url.includes('/computer/screenshot')) {
528+
return new Response(new Uint8Array([1, 2, 3]), {
529+
status: 200,
530+
headers: { 'content-type': 'image/png' },
531+
});
532+
}
533+
return Response.json({ success: true });
534+
},
535+
});
536+
537+
await kernel.browsers.create();
538+
await kernel.browsers.computer.captureScreenshot('sess-1');
539+
await kernel.browsers.playwright.execute('sess-1', { code: 'return 1' });
540+
541+
expect(calls[1]?.url).toBe(
542+
'http://browser-session.test/browser/kernel/computer/screenshot?jwt=token-abc',
543+
);
544+
expect(calls[1]?.headers.get('authorization')).toBeNull();
545+
expect(calls[2]?.url).toBe(
546+
'http://browser-session.test/browser/kernel/playwright/execute?jwt=token-abc',
547+
);
548+
expect(calls[2]?.headers.get('authorization')).toBeNull();
549+
});
550+
});
551+
552+
test('keeps process, fs, and telemetry/events on the API origin by default', async () => {
553+
await withBrowserRoutingEnv(undefined, async () => {
554+
const calls: string[] = [];
555+
const kernel = new Kernel({
556+
apiKey: 'k',
557+
baseURL: 'https://api.example/',
558+
fetch: async (input) => {
559+
const url = normalizeURL(input);
560+
calls.push(url);
561+
if (url === 'https://api.example/browsers') {
562+
return Response.json({
563+
session_id: 'sess-1',
564+
base_url: 'http://browser-session.test/browser/kernel',
565+
cdp_ws_url: 'wss://browser-session.test/browser/cdp?jwt=token-abc',
566+
});
567+
}
568+
if (url.includes('/telemetry/events')) {
569+
return Response.json([]);
570+
}
571+
if (url.includes('/fs/read_file')) {
572+
return new Response(new Uint8Array([1]), {
573+
status: 200,
574+
headers: { 'content-type': 'application/octet-stream' },
575+
});
576+
}
577+
return Response.json({ exit_code: 0, stdout_b64: '', stderr_b64: '' });
578+
},
579+
});
580+
581+
await kernel.browsers.create();
582+
await kernel.browsers.process.exec('sess-1', { command: 'echo' });
583+
await kernel.browsers.fs.readFile('sess-1', { path: '/tmp/x' });
584+
await kernel.browsers.telemetry.events('sess-1');
585+
586+
expect(calls.slice(1)).toEqual([
587+
'https://api.example/browsers/sess-1/process/exec',
588+
'https://api.example/browsers/sess-1/fs/read_file?path=%2Ftmp%2Fx',
589+
'https://api.example/browsers/sess-1/telemetry/events',
590+
]);
591+
});
592+
});
501593
});

0 commit comments

Comments
 (0)