11import { test } from 'vitest' ;
22import assert from 'node:assert/strict' ;
3- import { runCmd } from '../exec.ts' ;
3+ import fs from 'node:fs' ;
4+ import os from 'node:os' ;
5+ import path from 'node:path' ;
6+ import { runCmd , whichCmd } from '../exec.ts' ;
47
58test ( 'runCmd enforces timeoutMs and rejects with COMMAND_FAILED' , async ( ) => {
69 await assert . rejects (
@@ -16,3 +19,50 @@ test('runCmd enforces timeoutMs and rejects with COMMAND_FAILED', async () => {
1619 } ,
1720 ) ;
1821} ) ;
22+
23+ test ( 'whichCmd resolves absolute executable paths without invoking a shell' , async ( ) => {
24+ assert . equal ( await whichCmd ( process . execPath ) , true ) ;
25+ } ) ;
26+
27+ test ( 'whichCmd resolves bare commands from PATH' , async ( ) => {
28+ assert . equal ( await whichCmd ( 'node' ) , true ) ;
29+ } ) ;
30+
31+ test . runIf ( process . platform !== 'win32' ) (
32+ 'runCmd allows explicit relative executable paths when shell execution is disabled' ,
33+ async ( ) => {
34+ const root = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'agent-device-runcmd-relative-' ) ) ;
35+ const target = path . join ( root , 'local-node' ) ;
36+ fs . symlinkSync ( process . execPath , target ) ;
37+
38+ try {
39+ const result = await runCmd ( './local-node' , [ '-e' , 'process.stdout.write("ok")' ] , {
40+ cwd : root ,
41+ } ) ;
42+ assert . equal ( result . stdout , 'ok' ) ;
43+ } finally {
44+ fs . rmSync ( root , { recursive : true , force : true } ) ;
45+ }
46+ } ,
47+ ) ;
48+
49+ test ( 'whichCmd rejects suspicious command strings' , async ( ) => {
50+ assert . equal ( await whichCmd ( 'node; rm -rf /' ) , false ) ;
51+ assert . equal ( await whichCmd ( './node' ) , false ) ;
52+ } ) ;
53+
54+ test . sequential ( 'whichCmd ignores directories that match a command name in PATH' , async ( ) => {
55+ const root = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'agent-device-whichcmd-' ) ) ;
56+ const fakeCommandDir = path . join ( root , 'fake-tool' ) ;
57+ fs . mkdirSync ( fakeCommandDir ) ;
58+
59+ const previousPath = process . env . PATH ;
60+ process . env . PATH = `${ root } ${ path . delimiter } ${ previousPath ?? '' } ` ;
61+
62+ try {
63+ assert . equal ( await whichCmd ( 'fake-tool' ) , false ) ;
64+ } finally {
65+ process . env . PATH = previousPath ;
66+ fs . rmSync ( root , { recursive : true , force : true } ) ;
67+ }
68+ } ) ;
0 commit comments