Просмотр исходного кода

Harden Windows browser launcher

Drew Ritter 2 месяцев назад
Родитель
Сommit
1c80914052

+ 25 - 8
skills/brainstorming/scripts/server.cjs

@@ -214,6 +214,20 @@ function companionUrl() {
   return 'http://' + urlHostForHttp(URL_HOST) + ':' + PORT + '/?key=' + TOKEN;
 }
 
+function browserLauncherForPlatform(url, {
+  platform = process.platform,
+  osRelease = require('os').release(),
+  env = process.env
+} = {}) {
+  const isWSL = platform === 'linux' && /microsoft/i.test(osRelease);
+  if (platform === 'darwin') return { bin: 'open', args: [url] };
+  if (platform === 'win32' || isWSL) {
+    return { bin: 'rundll32.exe', args: ['url.dll,FileProtocolHandler', url] };
+  }
+  if (env.DISPLAY || env.WAYLAND_DISPLAY) return { bin: 'xdg-open', args: [url] };
+  return null;
+}
+
 function isRegularFileInsideContentDir(filePath) {
   let stat, realContentDir, realFilePath;
   try {
@@ -455,13 +469,9 @@ function maybeOpenBrowser() {
   }
   // Platform launchers: pass the URL as an argv element via execFile (no shell),
   // so a url-host containing shell metacharacters can't inject a command.
-  const isWSL = process.platform === 'linux' && /microsoft/i.test(require('os').release());
-  let bin, args;
-  if (process.platform === 'darwin') { bin = 'open'; args = [url]; }
-  else if (process.platform === 'win32' || isWSL) { bin = 'cmd.exe'; args = ['/c', 'start', '', url]; }
-  else if (process.env.DISPLAY || process.env.WAYLAND_DISPLAY) { bin = 'xdg-open'; args = [url]; }
-  else return; // headless: nothing to open
-  try { cp.execFile(bin, args, () => {}); } catch (e) { /* best effort */ }
+  const launcher = browserLauncherForPlatform(url);
+  if (!launcher) return; // headless: nothing to open
+  try { cp.execFile(launcher.bin, launcher.args, () => {}); } catch (e) { /* best effort */ }
 }
 
 // ========== Activity Tracking ==========
@@ -627,4 +637,11 @@ if (require.main === module) {
   startServer();
 }
 
-module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES, MAX_FRAME_PAYLOAD_BYTES };
+module.exports = {
+  computeAcceptKey,
+  encodeFrame,
+  decodeFrame,
+  browserLauncherForPlatform,
+  OPCODES,
+  MAX_FRAME_PAYLOAD_BYTES
+};

+ 66 - 0
tests/brainstorm-server/browser-launcher.test.js

@@ -0,0 +1,66 @@
+const assert = require('assert');
+const {
+  browserLauncherForPlatform
+} = require('../../skills/brainstorming/scripts/server.cjs');
+
+let passed = 0;
+let failed = 0;
+
+async function test(name, fn) {
+  try {
+    await fn();
+    console.log(`  PASS: ${name}`);
+    passed++;
+  } catch (e) {
+    console.log(`  FAIL: ${name}`);
+    console.log(`    ${e.message}`);
+    failed++;
+  }
+}
+
+(async () => {
+  console.log('\n--- Browser Launcher ---');
+
+  await test('Windows launcher does not route URLs through cmd.exe', () => {
+    const url = 'http://localhost:54122/?key=abc&x=SAFE&echo=INJECTED';
+    const launcher = browserLauncherForPlatform(url, {
+      platform: 'win32',
+      osRelease: '10.0.26200',
+      env: {}
+    });
+
+    assert.deepStrictEqual(launcher, {
+      bin: 'rundll32.exe',
+      args: ['url.dll,FileProtocolHandler', url]
+    });
+    assert(!launcher.args.includes('/c'), 'Windows launcher must not pass /c to a command interpreter');
+  });
+
+  await test('WSL launcher does not route URLs through cmd.exe', () => {
+    const url = 'http://localhost:54122/?key=abc&x=SAFE&echo=INJECTED';
+    const launcher = browserLauncherForPlatform(url, {
+      platform: 'linux',
+      osRelease: '5.15.167.4-microsoft-standard-WSL2',
+      env: {}
+    });
+
+    assert.deepStrictEqual(launcher, {
+      bin: 'rundll32.exe',
+      args: ['url.dll,FileProtocolHandler', url]
+    });
+  });
+
+  await test('Linux launcher stays headless without a display', () => {
+    assert.strictEqual(
+      browserLauncherForPlatform('http://localhost:1/', {
+        platform: 'linux',
+        osRelease: '6.0.0',
+        env: {}
+      }),
+      null
+    );
+  });
+
+  console.log(`\n--- Results: ${passed} passed, ${failed} failed ---`);
+  if (failed > 0) process.exit(1);
+})();

+ 1 - 1
tests/brainstorm-server/package.json

@@ -2,7 +2,7 @@
   "name": "brainstorm-server-tests",
   "version": "1.0.0",
   "scripts": {
-    "test": "node ws-protocol.test.js && node helper.test.js && node auth.test.js && node server.test.js && node lifecycle.test.js && bash start-server.test.sh && bash stop-server.test.sh"
+    "test": "node ws-protocol.test.js && node helper.test.js && node browser-launcher.test.js && node auth.test.js && node server.test.js && node lifecycle.test.js && bash start-server.test.sh && bash stop-server.test.sh"
   },
   "dependencies": {
     "ws": "^8.19.0"