File size: 2,814 Bytes
0ed8124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import { chromium } from '@playwright/test';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createServer as createViteServer } from 'vite';

const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
let browser;
let vite;

try {
  vite = await createViteServer({
    root,
    logLevel: 'error',
    server: { host: '127.0.0.1', port: 0 },
    plugins: [{
      name: 'js-sandbox-probe-page',
      configureServer(server) {
        server.middlewares.use('/js-sandbox-probe.html', (_request, response) => {
          response.setHeader('Content-Type', 'text/html; charset=utf-8');
          response.end('<!doctype html><title>JavaScript sandbox probe</title>');
        });
      },
    }],
  });
  await vite.listen();
  const address = vite.httpServer?.address();
  if (!address || typeof address === 'string') throw new Error('Vite probe page did not expose a TCP port');

  browser = await chromium.launch({ headless: true });
  const page = await browser.newPage();
  await page.goto(`http://127.0.0.1:${address.port}/js-sandbox-probe.html`);

  const result = await page.evaluate(async () => {
    const { runJavaScript } = await import('/src/lib/js-sandbox.ts');
    const valid = await runJavaScript('console.log("local-only"); return 2 + 3;', 2_000);
    const blocked = [];
    for (const source of [
      'return await fetch("data:text/plain,leak");',
      'return await navigator.storage.getDirectory();',
      'return (await import("data:text/javascript,export default 7")).default;',
      'return (await Function("return import(\\"data:text/javascript,export default 9\\")")()).default;',
    ]) {
      try {
        await runJavaScript(source, 2_000);
        blocked.push(false);
      } catch {
        blocked.push(true);
      }
    }
    let timedOut = false;
    try {
      await runJavaScript('while (true) {}', 100);
    } catch (error) {
      timedOut = error instanceof Error && error.message.includes('exceeded 100 ms');
    }
    const recovered = await runJavaScript('return "responsive";', 2_000);
    return { valid, blocked, timedOut, recovered };
  });

  if (result.valid.value !== '5' || result.valid.logs[0] !== 'local-only') {
    throw new Error(`Valid local evaluation failed: ${JSON.stringify(result.valid)}`);
  }
  if (result.blocked.some((value) => !value)) {
    throw new Error(`A forbidden JavaScript capability escaped the sandbox: ${JSON.stringify(result.blocked)}`);
  }
  if (!result.timedOut || result.recovered.value !== 'responsive') {
    throw new Error(`Timeout/recovery contract failed: ${JSON.stringify(result)}`);
  }
  console.log('js_eval sandbox passed: local execution, 4 blocked capabilities, timeout, and recovery');
} finally {
  await browser?.close();
  await vite?.close();
}