* fix(desktop): pin every command line the app may start On Windows the app starts npm-installed CLIs through cmd /c, and the shell scope let cmd take any arguments, so any code running in the webview could run any command. Each program now has a scope entry with its exact arguments, and Windows shims go through their own cmd-<name> entries with fixed /c <name> arguments. The agents and the MCP server no longer accept arbitrary arguments either. A test checks that every command the app starts has a matching entry on both platforms. * test(desktop): expect Windows shims through their own scope entries * test(desktop): check the executable of each shell scope entry * test(desktop): allow no shell scope entry beyond the programs the app starts An extra entry with a permissive validator passed the per-program checks.
84 lines
2.8 KiB
TypeScript
84 lines
2.8 KiB
TypeScript
import { afterEach, describe, expect, test } from 'bun:test'
|
|
|
|
import { spawnHarnessProcess } from '@/app/ai/harness/process'
|
|
|
|
import { clearTauriMocks, mockTauriIPC } from '#tests/helpers/tauri/mocks'
|
|
|
|
afterEach(async () => {
|
|
await clearTauriMocks()
|
|
})
|
|
|
|
describe('Harness sidecar process', () => {
|
|
test('spawns the optional companion and keeps credentials in process environment', async () => {
|
|
let onEvent: ((event: unknown) => void) | undefined
|
|
const calls: Array<{ cmd: string; args: unknown }> = []
|
|
await mockTauriIPC((cmd, args) => {
|
|
calls.push({ cmd, args })
|
|
if (cmd === 'plugin:shell|spawn') {
|
|
expect(args).toMatchObject({
|
|
program: 'openpencil-harness',
|
|
args: [],
|
|
options: {
|
|
encoding: 'raw',
|
|
env: { OPENPENCIL_HARNESS_API_KEY: 'secret' }
|
|
}
|
|
})
|
|
onEvent = (args as { onEvent: { onmessage: (event: unknown) => void } }).onEvent.onmessage
|
|
return 45
|
|
}
|
|
return null
|
|
})
|
|
|
|
const process = await spawnHarnessProcess({
|
|
environment: { OPENPENCIL_HARNESS_API_KEY: 'secret' },
|
|
onUnexpectedClose: () => undefined
|
|
})
|
|
await process.send({ id: 'one', method: 'service.shutdown' })
|
|
|
|
const reader = process.messages.getReader()
|
|
const encoded = new TextEncoder().encode(
|
|
'\n' + JSON.stringify({ type: 'response', id: 'one', result: { ok: true } }) + '\n'
|
|
)
|
|
onEvent?.({ event: 'Stdout', payload: Array.from(encoded.slice(0, 12)) })
|
|
onEvent?.({ event: 'Stdout', payload: Array.from(encoded.slice(12)) })
|
|
expect((await reader.read()).value).toEqual({
|
|
type: 'response',
|
|
id: 'one',
|
|
result: { ok: true }
|
|
})
|
|
|
|
expect(
|
|
JSON.stringify(calls.find((call) => call.cmd === 'plugin:shell|stdin_write'))
|
|
).not.toContain('secret')
|
|
})
|
|
|
|
test('routes the npm launcher through its cmd scope entry on Windows', async () => {
|
|
const originalNavigator = Object.getOwnPropertyDescriptor(globalThis, 'navigator')
|
|
try {
|
|
Object.defineProperty(globalThis, 'navigator', {
|
|
configurable: true,
|
|
value: { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' }
|
|
})
|
|
await mockTauriIPC((cmd, args) => {
|
|
if (cmd === 'plugin:shell|spawn') {
|
|
expect(args).toMatchObject({
|
|
program: 'cmd-openpencil-harness',
|
|
args: ['/c', 'openpencil-harness'],
|
|
options: { encoding: 'raw', env: {} }
|
|
})
|
|
return 46
|
|
}
|
|
return null
|
|
})
|
|
const process = await spawnHarnessProcess({
|
|
environment: {},
|
|
onUnexpectedClose: () => undefined
|
|
})
|
|
await process.child.kill()
|
|
} finally {
|
|
if (originalNavigator) Object.defineProperty(globalThis, 'navigator', originalNavigator)
|
|
else Reflect.deleteProperty(globalThis, 'navigator')
|
|
}
|
|
})
|
|
})
|