mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
* refactor(shell): one shell-argument-quoting module for the duplicated quote copies Each former copy now calls the canonical function with its exact spelling; a table test pins byte-identical output for every copy over hostile inputs. Distinct spellings stay distinct functions: '\'' vs '"'"', the two plain-word sets, and the cmd.exe doubled quote. Co-Authored-By: Claude <noreply@anthropic.com> * style: keep the quoting import at the top of two import-less files Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
305 lines
11 KiB
TypeScript
305 lines
11 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { quotePosixShellDoubleQuotedApostrophe } from '../shared/shell-argument-quoting'
|
|
|
|
const { statSyncMock } = vi.hoisted(() => ({ statSyncMock: vi.fn() }))
|
|
|
|
vi.mock('node:fs', () => ({ statSync: statSyncMock }))
|
|
|
|
// Executables the fake filesystem exposes, keyed by absolute path.
|
|
let executables: Map<string, { file: boolean; mode: number }>
|
|
|
|
function install(absolutePath: string, options?: { file?: boolean; mode?: number }): void {
|
|
executables.set(absolutePath, { file: options?.file ?? true, mode: options?.mode ?? 0o755 })
|
|
}
|
|
|
|
async function loadCommandModule() {
|
|
return import('./linux-package-install-command')
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.resetModules()
|
|
executables = new Map()
|
|
statSyncMock.mockReset().mockImplementation((candidate: string) => {
|
|
const entry = executables.get(candidate)
|
|
if (!entry) {
|
|
const error = new Error(`ENOENT: ${candidate}`) as NodeJS.ErrnoException
|
|
error.code = 'ENOENT'
|
|
throw error
|
|
}
|
|
return { isFile: () => entry.file, mode: entry.mode }
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
vi.unstubAllEnvs()
|
|
})
|
|
|
|
describe('quotePosixShellDoubleQuotedApostrophe', () => {
|
|
it('wraps plain values in single quotes', () => {
|
|
expect(quotePosixShellDoubleQuotedApostrophe('/tmp/orca.deb')).toBe("'/tmp/orca.deb'")
|
|
})
|
|
|
|
it('quotes spaces', () => {
|
|
expect(quotePosixShellDoubleQuotedApostrophe('/tmp/Orca Setup.deb')).toBe(
|
|
"'/tmp/Orca Setup.deb'"
|
|
)
|
|
})
|
|
|
|
it('neutralizes expansion, command substitution, separators, and double quotes', () => {
|
|
expect(quotePosixShellDoubleQuotedApostrophe('/tmp/$HOME.deb')).toBe("'/tmp/$HOME.deb'")
|
|
expect(quotePosixShellDoubleQuotedApostrophe('/tmp/`id`.deb')).toBe("'/tmp/`id`.deb'")
|
|
expect(quotePosixShellDoubleQuotedApostrophe('/tmp/a;rm -rf ~.deb')).toBe(
|
|
"'/tmp/a;rm -rf ~.deb'"
|
|
)
|
|
expect(quotePosixShellDoubleQuotedApostrophe('/tmp/"quoted".deb')).toBe('\'/tmp/"quoted".deb\'')
|
|
})
|
|
|
|
it('closes and reopens the quote around a literal single quote', () => {
|
|
expect(quotePosixShellDoubleQuotedApostrophe("/tmp/it's.deb")).toBe(`'/tmp/it'"'"'s.deb'`)
|
|
})
|
|
|
|
it('escapes every single quote in a value', () => {
|
|
expect(quotePosixShellDoubleQuotedApostrophe("'; rm -rf /; '")).toBe(`''"'"'; rm -rf /; '"'"''`)
|
|
})
|
|
|
|
it('quotes an empty value', () => {
|
|
expect(quotePosixShellDoubleQuotedApostrophe('')).toBe("''")
|
|
})
|
|
})
|
|
|
|
describe('resolveTrustedExecutable', () => {
|
|
it('resolves from /usr/bin first', async () => {
|
|
install('/usr/bin/sudo')
|
|
install('/bin/sudo')
|
|
const { resolveTrustedExecutable } = await loadCommandModule()
|
|
expect(resolveTrustedExecutable('sudo')).toBe('/usr/bin/sudo')
|
|
})
|
|
|
|
it('falls through the remaining trusted directories', async () => {
|
|
install('/sbin/dpkg')
|
|
const { resolveTrustedExecutable } = await loadCommandModule()
|
|
expect(resolveTrustedExecutable('dpkg')).toBe('/sbin/dpkg')
|
|
})
|
|
|
|
it('consults only the trusted directories', async () => {
|
|
const { resolveTrustedExecutable } = await loadCommandModule()
|
|
expect(resolveTrustedExecutable('sudo')).toBeNull()
|
|
expect(statSyncMock.mock.calls.map((call) => call[0])).toEqual([
|
|
'/usr/bin/sudo',
|
|
'/bin/sudo',
|
|
'/usr/sbin/sudo',
|
|
'/sbin/sudo'
|
|
])
|
|
})
|
|
|
|
it('ignores a user-writable absolute PATH decoy', async () => {
|
|
install('/home/user/.local/bin/sudo')
|
|
vi.stubEnv('PATH', '/home/user/.local/bin:/usr/bin')
|
|
const { resolveTrustedExecutable } = await loadCommandModule()
|
|
expect(resolveTrustedExecutable('sudo')).toBeNull()
|
|
expect(statSyncMock).not.toHaveBeenCalledWith('/home/user/.local/bin/sudo')
|
|
})
|
|
|
|
it('rejects a non-executable file', async () => {
|
|
install('/usr/bin/apt', { mode: 0o644 })
|
|
const { resolveTrustedExecutable } = await loadCommandModule()
|
|
expect(resolveTrustedExecutable('apt')).toBeNull()
|
|
})
|
|
|
|
it('accepts a file executable only by group or other', async () => {
|
|
install('/usr/bin/apt', { mode: 0o011 })
|
|
const { resolveTrustedExecutable } = await loadCommandModule()
|
|
expect(resolveTrustedExecutable('apt')).toBe('/usr/bin/apt')
|
|
})
|
|
|
|
it('rejects a directory of the same name', async () => {
|
|
install('/usr/bin/apt', { file: false })
|
|
const { resolveTrustedExecutable } = await loadCommandModule()
|
|
expect(resolveTrustedExecutable('apt')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('buildLinuxPackageInstallCommand', () => {
|
|
it('refuses a non-absolute package path before looking for anything', async () => {
|
|
// Why: zypper/dnf/yum/rpm take no `--`, so a relative or dash-leading path would become an option.
|
|
install('/usr/bin/sudo')
|
|
install('/usr/bin/apt')
|
|
const { buildLinuxPackageInstallCommand } = await loadCommandModule()
|
|
for (const packagePath of ['orca.deb', './orca.deb', '--force-all', '-i']) {
|
|
expect(buildLinuxPackageInstallCommand('deb', packagePath)).toEqual({
|
|
ok: false,
|
|
reason: 'invalid-package-path'
|
|
})
|
|
}
|
|
})
|
|
|
|
it('reports no-sudo when sudo is absent', async () => {
|
|
install('/usr/bin/apt')
|
|
const { buildLinuxPackageInstallCommand } = await loadCommandModule()
|
|
expect(buildLinuxPackageInstallCommand('deb', '/tmp/orca.deb')).toEqual({
|
|
ok: false,
|
|
reason: 'no-sudo'
|
|
})
|
|
})
|
|
|
|
it('reports no-package-manager when no manager is installed', async () => {
|
|
install('/usr/bin/sudo')
|
|
const { buildLinuxPackageInstallCommand } = await loadCommandModule()
|
|
expect(buildLinuxPackageInstallCommand('deb', '/tmp/orca.deb')).toEqual({
|
|
ok: false,
|
|
reason: 'no-package-manager'
|
|
})
|
|
})
|
|
|
|
it('does not fall back to an rpm manager for a deb package', async () => {
|
|
install('/usr/bin/sudo')
|
|
install('/usr/bin/dnf')
|
|
const { buildLinuxPackageInstallCommand } = await loadCommandModule()
|
|
expect(buildLinuxPackageInstallCommand('deb', '/tmp/orca.deb')).toEqual({
|
|
ok: false,
|
|
reason: 'no-package-manager'
|
|
})
|
|
})
|
|
|
|
it('builds an apt command', async () => {
|
|
install('/usr/bin/sudo')
|
|
install('/usr/bin/apt')
|
|
install('/usr/bin/dpkg')
|
|
const { buildLinuxPackageInstallCommand } = await loadCommandModule()
|
|
expect(buildLinuxPackageInstallCommand('deb', '/tmp/orca.deb')).toEqual({
|
|
ok: true,
|
|
command: "/usr/bin/sudo /usr/bin/apt install -- '/tmp/orca.deb'"
|
|
})
|
|
})
|
|
|
|
it('falls back to dpkg', async () => {
|
|
install('/usr/bin/sudo')
|
|
install('/usr/bin/dpkg')
|
|
const { buildLinuxPackageInstallCommand } = await loadCommandModule()
|
|
expect(buildLinuxPackageInstallCommand('deb', '/tmp/orca.deb')).toEqual({
|
|
ok: true,
|
|
command: "/usr/bin/sudo /usr/bin/dpkg -i -- '/tmp/orca.deb'"
|
|
})
|
|
})
|
|
|
|
it('builds a zypper command', async () => {
|
|
install('/usr/bin/sudo')
|
|
install('/usr/bin/zypper')
|
|
install('/usr/bin/dnf')
|
|
install('/usr/bin/yum')
|
|
install('/usr/bin/rpm')
|
|
const { buildLinuxPackageInstallCommand } = await loadCommandModule()
|
|
expect(buildLinuxPackageInstallCommand('rpm', '/tmp/orca.rpm')).toEqual({
|
|
ok: true,
|
|
command:
|
|
"/usr/bin/sudo /usr/bin/zypper --no-refresh install --allow-unsigned-rpm -f '/tmp/orca.rpm'"
|
|
})
|
|
})
|
|
|
|
it('prefers dnf over yum and rpm', async () => {
|
|
install('/usr/bin/sudo')
|
|
install('/usr/bin/dnf')
|
|
install('/usr/bin/yum')
|
|
install('/usr/bin/rpm')
|
|
const { buildLinuxPackageInstallCommand } = await loadCommandModule()
|
|
expect(buildLinuxPackageInstallCommand('rpm', '/tmp/orca.rpm')).toEqual({
|
|
ok: true,
|
|
command: "/usr/bin/sudo /usr/bin/dnf install --nogpgcheck '/tmp/orca.rpm'"
|
|
})
|
|
})
|
|
|
|
it('falls back to yum', async () => {
|
|
install('/usr/bin/sudo')
|
|
install('/usr/bin/yum')
|
|
install('/usr/bin/rpm')
|
|
const { buildLinuxPackageInstallCommand } = await loadCommandModule()
|
|
expect(buildLinuxPackageInstallCommand('rpm', '/tmp/orca.rpm')).toEqual({
|
|
ok: true,
|
|
command: "/usr/bin/sudo /usr/bin/yum install --nogpgcheck '/tmp/orca.rpm'"
|
|
})
|
|
})
|
|
|
|
it('falls back to rpm', async () => {
|
|
install('/usr/bin/sudo')
|
|
install('/sbin/rpm')
|
|
const { buildLinuxPackageInstallCommand } = await loadCommandModule()
|
|
expect(buildLinuxPackageInstallCommand('rpm', '/tmp/orca.rpm')).toEqual({
|
|
ok: true,
|
|
command: "/usr/bin/sudo /sbin/rpm -Uvh '/tmp/orca.rpm'"
|
|
})
|
|
})
|
|
|
|
it('never adds an unattended-confirmation flag', async () => {
|
|
install('/usr/bin/sudo')
|
|
install('/usr/bin/apt')
|
|
const { buildLinuxPackageInstallCommand } = await loadCommandModule()
|
|
const result = buildLinuxPackageInstallCommand('deb', '/tmp/orca.deb')
|
|
expect(result.ok).toBe(true)
|
|
expect(result.ok ? result.command : '').not.toMatch(
|
|
/(^|\s)(-y|--yes|--noconfirm|--assumeyes)(\s|$)/
|
|
)
|
|
})
|
|
|
|
it('quotes a hostile package path', async () => {
|
|
install('/usr/bin/sudo')
|
|
install('/usr/bin/apt')
|
|
const { buildLinuxPackageInstallCommand } = await loadCommandModule()
|
|
expect(buildLinuxPackageInstallCommand('deb', "/tmp/a b'; id #.deb")).toEqual({
|
|
ok: true,
|
|
command: `/usr/bin/sudo /usr/bin/apt install -- '/tmp/a b'"'"'; id #.deb'`
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('hasTrustedPackageManagerFor', () => {
|
|
it('accepts a deb host that has dpkg', async () => {
|
|
install('/usr/bin/dpkg')
|
|
const { hasTrustedPackageManagerFor } = await loadCommandModule()
|
|
expect(hasTrustedPackageManagerFor('deb')).toBe(true)
|
|
})
|
|
|
|
it('accepts a deb host that has only apt', async () => {
|
|
install('/usr/bin/apt')
|
|
const { hasTrustedPackageManagerFor } = await loadCommandModule()
|
|
expect(hasTrustedPackageManagerFor('deb')).toBe(true)
|
|
})
|
|
|
|
// The #17702 case: an Arch rebuild of the .deb inherits the marker but has no deb tooling.
|
|
it('rejects a deb marker on a host with only pacman', async () => {
|
|
install('/usr/bin/pacman')
|
|
install('/usr/bin/sudo')
|
|
const { hasTrustedPackageManagerFor } = await loadCommandModule()
|
|
expect(hasTrustedPackageManagerFor('deb')).toBe(false)
|
|
})
|
|
|
|
it('rejects an rpm marker on a host with only deb tooling', async () => {
|
|
install('/usr/bin/dpkg')
|
|
install('/usr/bin/apt')
|
|
const { hasTrustedPackageManagerFor } = await loadCommandModule()
|
|
expect(hasTrustedPackageManagerFor('rpm')).toBe(false)
|
|
})
|
|
|
|
it('accepts each rpm-family manager on its own', async () => {
|
|
for (const name of ['zypper', 'dnf', 'yum', 'rpm']) {
|
|
vi.resetModules()
|
|
executables = new Map()
|
|
install(`/usr/bin/${name}`)
|
|
const { hasTrustedPackageManagerFor } = await loadCommandModule()
|
|
expect(hasTrustedPackageManagerFor('rpm')).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('ignores a package manager outside the trusted directories', async () => {
|
|
install('/home/user/.local/bin/dpkg')
|
|
const { hasTrustedPackageManagerFor } = await loadCommandModule()
|
|
expect(hasTrustedPackageManagerFor('deb')).toBe(false)
|
|
})
|
|
|
|
it('ignores a non-executable file at a trusted path', async () => {
|
|
install('/usr/bin/dpkg', { mode: 0o644 })
|
|
const { hasTrustedPackageManagerFor } = await loadCommandModule()
|
|
expect(hasTrustedPackageManagerFor('deb')).toBe(false)
|
|
})
|
|
})
|