typescript-virtual-container 1.5.2 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2277,7 +2277,21 @@ function resolveVfsBinary(name, env, shell, authUser) {
2277
2277
  }
2278
2278
  return null;
2279
2279
  }
2280
+ var MAX_CALL_DEPTH = 8;
2281
+ var _callDepth = 0;
2280
2282
  async function runCommandDirect(name, args, authUser, hostname, mode, cwd, shell, stdin, env) {
2283
+ _callDepth++;
2284
+ if (_callDepth > MAX_CALL_DEPTH) {
2285
+ _callDepth--;
2286
+ return { stderr: `${name}: maximum call depth (${MAX_CALL_DEPTH}) exceeded`, exitCode: 126 };
2287
+ }
2288
+ try {
2289
+ return await _runCommandDirectInner(name, args, authUser, hostname, mode, cwd, shell, stdin, env);
2290
+ } finally {
2291
+ _callDepth--;
2292
+ }
2293
+ }
2294
+ async function _runCommandDirectInner(name, args, authUser, hostname, mode, cwd, shell, stdin, env) {
2281
2295
  const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
2282
2296
  const invocation = [name, ...args];
2283
2297
  let assignCount = 0;
@@ -2350,6 +2364,7 @@ async function runCommandDirect(name, args, authUser, hostname, mode, cwd, shell
2350
2364
  env
2351
2365
  });
2352
2366
  }
2367
+ return { stderr: `${name}: exec builtin '${builtinMatch[1]}' not found`, exitCode: 127 };
2353
2368
  }
2354
2369
  const shMod = resolveModule("sh");
2355
2370
  if (shMod) {
@@ -2393,100 +2408,122 @@ async function runCommand(rawInput, authUser, hostname, mode, cwd, shell, stdin,
2393
2408
  const trimmed = rawInput.trim();
2394
2409
  if (trimmed.length === 0) return { exitCode: 0 };
2395
2410
  const shellEnv = env ?? makeDefaultEnv(authUser, hostname);
2396
- const rawTokens = tokenizeCommand(trimmed);
2397
- const rawFirstWord = rawTokens[0]?.toLowerCase() ?? "";
2398
- const aliasVal = shellEnv.vars[`__alias_${rawFirstWord}`];
2399
- const aliasExpanded = aliasVal ? trimmed.replace(rawFirstWord, aliasVal) : trimmed;
2400
- const isShScript = /\bfor\s+\w+\s+in\b/.test(aliasExpanded) || /\bwhile\s+/.test(aliasExpanded) || /\bif\s+/.test(aliasExpanded) || /\w+\s*\(\s*\)\s*\{/.test(aliasExpanded) || /\bfunction\s+\w+/.test(aliasExpanded) || /\(\(\s*.+\s*\)\)/.test(aliasExpanded);
2401
- const hasOperators = /(?<![|&])[|](?![|])/.test(aliasExpanded) || aliasExpanded.includes(">") || aliasExpanded.includes("<") || aliasExpanded.includes("&&") || aliasExpanded.includes("||") || aliasExpanded.includes(";");
2402
- if (isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash" || hasOperators) {
2403
- if (isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") {
2404
- const shMod = resolveModule("sh");
2405
- if (shMod) {
2406
- return await shMod.run({
2411
+ _callDepth++;
2412
+ if (_callDepth > MAX_CALL_DEPTH) {
2413
+ _callDepth--;
2414
+ return { stderr: `${trimmed.split(" ")[0]}: maximum call depth (${MAX_CALL_DEPTH}) exceeded`, exitCode: 126 };
2415
+ }
2416
+ try {
2417
+ const rawTokens = tokenizeCommand(trimmed);
2418
+ const rawFirstWord = rawTokens[0]?.toLowerCase() ?? "";
2419
+ const aliasVal = shellEnv.vars[`__alias_${rawFirstWord}`];
2420
+ const aliasExpanded = aliasVal ? trimmed.replace(rawFirstWord, aliasVal) : trimmed;
2421
+ const isShScript = /\bfor\s+\w+\s+in\b/.test(aliasExpanded) || /\bwhile\s+/.test(aliasExpanded) || /\bif\s+/.test(aliasExpanded) || /\w+\s*\(\s*\)\s*\{/.test(aliasExpanded) || /\bfunction\s+\w+/.test(aliasExpanded) || /\(\(\s*.+\s*\)\)/.test(aliasExpanded);
2422
+ const hasOperators = /(?<![|&])[|](?![|])/.test(aliasExpanded) || aliasExpanded.includes(">") || aliasExpanded.includes("<") || aliasExpanded.includes("&&") || aliasExpanded.includes("||") || aliasExpanded.includes(";");
2423
+ if (isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash" || hasOperators) {
2424
+ if (isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") {
2425
+ const shMod = resolveModule("sh");
2426
+ if (shMod) {
2427
+ return await shMod.run({
2428
+ authUser,
2429
+ hostname,
2430
+ activeSessions: shell.users.listActiveSessions(),
2431
+ rawInput: aliasExpanded,
2432
+ mode,
2433
+ args: ["-c", aliasExpanded],
2434
+ stdin: void 0,
2435
+ cwd,
2436
+ shell,
2437
+ env: shellEnv
2438
+ });
2439
+ }
2440
+ }
2441
+ const script = parseScript(aliasExpanded);
2442
+ if (!script.isValid)
2443
+ return { stderr: script.error || "Syntax error", exitCode: 1 };
2444
+ try {
2445
+ return await executeStatements(
2446
+ script.statements,
2407
2447
  authUser,
2408
2448
  hostname,
2409
- activeSessions: shell.users.listActiveSessions(),
2410
- rawInput: aliasExpanded,
2411
2449
  mode,
2412
- args: ["-c", aliasExpanded],
2413
- stdin: void 0,
2414
2450
  cwd,
2415
2451
  shell,
2416
- env: shellEnv
2417
- });
2452
+ shellEnv
2453
+ );
2454
+ } catch (error) {
2455
+ return {
2456
+ stderr: error instanceof Error ? error.message : "Execution failed",
2457
+ exitCode: 1
2458
+ };
2418
2459
  }
2419
2460
  }
2420
- const script = parseScript(aliasExpanded);
2421
- if (!script.isValid)
2422
- return { stderr: script.error || "Syntax error", exitCode: 1 };
2423
- try {
2424
- return await executeStatements(
2425
- script.statements,
2461
+ const expanded = await expandAsync(
2462
+ aliasExpanded,
2463
+ shellEnv.vars,
2464
+ shellEnv.lastExitCode,
2465
+ (sub) => runCommand(
2466
+ sub,
2426
2467
  authUser,
2427
2468
  hostname,
2428
2469
  mode,
2429
2470
  cwd,
2430
2471
  shell,
2472
+ void 0,
2473
+ shellEnv
2474
+ ).then((r) => r.stdout ?? "")
2475
+ );
2476
+ const parts = tokenizeCommand(expanded.trim());
2477
+ if (parts.length === 0) return { exitCode: 0 };
2478
+ const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
2479
+ if (assignRe.test(parts[0])) {
2480
+ return runCommandDirect(
2481
+ parts[0],
2482
+ parts.slice(1),
2483
+ authUser,
2484
+ hostname,
2485
+ mode,
2486
+ cwd,
2487
+ shell,
2488
+ stdin,
2431
2489
  shellEnv
2432
2490
  );
2433
- } catch (error) {
2434
- return {
2435
- stderr: error instanceof Error ? error.message : "Execution failed",
2436
- exitCode: 1
2437
- };
2438
2491
  }
2439
- }
2440
- const expanded = await expandAsync(
2441
- aliasExpanded,
2442
- shellEnv.vars,
2443
- shellEnv.lastExitCode,
2444
- (sub) => runCommand(
2445
- sub,
2446
- authUser,
2447
- hostname,
2448
- mode,
2449
- cwd,
2450
- shell,
2451
- void 0,
2452
- shellEnv
2453
- ).then((r) => r.stdout ?? "")
2454
- );
2455
- const parts = tokenizeCommand(expanded.trim());
2456
- if (parts.length === 0) return { exitCode: 0 };
2457
- const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
2458
- if (assignRe.test(parts[0])) {
2459
- return runCommandDirect(
2460
- parts[0],
2461
- parts.slice(1),
2462
- authUser,
2463
- hostname,
2464
- mode,
2465
- cwd,
2466
- shell,
2467
- stdin,
2468
- shellEnv
2469
- );
2470
- }
2471
- const commandName = parts[0]?.toLowerCase() ?? "";
2472
- const args = parts.slice(1).flatMap(expandBraces);
2473
- const mod = resolveModule(commandName);
2474
- if (!mod) {
2475
- const vfsBinary = resolveVfsBinary(commandName, shellEnv, shell, authUser);
2476
- if (vfsBinary) {
2477
- const stubContent = shell.vfs.readFile(vfsBinary);
2478
- const builtinMatch = stubContent.match(/exec\s+builtin\s+(\S+)/);
2479
- if (builtinMatch) {
2480
- const builtinName = builtinMatch[1];
2481
- const builtinMod = resolveModule(builtinName);
2482
- if (builtinMod) {
2483
- return await builtinMod.run({
2492
+ const commandName = parts[0]?.toLowerCase() ?? "";
2493
+ const args = parts.slice(1).flatMap(expandBraces);
2494
+ const mod = resolveModule(commandName);
2495
+ if (!mod) {
2496
+ const vfsBinary = resolveVfsBinary(commandName, shellEnv, shell, authUser);
2497
+ if (vfsBinary) {
2498
+ const stubContent = shell.vfs.readFile(vfsBinary);
2499
+ const builtinMatch = stubContent.match(/exec\s+builtin\s+(\S+)/);
2500
+ if (builtinMatch) {
2501
+ const builtinName = builtinMatch[1];
2502
+ const builtinMod = resolveModule(builtinName);
2503
+ if (builtinMod) {
2504
+ return await builtinMod.run({
2505
+ authUser,
2506
+ hostname,
2507
+ activeSessions: shell.users.listActiveSessions(),
2508
+ rawInput: [commandName, ...args].join(" "),
2509
+ mode,
2510
+ args,
2511
+ stdin,
2512
+ cwd,
2513
+ shell,
2514
+ env: shellEnv
2515
+ });
2516
+ }
2517
+ }
2518
+ const shMod = resolveModule("sh");
2519
+ if (shMod) {
2520
+ return await shMod.run({
2484
2521
  authUser,
2485
2522
  hostname,
2486
2523
  activeSessions: shell.users.listActiveSessions(),
2487
- rawInput: [commandName, ...args].join(" "),
2524
+ rawInput: `sh -c ${JSON.stringify(stubContent)}`,
2488
2525
  mode,
2489
- args,
2526
+ args: ["-c", stubContent, "--", ...args],
2490
2527
  stdin,
2491
2528
  cwd,
2492
2529
  shell,
@@ -2494,42 +2531,29 @@ async function runCommand(rawInput, authUser, hostname, mode, cwd, shell, stdin,
2494
2531
  });
2495
2532
  }
2496
2533
  }
2497
- const shMod = resolveModule("sh");
2498
- if (shMod) {
2499
- return await shMod.run({
2500
- authUser,
2501
- hostname,
2502
- activeSessions: shell.users.listActiveSessions(),
2503
- rawInput: `sh -c ${JSON.stringify(stubContent)}`,
2504
- mode,
2505
- args: ["-c", stubContent, "--", ...args],
2506
- stdin,
2507
- cwd,
2508
- shell,
2509
- env: shellEnv
2510
- });
2511
- }
2534
+ return { stderr: `${commandName}: command not found`, exitCode: 127 };
2512
2535
  }
2513
- return { stderr: `${commandName}: command not found`, exitCode: 127 };
2514
- }
2515
- try {
2516
- return await mod.run({
2517
- authUser,
2518
- hostname,
2519
- activeSessions: shell.users.listActiveSessions(),
2520
- rawInput: expanded,
2521
- mode,
2522
- args,
2523
- stdin,
2524
- cwd,
2525
- shell,
2526
- env: shellEnv
2527
- });
2528
- } catch (error) {
2529
- return {
2530
- stderr: error instanceof Error ? error.message : "Command failed",
2531
- exitCode: 1
2532
- };
2536
+ try {
2537
+ return await mod.run({
2538
+ authUser,
2539
+ hostname,
2540
+ activeSessions: shell.users.listActiveSessions(),
2541
+ rawInput: expanded,
2542
+ mode,
2543
+ args,
2544
+ stdin,
2545
+ cwd,
2546
+ shell,
2547
+ env: shellEnv
2548
+ });
2549
+ } catch (error) {
2550
+ return {
2551
+ stderr: error instanceof Error ? error.message : "Command failed",
2552
+ exitCode: 1
2553
+ };
2554
+ }
2555
+ } finally {
2556
+ _callDepth--;
2533
2557
  }
2534
2558
  }
2535
2559
 
@@ -13279,16 +13303,6 @@ var PACKAGE_REGISTRY = [
13279
13303
  shortDesc: "Vi IMproved",
13280
13304
  installedSizeKb: 3812,
13281
13305
  files: [
13282
- {
13283
- path: "/usr/bin/vim",
13284
- content: '#!/bin/sh\nexec builtin nano "$@"\n',
13285
- mode: 493
13286
- },
13287
- {
13288
- path: "/usr/bin/vi",
13289
- content: '#!/bin/sh\nexec builtin nano "$@"\n',
13290
- mode: 493
13291
- },
13292
13306
  {
13293
13307
  path: "/usr/share/doc/vim/README",
13294
13308
  content: "Vim editor \u2014 virtual package.\n"
@@ -8,16 +8,6 @@ const PACKAGE_REGISTRY = [
8
8
  shortDesc: "Vi IMproved",
9
9
  installedSizeKb: 3812,
10
10
  files: [
11
- {
12
- path: "/usr/bin/vim",
13
- content: "#!/bin/sh\nexec builtin nano \"$@\"\n",
14
- mode: 0o755,
15
- },
16
- {
17
- path: "/usr/bin/vi",
18
- content: "#!/bin/sh\nexec builtin nano \"$@\"\n",
19
- mode: 0o755,
20
- },
21
11
  {
22
12
  path: "/usr/share/doc/vim/README",
23
13
  content: "Vim editor — virtual package.\n",
@@ -61,7 +61,25 @@ function resolveVfsBinary(name, env, shell, authUser) {
61
61
  }
62
62
  return null;
63
63
  }
64
+ const MAX_CALL_DEPTH = 8;
65
+ let _callDepth = 0;
64
66
  export async function runCommandDirect(name, args, authUser, hostname, mode, cwd, shell, stdin, env) {
67
+ // Anti-loop guard: track call depth via env to avoid infinite recursion
68
+ _callDepth++;
69
+ // console.debug(`[depth=${_callDepth}] runCommandDirect: ${name}`);
70
+ if (_callDepth > MAX_CALL_DEPTH) {
71
+ _callDepth--;
72
+ // console.debug(`[LOOP DETECTED] runCommandDirect blocked: ${name}`);
73
+ return { stderr: `${name}: maximum call depth (${MAX_CALL_DEPTH}) exceeded`, exitCode: 126 };
74
+ }
75
+ try {
76
+ return await _runCommandDirectInner(name, args, authUser, hostname, mode, cwd, shell, stdin, env);
77
+ }
78
+ finally {
79
+ _callDepth--;
80
+ }
81
+ }
82
+ async function _runCommandDirectInner(name, args, authUser, hostname, mode, cwd, shell, stdin, env) {
65
83
  const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
66
84
  const invocation = [name, ...args];
67
85
  let assignCount = 0;
@@ -118,6 +136,8 @@ export async function runCommandDirect(name, args, authUser, hostname, mode, cwd
118
136
  env,
119
137
  });
120
138
  }
139
+ // builtin not found — stop here, don't fall through to sh -c (avoids infinite loop)
140
+ return { stderr: `${name}: exec builtin '${builtinMatch[1]}' not found`, exitCode: 127 };
121
141
  }
122
142
  const shMod = resolveModule("sh");
123
143
  if (shMod) {
@@ -163,84 +183,109 @@ export async function runCommand(rawInput, authUser, hostname, mode, cwd, shell,
163
183
  if (trimmed.length === 0)
164
184
  return { exitCode: 0 };
165
185
  const shellEnv = env ?? makeDefaultEnv(authUser, hostname);
166
- const rawTokens = tokenizeCommand(trimmed);
167
- const rawFirstWord = rawTokens[0]?.toLowerCase() ?? "";
168
- const aliasVal = shellEnv.vars[`__alias_${rawFirstWord}`];
169
- const aliasExpanded = aliasVal
170
- ? trimmed.replace(rawFirstWord, aliasVal)
171
- : trimmed;
172
- // Detect sh-syntax constructs that must be handled by the sh interpreter
173
- const isShScript = /\bfor\s+\w+\s+in\b/.test(aliasExpanded) ||
174
- /\bwhile\s+/.test(aliasExpanded) ||
175
- /\bif\s+/.test(aliasExpanded) ||
176
- /\w+\s*\(\s*\)\s*\{/.test(aliasExpanded) ||
177
- /\bfunction\s+\w+/.test(aliasExpanded) ||
178
- /\(\(\s*.+\s*\)\)/.test(aliasExpanded);
179
- const hasOperators = /(?<![|&])[|](?![|])/.test(aliasExpanded) ||
180
- aliasExpanded.includes(">") ||
181
- aliasExpanded.includes("<") ||
182
- aliasExpanded.includes("&&") ||
183
- aliasExpanded.includes("||") ||
184
- aliasExpanded.includes(";");
185
- if ((isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") || hasOperators) {
186
- // sh-syntax: route through sh interpreter to handle for/while/functions
187
- if (isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") {
188
- const shMod = resolveModule("sh");
189
- if (shMod) {
190
- return await shMod.run({
191
- authUser, hostname,
192
- activeSessions: shell.users.listActiveSessions(),
193
- rawInput: aliasExpanded,
194
- mode,
195
- args: ["-c", aliasExpanded],
196
- stdin: undefined,
197
- cwd,
198
- shell,
199
- env: shellEnv,
200
- });
186
+ // Anti-loop guard: check depth here too — catches sh -c recursive calls
187
+ _callDepth++;
188
+ // console.debug(`[depth=${_callDepth}] runCommand: ${trimmed.slice(0, 60)}`);
189
+ if (_callDepth > MAX_CALL_DEPTH) {
190
+ _callDepth--;
191
+ // console.debug(`[LOOP DETECTED] runCommand blocked: ${trimmed.slice(0, 60)}`);
192
+ return { stderr: `${trimmed.split(" ")[0]}: maximum call depth (${MAX_CALL_DEPTH}) exceeded`, exitCode: 126 };
193
+ }
194
+ try {
195
+ const rawTokens = tokenizeCommand(trimmed);
196
+ const rawFirstWord = rawTokens[0]?.toLowerCase() ?? "";
197
+ const aliasVal = shellEnv.vars[`__alias_${rawFirstWord}`];
198
+ const aliasExpanded = aliasVal
199
+ ? trimmed.replace(rawFirstWord, aliasVal)
200
+ : trimmed;
201
+ // Detect sh-syntax constructs that must be handled by the sh interpreter
202
+ const isShScript = /\bfor\s+\w+\s+in\b/.test(aliasExpanded) ||
203
+ /\bwhile\s+/.test(aliasExpanded) ||
204
+ /\bif\s+/.test(aliasExpanded) ||
205
+ /\w+\s*\(\s*\)\s*\{/.test(aliasExpanded) ||
206
+ /\bfunction\s+\w+/.test(aliasExpanded) ||
207
+ /\(\(\s*.+\s*\)\)/.test(aliasExpanded);
208
+ const hasOperators = /(?<![|&])[|](?![|])/.test(aliasExpanded) ||
209
+ aliasExpanded.includes(">") ||
210
+ aliasExpanded.includes("<") ||
211
+ aliasExpanded.includes("&&") ||
212
+ aliasExpanded.includes("||") ||
213
+ aliasExpanded.includes(";");
214
+ if ((isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") || hasOperators) {
215
+ // sh-syntax: route through sh interpreter to handle for/while/functions
216
+ if (isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") {
217
+ const shMod = resolveModule("sh");
218
+ if (shMod) {
219
+ return await shMod.run({
220
+ authUser, hostname,
221
+ activeSessions: shell.users.listActiveSessions(),
222
+ rawInput: aliasExpanded,
223
+ mode,
224
+ args: ["-c", aliasExpanded],
225
+ stdin: undefined,
226
+ cwd,
227
+ shell,
228
+ env: shellEnv,
229
+ });
230
+ }
231
+ }
232
+ const script = parseScript(aliasExpanded);
233
+ if (!script.isValid)
234
+ return { stderr: script.error || "Syntax error", exitCode: 1 };
235
+ try {
236
+ return await executeStatements(script.statements, authUser, hostname, mode, cwd, shell, shellEnv);
237
+ }
238
+ catch (error) {
239
+ return {
240
+ stderr: error instanceof Error ? error.message : "Execution failed",
241
+ exitCode: 1,
242
+ };
201
243
  }
202
244
  }
203
- const script = parseScript(aliasExpanded);
204
- if (!script.isValid)
205
- return { stderr: script.error || "Syntax error", exitCode: 1 };
206
- try {
207
- return await executeStatements(script.statements, authUser, hostname, mode, cwd, shell, shellEnv);
208
- }
209
- catch (error) {
210
- return {
211
- stderr: error instanceof Error ? error.message : "Execution failed",
212
- exitCode: 1,
213
- };
245
+ const expanded = await expandAsync(aliasExpanded, shellEnv.vars, shellEnv.lastExitCode, (sub) => runCommand(sub, authUser, hostname, mode, cwd, shell, undefined, shellEnv).then((r) => r.stdout ?? ""));
246
+ const parts = tokenizeCommand(expanded.trim());
247
+ if (parts.length === 0)
248
+ return { exitCode: 0 };
249
+ const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
250
+ if (assignRe.test(parts[0])) {
251
+ return runCommandDirect(parts[0], parts.slice(1), authUser, hostname, mode, cwd, shell, stdin, shellEnv);
214
252
  }
215
- }
216
- const expanded = await expandAsync(aliasExpanded, shellEnv.vars, shellEnv.lastExitCode, (sub) => runCommand(sub, authUser, hostname, mode, cwd, shell, undefined, shellEnv).then((r) => r.stdout ?? ""));
217
- const parts = tokenizeCommand(expanded.trim());
218
- if (parts.length === 0)
219
- return { exitCode: 0 };
220
- const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
221
- if (assignRe.test(parts[0])) {
222
- return runCommandDirect(parts[0], parts.slice(1), authUser, hostname, mode, cwd, shell, stdin, shellEnv);
223
- }
224
- const commandName = parts[0]?.toLowerCase() ?? "";
225
- // Apply brace expansion to each arg token
226
- const args = parts.slice(1).flatMap(expandBraces);
227
- const mod = resolveModule(commandName);
228
- if (!mod) {
229
- const vfsBinary = resolveVfsBinary(commandName, shellEnv, shell, authUser);
230
- if (vfsBinary) {
231
- const stubContent = shell.vfs.readFile(vfsBinary);
232
- const builtinMatch = stubContent.match(/exec\s+builtin\s+(\S+)/);
233
- if (builtinMatch) {
234
- const builtinName = builtinMatch[1];
235
- const builtinMod = resolveModule(builtinName);
236
- if (builtinMod) {
237
- return await builtinMod.run({
253
+ const commandName = parts[0]?.toLowerCase() ?? "";
254
+ // Apply brace expansion to each arg token
255
+ const args = parts.slice(1).flatMap(expandBraces);
256
+ const mod = resolveModule(commandName);
257
+ if (!mod) {
258
+ const vfsBinary = resolveVfsBinary(commandName, shellEnv, shell, authUser);
259
+ if (vfsBinary) {
260
+ const stubContent = shell.vfs.readFile(vfsBinary);
261
+ const builtinMatch = stubContent.match(/exec\s+builtin\s+(\S+)/);
262
+ if (builtinMatch) {
263
+ const builtinName = builtinMatch[1];
264
+ const builtinMod = resolveModule(builtinName);
265
+ if (builtinMod) {
266
+ return await builtinMod.run({
267
+ authUser,
268
+ hostname,
269
+ activeSessions: shell.users.listActiveSessions(),
270
+ rawInput: [commandName, ...args].join(" "),
271
+ mode,
272
+ args,
273
+ stdin,
274
+ cwd,
275
+ shell,
276
+ env: shellEnv,
277
+ });
278
+ }
279
+ }
280
+ const shMod = resolveModule("sh");
281
+ if (shMod) {
282
+ return await shMod.run({
238
283
  authUser,
239
284
  hostname,
240
285
  activeSessions: shell.users.listActiveSessions(),
241
- rawInput: [commandName, ...args].join(" "),
286
+ rawInput: `sh -c ${JSON.stringify(stubContent)}`,
242
287
  mode,
243
- args,
288
+ args: ["-c", stubContent, "--", ...args],
244
289
  stdin,
245
290
  cwd,
246
291
  shell,
@@ -248,42 +293,30 @@ export async function runCommand(rawInput, authUser, hostname, mode, cwd, shell,
248
293
  });
249
294
  }
250
295
  }
251
- const shMod = resolveModule("sh");
252
- if (shMod) {
253
- return await shMod.run({
254
- authUser,
255
- hostname,
256
- activeSessions: shell.users.listActiveSessions(),
257
- rawInput: `sh -c ${JSON.stringify(stubContent)}`,
258
- mode,
259
- args: ["-c", stubContent, "--", ...args],
260
- stdin,
261
- cwd,
262
- shell,
263
- env: shellEnv,
264
- });
265
- }
296
+ return { stderr: `${commandName}: command not found`, exitCode: 127 };
297
+ }
298
+ try {
299
+ return await mod.run({
300
+ authUser,
301
+ hostname,
302
+ activeSessions: shell.users.listActiveSessions(),
303
+ rawInput: expanded,
304
+ mode,
305
+ args,
306
+ stdin,
307
+ cwd,
308
+ shell,
309
+ env: shellEnv,
310
+ });
311
+ }
312
+ catch (error) {
313
+ return {
314
+ stderr: error instanceof Error ? error.message : "Command failed",
315
+ exitCode: 1,
316
+ };
266
317
  }
267
- return { stderr: `${commandName}: command not found`, exitCode: 127 };
268
- }
269
- try {
270
- return await mod.run({
271
- authUser,
272
- hostname,
273
- activeSessions: shell.users.listActiveSessions(),
274
- rawInput: expanded,
275
- mode,
276
- args,
277
- stdin,
278
- cwd,
279
- shell,
280
- env: shellEnv,
281
- });
282
318
  }
283
- catch (error) {
284
- return {
285
- stderr: error instanceof Error ? error.message : "Command failed",
286
- exitCode: 1,
287
- };
319
+ finally {
320
+ _callDepth--;
288
321
  }
289
322
  }