mirror of
https://github.com/obra/superpowers.git
synced 2026-06-13 14:19:05 +08:00
Harden Windows browser launcher
This commit is contained in:
@@ -214,6 +214,20 @@ function companionUrl() {
|
|||||||
return 'http://' + urlHostForHttp(URL_HOST) + ':' + PORT + '/?key=' + TOKEN;
|
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) {
|
function isRegularFileInsideContentDir(filePath) {
|
||||||
let stat, realContentDir, realFilePath;
|
let stat, realContentDir, realFilePath;
|
||||||
try {
|
try {
|
||||||
@@ -455,13 +469,9 @@ function maybeOpenBrowser() {
|
|||||||
}
|
}
|
||||||
// Platform launchers: pass the URL as an argv element via execFile (no shell),
|
// 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.
|
// so a url-host containing shell metacharacters can't inject a command.
|
||||||
const isWSL = process.platform === 'linux' && /microsoft/i.test(require('os').release());
|
const launcher = browserLauncherForPlatform(url);
|
||||||
let bin, args;
|
if (!launcher) return; // headless: nothing to open
|
||||||
if (process.platform === 'darwin') { bin = 'open'; args = [url]; }
|
try { cp.execFile(launcher.bin, launcher.args, () => {}); } catch (e) { /* best effort */ }
|
||||||
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 */ }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Activity Tracking ==========
|
// ========== Activity Tracking ==========
|
||||||
@@ -627,4 +637,11 @@ if (require.main === module) {
|
|||||||
startServer();
|
startServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES, MAX_FRAME_PAYLOAD_BYTES };
|
module.exports = {
|
||||||
|
computeAcceptKey,
|
||||||
|
encodeFrame,
|
||||||
|
decodeFrame,
|
||||||
|
browserLauncherForPlatform,
|
||||||
|
OPCODES,
|
||||||
|
MAX_FRAME_PAYLOAD_BYTES
|
||||||
|
};
|
||||||
|
|||||||
66
tests/brainstorm-server/browser-launcher.test.js
Normal file
66
tests/brainstorm-server/browser-launcher.test.js
Normal file
@@ -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);
|
||||||
|
})();
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "brainstorm-server-tests",
|
"name": "brainstorm-server-tests",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"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": {
|
"dependencies": {
|
||||||
"ws": "^8.19.0"
|
"ws": "^8.19.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user