openpencil/tests/engine/tauri/command.test.ts
Danila Poyarkov bd7acd6e34
fix(desktop): pin every command line the app may start (#922)
* 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.
2026-10-06 11:19:05 +00:00

117 lines
3.8 KiB
TypeScript

import { describe, expect, test } from 'bun:test'
import { readFileSync } from 'node:fs'
import * as v from 'valibot'
import { ACP_AGENTS } from '@open-pencil/core/constants'
import { resolvePlatformCommand } from '@/app/tauri/command'
import { repoPath } from '#tests/helpers/paths'
const WINDOWS_UA =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0'
const MAC_UA =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)'
describe('resolvePlatformCommand', () => {
test('wraps a bare command in cmd /c through its own scope entry on Windows', () => {
expect(resolvePlatformCommand('claude-agent-acp', [], WINDOWS_UA)).toEqual({
command: 'cmd-claude-agent-acp',
args: ['/c', 'claude-agent-acp']
})
})
test('preserves extra args after the command on Windows', () => {
expect(resolvePlatformCommand('gemini', ['--acp'], WINDOWS_UA)).toEqual({
command: 'cmd-gemini',
args: ['/c', 'gemini', '--acp']
})
})
test('passes the command through unchanged on non-Windows', () => {
expect(resolvePlatformCommand('claude-agent-acp', ['--acp'], MAC_UA)).toEqual({
command: 'claude-agent-acp',
args: ['--acp']
})
})
test('defaults args to an empty array', () => {
expect(resolvePlatformCommand('openpencil-mcp-http', undefined, MAC_UA)).toEqual({
command: 'openpencil-mcp-http',
args: []
})
})
test('passes through when the user agent is unavailable (headless/non-browser)', () => {
expect(resolvePlatformCommand('openpencil-mcp-http', ['--stdio'], '')).toEqual({
command: 'openpencil-mcp-http',
args: ['--stdio']
})
})
})
const ShellScope = v.object({
permissions: v.array(
v.union([
v.string(),
v.object({
identifier: v.string(),
allow: v.optional(
v.array(
v.object({
name: v.optional(v.string()),
cmd: v.optional(v.string()),
args: v.optional(v.union([v.boolean(), v.array(v.unknown())]))
})
)
)
})
])
)
})
function spawnScope() {
const text = readFileSync(repoPath('desktop/capabilities/default.json'), 'utf8')
const capability = v.parse(v.pipe(v.string(), v.parseJson(), ShellScope), text)
const spawn = capability.permissions.find(
(permission) => typeof permission !== 'string' && permission.identifier === 'shell:allow-spawn'
)
return typeof spawn === 'string' ? [] : (spawn?.allow ?? [])
}
describe('shell scope', () => {
// Every program the app starts, with the arguments it starts it with.
const spawns: [string, string[]][] = [
...ACP_AGENTS.map((agent): [string, string[]] => [agent.command, agent.args]),
['openpencil-mcp-http', []],
['openpencil-harness', []]
]
for (const userAgent of [WINDOWS_UA, MAC_UA]) {
for (const [name, args] of spawns) {
test(`allows exactly ${name} ${args.join(' ')} on ${userAgent === MAC_UA ? 'macOS' : 'Windows'}`, () => {
const resolved = resolvePlatformCommand(name, args, userAgent)
const entry = spawnScope().find((candidate) => candidate.name === resolved.command)
expect(entry?.cmd).toBe(userAgent === WINDOWS_UA ? 'cmd' : name)
expect(entry?.args === false ? [] : entry?.args).toEqual(resolved.args)
})
}
}
test('allows nothing but those programs', () => {
const expected = [WINDOWS_UA, MAC_UA].flatMap((userAgent) =>
spawns.map(([name, args]) => resolvePlatformCommand(name, args, userAgent).command)
)
expect(
spawnScope()
.map((entry) => entry.name)
.sort()
).toEqual([...new Set(expected)].sort())
})
test('lets no program take arbitrary arguments', () => {
expect(spawnScope().filter((entry) => entry.args === true)).toEqual([])
})
})