返回 CodeWhale
security-input.test.mjs
根目录 / crates / tui / plugins / computer-use / tests / security-input.test.mjs
1 import { test } from 'node:test';
2 import assert from 'node:assert/strict';
3 import path from 'node:path';
4 import os from 'node:os';
5 import fs from 'node:fs';
6 import { create as windows } from '../src/backends/win32.mjs';
7 import { create as harmony } from '../src/backends/harmonyos.mjs';
8 import { create as linux } from '../src/backends/linux.mjs';
9
10 test('Windows launch URLs are a single data argument, including PowerShell-looking text', async () => {
11 const scripts = [];
12 const b = windows({ exec: { run: async (_, args) => {
13 scripts.push(Buffer.from(args[args.indexOf('-EncodedCommand') + 1], 'base64').toString('utf16le'));
14 return {code:0, stdout:'', stderr:''};
15 } } });
16 for (const [url, quoted] of [
17 ['https://example.test/$(Write-Output marker)`whoami`?q=中 😀', '"https://example.test/$(Write-Output marker)`whoami`?q=中 😀"'],
18 ['https://example.test/"quoted"', '"https://example.test/\\"quoted\\""'],
19 ['https://example.test/end\\', '"https://example.test/end\\\\"'],
20 ]) {
21 assert.equal((await b.open_application({name:'msedge',url})).launched,true);
22 const script=scripts.at(-1);
23 assert.ok(!script.includes(url));
24 assert.doesNotMatch(script,/Write-Output marker|whoami/);
25 const encoded=script.match(/FromBase64String\('([^']+)'\)/)?.[1];
26 assert.ok(encoded, 'URL data must be carried separately from executable script');
27 assert.equal(Buffer.from(encoded,'base64').toString('utf16le'),quoted);
28 assert.match(script,/Start-Process -FilePath "msedge" -WindowStyle Minimized -ArgumentList \$launchArg/);
29 }
30 // activate defaults to background everywhere: an explicit activate:true is
31 // the only launch that takes the foreground.
32 assert.match((await b.open_application({name:'msedge'}),scripts.at(-1)),/-WindowStyle Minimized/);
33 await b.open_application({name:'msedge',activate:true});
34 assert.ok(!scripts.at(-1).includes('WindowStyle'));
35 const before=scripts.length;
36 for(const url of [123,{},'', '-Command whoami','https://example.test/\nwhoami']) await assert.rejects(b.open_application({name:'msedge',url}), /absolute URL/);
37 assert.equal(scripts.length,before);
38 });
39
40 test('Harmony launch rejects shell syntax before dispatch and quotes admitted identifiers', async () => {
41 const calls=[];
42 const b=harmony({exec:{shell:async args=>{calls.push(args);return {code:0,stdout:'',stderr:''};}}});
43 for(const value of ['com.example;id','$(id)','com.example\nid','com.example app','"com.example"','-debug',123,{},'']) {
44 await assert.rejects(b.open_application({bundle_id:value}),/bundle identifier/);
45 await assert.rejects(b.open_application({bundle_id:'com.example',ability:value}),/ability identifier/);
46 }
47 assert.equal(calls.length,0);
48 const result=await b.open_application({bundle_id:'com.example.app',ability:'MainAbility'});
49 assert.equal(result.launched,true);
50 assert.deepEqual(calls,[['aa','start','-b',"'com.example.app'",'-a',"'MainAbility'"]]);
51 });
52
53 test('Linux screenshot rejects option-shaped destinations before probing or capture', async t => {
54 const dir=fs.mkdtempSync(path.join(os.tmpdir(),'cu-path-security-'));
55 t.after(()=>fs.rmSync(dir,{recursive:true,force:true}));
56 const saved={};
57 for(const key of ['DISPLAY','WAYLAND_DISPLAY','XDG_SESSION_TYPE','CODEWHALE_CU_RECORDINGS_DIR']) saved[key]=process.env[key];
58 t.after(()=>{for(const [key,value] of Object.entries(saved)) if(value===undefined)delete process.env[key];else process.env[key]=value;});
59 process.env.DISPLAY=':fixture';delete process.env.WAYLAND_DISPLAY;
60 process.env.XDG_SESSION_TYPE='x11';process.env.CODEWHALE_CU_RECORDINGS_DIR=dir;
61 const calls=[];let probes=0;
62 const b=linux({exec:{have:async name=>{probes++;return name==='scrot';},run:async (cmd,args)=>{calls.push({cmd,args});return {code:1,stdout:'',stderr:'fixture capture reached'};}}});
63 for(const file of ['--exec=anything','relative.png','',123,{},'/tmp/a\0b']) await assert.rejects(b.screenshot({path:file}),/absolute filename/);
64 assert.equal(probes,0);assert.equal(calls.length,0);
65 const file=path.join(dir,'--exec=ordinary-filename.png');
66 await assert.rejects(b.screenshot({path:file}),/fixture capture reached/);
67 assert.deepEqual(calls,[{cmd:'scrot',args:['-z',file]}]);
68 });
69
69 lines Plain Text