@@ -4,6 +4,13 @@ import os from "node:os";
44import path from "node:path" ;
55import test from "node:test" ;
66
7+ // Minimal fixture environments still need Windows' OS/profile bootstrap values.
8+ // Do not inherit API keys, OAuth tokens or model-provider configuration.
9+ const PLATFORM_ENV : NodeJS . ProcessEnv = process . platform === "win32"
10+ ? Object . fromEntries ( Object . entries ( process . env ) . filter ( ( [ key ] ) =>
11+ / ^ ( S y s t e m R o o t | w i n d i r | C o m S p e c | P A T H E X T | T E M P | T M P | U S E R P R O F I L E | A P P D A T A | L O C A L A P P D A T A | P R O G R A M D A T A | P R O G R A M F I L E S | P R O G R A M F I L E S \( X 8 6 \) | H O M E D R I V E | H O M E P A T H ) $ / i. test ( key ) ) )
12+ : { } ;
13+
714import {
815 LocalAgentError , claudeArgs , codeBuddyArgs , codexLoginProgress , findExecutable , parseClaudeOutput ,
916 executableInvocation , normalizeLocalAgentTimeoutMs , parseCodeBuddyOutput , probeClaude , probeCodeBuddy , probeCodex , runLocalAgent ,
@@ -28,7 +35,7 @@ function fakeNodeExecutable(dir: string, name: string, source: string): string {
2835 const bin = path . join ( dir , `${ name } .ps1` ) ;
2936 fs . writeFileSync ( script , source ) ;
3037 const quote = ( s : string ) => s . replaceAll ( "'" , "''" ) ;
31- fs . writeFileSync ( bin , `& '${ quote ( process . execPath ) } ' '${ quote ( script ) } ' @args\r\nexit $LASTEXITCODE\r\n` ) ;
38+ fs . writeFileSync ( bin , `$ErrorActionPreference = 'Stop'\r\n & '${ quote ( process . execPath ) } ' '${ quote ( script ) } ' @args\r\nexit $LASTEXITCODE\r\n` ) ;
3239 return bin ;
3340}
3441
@@ -91,11 +98,11 @@ if(a[0]==='login'){
9198}
9299` ) ;
93100 try {
94- const before = await probeCodex ( bin , codexHome , { TEST_LAUNCH_FILE : launchFile } ) ;
101+ const before = await probeCodex ( bin , codexHome , { ... PLATFORM_ENV , TEST_LAUNCH_FILE : launchFile } ) ;
95102 assert . equal ( before . status , "not_authenticated" ) ;
96103
97- const first = startCodexLogin ( bin , codexHome , { TEST_LAUNCH_FILE : launchFile } ) ;
98- const duplicate = startCodexLogin ( bin , codexHome , { TEST_LAUNCH_FILE : launchFile } ) ;
104+ const first = startCodexLogin ( bin , codexHome , { ... PLATFORM_ENV , TEST_LAUNCH_FILE : launchFile } ) ;
105+ const duplicate = startCodexLogin ( bin , codexHome , { ... PLATFORM_ENV , TEST_LAUNCH_FILE : launchFile } ) ;
99106 assert . equal ( first . state , "started" ) ;
100107 assert . equal ( duplicate . state , "pending" , "同一个产品 home 不能同时弹出两个登录流程" ) ;
101108 assert . equal ( codexLoginProgress ( codexHome ) ?. state , "pending" ) ;
@@ -106,7 +113,7 @@ if(a[0]==='login'){
106113 const launched = JSON . parse ( fs . readFileSync ( launchFile , "utf8" ) ) as { home : string ; args : string [ ] } ;
107114 assert . equal ( launched . home , codexHome ) ;
108115 assert . deepEqual ( launched . args , [ "login" ] ) ;
109- const after = await probeCodex ( bin , codexHome , { TEST_LAUNCH_FILE : launchFile } ) ;
116+ const after = await probeCodex ( bin , codexHome , { ... PLATFORM_ENV , TEST_LAUNCH_FILE : launchFile } ) ;
110117 assert . equal ( after . status , "ready" ) ;
111118 } finally {
112119 fs . rmSync ( dir , { recursive : true , force : true } ) ;
@@ -132,7 +139,7 @@ if(process.env.TEST_IS_CHILD==='1'){
132139}
133140` ) ;
134141 try {
135- const env = { TEST_PARENT_PID_FILE : parentPidFile , TEST_CHILD_PID_FILE : childPidFile } ;
142+ const env = { ... PLATFORM_ENV , TEST_PARENT_PID_FILE : parentPidFile , TEST_CHILD_PID_FILE : childPidFile } ;
136143 const started = Date . now ( ) ;
137144 assert . equal ( startCodexLogin ( bin , codexHome , env , { timeoutMs : 6_000 } ) . state , "started" ) ;
138145 for ( let i = 0 ; i < 500 && ( ! fs . existsSync ( parentPidFile ) || ! fs . existsSync ( childPidFile ) ) ; i += 1 ) {
@@ -184,7 +191,7 @@ test("本机 Claude 探针只返回版本与登录布尔,不泄露账号", asy
184191 const f = fakeClaude ( ) ;
185192 try {
186193 assert . equal ( findExecutable ( "claude" , { CLAUDE_BIN : f . bin , PATH : "" } ) , f . bin ) ;
187- const status = await probeClaude ( { CLAUDE_BIN : f . bin , PATH : "" } ) ;
194+ const status = await probeClaude ( { ... PLATFORM_ENV , CLAUDE_BIN : f . bin , PATH : "" } ) ;
188195 assert . equal ( status . status , "ready" ) ;
189196 assert . equal ( status . version , "2.1.226 (Claude Code)" ) ;
190197 assert . ok ( ! JSON . stringify ( status ) . includes ( "hidden@example.com" ) ) ;
@@ -194,11 +201,11 @@ test("本机 Claude 探针只返回版本与登录布尔,不泄露账号", asy
194201test ( "Claude 探针不把 API/云平台认证冒充订阅,旧版 CLI 也不点亮" , async ( ) => {
195202 const f = fakeClaude ( ) ;
196203 try {
197- const api = await probeClaude ( { CLAUDE_BIN : f . bin , PATH : "" , FAKE_AUTH_METHOD : "api_key" } ) ;
204+ const api = await probeClaude ( { ... PLATFORM_ENV , CLAUDE_BIN : f . bin , PATH : "" , FAKE_AUTH_METHOD : "api_key" } ) ;
198205 assert . equal ( api . status , "not_authenticated" ) ;
199- const bedrock = await probeClaude ( { CLAUDE_BIN : f . bin , PATH : "" , FAKE_API_PROVIDER : "bedrock" } ) ;
206+ const bedrock = await probeClaude ( { ... PLATFORM_ENV , CLAUDE_BIN : f . bin , PATH : "" , FAKE_API_PROVIDER : "bedrock" } ) ;
200207 assert . equal ( bedrock . status , "not_authenticated" ) ;
201- const old = await probeClaude ( { CLAUDE_BIN : f . bin , PATH : "" , FAKE_OLD_HELP : "1" } ) ;
208+ const old = await probeClaude ( { ... PLATFORM_ENV , CLAUDE_BIN : f . bin , PATH : "" , FAKE_OLD_HELP : "1" } ) ;
202209 assert . equal ( old . status , "probe_failed" ) ;
203210 } finally { fs . rmSync ( f . dir , { recursive : true , force : true } ) ; }
204211} ) ;
@@ -208,7 +215,7 @@ test("Claude 订阅调用走 stdin,并移除会静默改计费方的 Anthropic
208215 try {
209216 const out = await runLocalAgent ( "claude" , {
210217 systemPrompt : "规则" , userPrompt : "USER_SECRET_PROMPT" ,
211- env : {
218+ env : { ... PLATFORM_ENV ,
212219 CLAUDE_BIN : f . bin , PATH : process . env . PATH , ANTHROPIC_API_KEY : "must-not-forward" ,
213220 CLAUDE_CODE_OAUTH_TOKEN : "official-subscription-token" ,
214221 } ,
@@ -271,15 +278,15 @@ test("CodeBuddy 官方控制探针只返回版本与登录布尔,不泄露账
271278 const f = fakeCodeBuddy ( ) ;
272279 try {
273280 assert . equal ( findExecutable ( "codebuddy" , { CODEBUDDY_BIN : f . bin , PATH : "" } ) , f . bin ) ;
274- const status = await probeCodeBuddy ( { CODEBUDDY_BIN : f . bin , PATH : "" } ) ;
281+ const status = await probeCodeBuddy ( { ... PLATFORM_ENV , CODEBUDDY_BIN : f . bin , PATH : "" } ) ;
275282 assert . equal ( status . status , "ready" ) ;
276283 assert . equal ( status . version , "2.143.1 (CodeBuddy Code)" ) ;
277284 assert . ok ( ! JSON . stringify ( status ) . includes ( "hidden@example.com" ) ) ;
278285 assert . ok ( ! JSON . stringify ( status ) . includes ( "secret-token" ) ) ;
279286
280- const loggedOut = await probeCodeBuddy ( { CODEBUDDY_BIN : f . bin , PATH : "" , FAKE_NOT_LOGGED : "1" } ) ;
287+ const loggedOut = await probeCodeBuddy ( { ... PLATFORM_ENV , CODEBUDDY_BIN : f . bin , PATH : "" , FAKE_NOT_LOGGED : "1" } ) ;
281288 assert . equal ( loggedOut . status , "not_authenticated" ) ;
282- const old = await probeCodeBuddy ( { CODEBUDDY_BIN : f . bin , PATH : "" , FAKE_OLD_HELP : "1" } ) ;
289+ const old = await probeCodeBuddy ( { ... PLATFORM_ENV , CODEBUDDY_BIN : f . bin , PATH : "" , FAKE_OLD_HELP : "1" } ) ;
283290 assert . equal ( old . status , "probe_failed" ) ;
284291 } finally { fs . rmSync ( f . dir , { recursive : true , force : true } ) ; }
285292} ) ;
@@ -288,7 +295,7 @@ test("CodeBuddy 冷启动超过五秒不应误报登录检测失败", async () =
288295 const f = fakeCodeBuddy ( ) ;
289296 try {
290297 for ( const flag of [ "FAKE_COLD_HELP" , "FAKE_COLD_AUTH" ] ) {
291- const status = await probeCodeBuddy ( { CODEBUDDY_BIN : f . bin , PATH : "" , [ flag ] : "1" } ) ;
298+ const status = await probeCodeBuddy ( { ... PLATFORM_ENV , CODEBUDDY_BIN : f . bin , PATH : "" , [ flag ] : "1" } ) ;
292299 assert . equal ( status . status , "ready" , flag ) ;
293300 assert . equal ( status . authenticated , true ) ;
294301 assert . doesNotMatch ( JSON . stringify ( status ) , / s e c r e t - u s e r | s e c r e t - t o k e n | h i d d e n @ e x a m p l e / ) ;
@@ -301,7 +308,7 @@ test("CodeBuddy 订阅调用走 stdin,移除 API / token / 自定义端点并
301308 try {
302309 const out = await runLocalAgent ( "codebuddy" , {
303310 systemPrompt : "规则" , userPrompt : "USER_SECRET_PROMPT" ,
304- env : {
311+ env : { ... PLATFORM_ENV ,
305312 CODEBUDDY_BIN : f . bin , PATH : process . env . PATH ,
306313 CODEBUDDY_API_KEY : "must-not-forward" , CODEBUDDY_AUTH_TOKEN : "must-not-forward" ,
307314 CODEBUDDY_BASE_URL : "https://custom.invalid" , CODEBUDDY_MODEL : "other-model" ,
@@ -316,7 +323,7 @@ test("CodeBuddy Deep 首轮等待唯一 MCP,并维持订阅环境隔离", asyn
316323 try {
317324 const out = await runLocalAgent ( "codebuddy" , {
318325 systemPrompt : "规则" , userPrompt : "读取运行文件" ,
319- env : { CODEBUDDY_BIN : f . bin , PATH : process . env . PATH , FAKE_ECHO_MCP_ENV : "1" } ,
326+ env : { ... PLATFORM_ENV , CODEBUDDY_BIN : f . bin , PATH : process . env . PATH , FAKE_ECHO_MCP_ENV : "1" } ,
320327 controlledMcp : {
321328 serverName : "vra" , command : process . execPath , args : [ "/app/run_tools_mcp.ts" ] ,
322329 env : { VRA_RUN_DIR : "/data/runs/r1" } , allowedTools : [ "mcp__vra__list_run_files" ] , maxTurns : 8 ,
@@ -337,14 +344,14 @@ test("WorkBuddy 桌面端旧 CLI 复用现有订阅登录,但回答只在一
337344 const baseAppData = path . join ( baseProfile , "AppData" , "Roaming" ) ;
338345 const baseLocalAppData = path . join ( baseProfile , "AppData" , "Local" ) ;
339346 fs . mkdirSync ( baseHome ) ;
340- const status = await probeCodeBuddy ( {
347+ const status = await probeCodeBuddy ( { ... PLATFORM_ENV ,
341348 CODEBUDDY_BIN : f . bin , PATH : process . env . PATH , HOME : baseHome ,
342349 FAKE_BASE_HOME : baseHome , FAKE_LEGACY_HELP : "1" ,
343350 } ) ;
344351 assert . equal ( status . status , "ready" ) ;
345352 const out = await runLocalAgent ( "codebuddy" , {
346353 systemPrompt : "规则" , userPrompt : "USER_SECRET_PROMPT" ,
347- env : {
354+ env : { ... PLATFORM_ENV ,
348355 CODEBUDDY_BIN : f . bin , PATH : process . env . PATH , HOME : baseHome ,
349356 USERPROFILE : baseProfile , APPDATA : baseAppData , LOCALAPPDATA : baseLocalAppData ,
350357 FAKE_BASE_HOME : baseHome , FAKE_BASE_USERPROFILE : baseProfile ,
@@ -373,7 +380,7 @@ test("WorkBuddy 原生图片仅进入 stdin 用户消息,不当文件路径或
373380 assert . equal ( packet . type , "user" ) ;
374381 assert . deepEqual ( packet . message . content [ 2 ] , { type : "image" , source : { type : "base64" , media_type : "image/png" , data : "aW1hZ2U=" } } ) ;
375382 const output = await runLocalAgent ( "codebuddy" , { systemPrompt : "转写" , userPrompt : "转写" , userImages : images ,
376- env : { CODEBUDDY_BIN : f . bin , PATH : "" , FAKE_IMAGE_INPUT : "1" } } ) ;
383+ env : { ... PLATFORM_ENV , CODEBUDDY_BIN : f . bin , PATH : "" , FAKE_IMAGE_INPUT : "1" } } ) ;
377384 assert . match ( output , / a W 1 h Z 2 U = / ) ;
378385 assert . match ( output , / \| i n p u t _ f o r m a t = s t r e a m - j s o n \| b u i l t i n _ t o o l s = \| m o d e l _ o v e r r i d e = f a l s e / ) ;
379386 assert . equal ( localAgentInput ( "codebuddy" , "plain" ) , "plain" ) ;
@@ -389,7 +396,7 @@ test("CodeBuddy 未登录即使 CLI 以 exit 0 返回纯文本,也要归类为
389396 await assert . rejects (
390397 ( ) => runLocalAgent ( "codebuddy" , {
391398 systemPrompt : "规则" , userPrompt : "hello" ,
392- env : { CODEBUDDY_BIN : f . bin , PATH : process . env . PATH , FAKE_EXEC_NOT_LOGGED : "1" } ,
399+ env : { ... PLATFORM_ENV , CODEBUDDY_BIN : f . bin , PATH : process . env . PATH , FAKE_EXEC_NOT_LOGGED : "1" } ,
393400 } ) ,
394401 ( e : unknown ) => e instanceof LocalAgentError && e . code === "agent_not_authenticated" ,
395402 ) ;
@@ -418,7 +425,7 @@ setInterval(()=>{},1000);
418425 // 全量测试并行启动大量 Node 子进程;1 秒可能在假进程真正获得调度前就到期,
419426 // 那只测到了机器负载,不是“已启动的顽固进程树能否被清理”。
420427 systemPrompt : "规则" , userPrompt : "等待" , timeoutMs : 5_000 ,
421- env : {
428+ env : { ... PLATFORM_ENV ,
422429 CLAUDE_BIN : bin , PATH : process . env . PATH ,
423430 TEST_PARENT_PID_FILE : parentPidFile , TEST_CHILD_PID_FILE : childPidFile ,
424431 } ,
@@ -442,7 +449,7 @@ setInterval(()=>{},1000);
442449` ) ;
443450 const running = runLocalAgent ( "claude" , {
444451 systemPrompt : "规则" , userPrompt : "等待" , timeoutMs : 60_000 ,
445- env : { CLAUDE_BIN : bin , PATH : process . env . PATH , TEST_PID_FILE : pidFile } ,
452+ env : { ... PLATFORM_ENV , CLAUDE_BIN : bin , PATH : process . env . PATH , TEST_PID_FILE : pidFile } ,
446453 } ) ;
447454 try {
448455 for ( let i = 0 ; i < 100 && ! fs . existsSync ( pidFile ) ; i += 1 ) await new Promise ( ( resolve ) => setTimeout ( resolve , 20 ) ) ;
@@ -454,18 +461,40 @@ setInterval(()=>{},1000);
454461 } finally { fs . rmSync ( dir , { recursive : true , force : true } ) ; }
455462} ) ;
456463
464+ test ( "临时目录清理失败应拒绝请求,不抛出未捕获异常或把任务悬空" , async ( ) => {
465+ const f = fakeClaude ( ) ;
466+ const original = fs . rmSync ;
467+ let retained : fs . PathLike | undefined ;
468+ fs . rmSync = ( ( target , options ) => {
469+ if ( String ( target ) . includes ( `${ path . sep } vra-claude-` ) ) {
470+ retained = target ;
471+ throw Object . assign ( new Error ( "synthetic permission failure" ) , { code : "EPERM" } ) ;
472+ }
473+ return original ( target , options ) ;
474+ } ) as typeof fs . rmSync ;
475+ try {
476+ await assert . rejects ( runLocalAgent ( "claude" , {
477+ systemPrompt : "规则" , userPrompt : "测试" , env : { ...process . env , CLAUDE_BIN : f . bin } ,
478+ } ) , ( e : unknown ) => e instanceof LocalAgentError && e . code === "agent_cleanup_failed" ) ;
479+ } finally {
480+ fs . rmSync = original ;
481+ if ( retained ) original ( retained , { recursive : true , force : true , maxRetries : 5 } ) ;
482+ original ( f . dir , { recursive : true , force : true , maxRetries : 5 } ) ;
483+ }
484+ } ) ;
485+
457486test ( "一次性任务也受本机 Agent 全局并发上限约束,不能绕过会话表无限启动" , async ( ) => {
458487 const dir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "vra-busy-claude-" ) ) ;
459488 const bin = fakeNodeExecutable ( dir , "claude" , `setInterval(()=>{},1000);\n` ) ;
460489 const controllers = Array . from ( { length : 4 } , ( ) => new AbortController ( ) ) ;
461490 const runs = controllers . map ( ( ac ) => runLocalAgent ( "claude" , {
462491 systemPrompt : "规则" , userPrompt : "等待" , signal : ac . signal ,
463- env : { CLAUDE_BIN : bin , PATH : process . env . PATH } ,
492+ env : { ... PLATFORM_ENV , CLAUDE_BIN : bin , PATH : process . env . PATH } ,
464493 } ) ) ;
465494 try {
466495 await assert . rejects (
467496 ( ) => runLocalAgent ( "claude" , {
468- systemPrompt : "规则" , userPrompt : "第五个" , env : { CLAUDE_BIN : bin , PATH : process . env . PATH } ,
497+ systemPrompt : "规则" , userPrompt : "第五个" , env : { ... PLATFORM_ENV , CLAUDE_BIN : bin , PATH : process . env . PATH } ,
469498 } ) ,
470499 ( e : unknown ) => e instanceof LocalAgentError && e . code === "agent_busy" ,
471500 ) ;
0 commit comments