typescript-virtual-container 1.4.9 → 1.5.1

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.
Files changed (135) hide show
  1. package/.vscode/settings.json +1 -1
  2. package/README.md +141 -89
  3. package/builds/fortune-nyx-v1.5.1-directbash-k6.1.0.mjs +1768 -0
  4. package/builds/fortune-nyx-v1.5.1-ssh-nosftp.js +1768 -0
  5. package/builds/fortune-nyx-v1.5.1-ssh.cjs +1769 -0
  6. package/bun.lock +3 -3
  7. package/dist/SSHMimic/exec.js +2 -2
  8. package/dist/SSHMimic/exec.js.map +1 -1
  9. package/dist/SSHMimic/index.d.ts.map +1 -1
  10. package/dist/SSHMimic/index.js +2 -1
  11. package/dist/SSHMimic/index.js.map +1 -1
  12. package/dist/SSHMimic/sftp.d.ts.map +1 -1
  13. package/dist/SSHMimic/sftp.js +4 -3
  14. package/dist/SSHMimic/sftp.js.map +1 -1
  15. package/dist/VirtualFileSystem/index.d.ts +14 -0
  16. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  17. package/dist/VirtualFileSystem/index.js +51 -3
  18. package/dist/VirtualFileSystem/index.js.map +1 -1
  19. package/dist/VirtualFileSystem/journal.d.ts.map +1 -1
  20. package/dist/VirtualFileSystem/journal.js +13 -5
  21. package/dist/VirtualFileSystem/journal.js.map +1 -1
  22. package/dist/VirtualShell/shell.js +12 -12
  23. package/dist/VirtualShell/shell.js.map +1 -1
  24. package/dist/VirtualUserManager/index.js +8 -11
  25. package/dist/VirtualUserManager/index.js.map +1 -1
  26. package/dist/commands/apt.js +3 -3
  27. package/dist/commands/apt.js.map +1 -1
  28. package/dist/commands/cd.d.ts.map +1 -1
  29. package/dist/commands/cd.js +2 -1
  30. package/dist/commands/cd.js.map +1 -1
  31. package/dist/commands/helpers.d.ts +1 -1
  32. package/dist/commands/helpers.d.ts.map +1 -1
  33. package/dist/commands/helpers.js +3 -2
  34. package/dist/commands/helpers.js.map +1 -1
  35. package/dist/commands/index.d.ts +1 -1
  36. package/dist/commands/index.d.ts.map +1 -1
  37. package/dist/commands/index.js +1 -1
  38. package/dist/commands/index.js.map +1 -1
  39. package/dist/commands/lsb-release.js +1 -1
  40. package/dist/commands/lsb-release.js.map +1 -1
  41. package/dist/commands/runtime.d.ts +2 -0
  42. package/dist/commands/runtime.d.ts.map +1 -1
  43. package/dist/commands/runtime.js +5 -1
  44. package/dist/commands/runtime.js.map +1 -1
  45. package/dist/modules/linuxRootfs.d.ts +9 -5
  46. package/dist/modules/linuxRootfs.d.ts.map +1 -1
  47. package/dist/modules/linuxRootfs.js +1079 -148
  48. package/dist/modules/linuxRootfs.js.map +1 -1
  49. package/dist/self-standalone.js +22 -12
  50. package/dist/self-standalone.js.map +1 -1
  51. package/docs/assets/hierarchy.js +1 -1
  52. package/docs/classes/HoneyPot.html +9 -9
  53. package/docs/classes/IdleManager.html +8 -8
  54. package/docs/classes/SshClient.html +18 -18
  55. package/docs/classes/VirtualFileSystem.html +34 -34
  56. package/docs/classes/VirtualPackageManager.html +13 -13
  57. package/docs/classes/VirtualSftpServer.html +3 -3
  58. package/docs/classes/VirtualShell.html +28 -28
  59. package/docs/classes/VirtualSshServer.html +5 -5
  60. package/docs/classes/VirtualUserManager.html +27 -27
  61. package/docs/functions/assertDiff.html +2 -2
  62. package/docs/functions/diffSnapshots.html +2 -2
  63. package/docs/functions/formatDiff.html +2 -2
  64. package/docs/functions/getArg.html +2 -2
  65. package/docs/functions/getFlag.html +2 -2
  66. package/docs/functions/ifFlag.html +2 -2
  67. package/docs/hierarchy.html +1 -1
  68. package/docs/index.html +37 -31
  69. package/docs/interfaces/AuditLogEntry.html +3 -3
  70. package/docs/interfaces/CommandContext.html +12 -12
  71. package/docs/interfaces/CommandResult.html +13 -13
  72. package/docs/interfaces/ExecStream.html +6 -6
  73. package/docs/interfaces/HoneyPotStats.html +3 -3
  74. package/docs/interfaces/IdleManagerOptions.html +3 -3
  75. package/docs/interfaces/InstalledPackage.html +11 -11
  76. package/docs/interfaces/NanoEditorSession.html +5 -5
  77. package/docs/interfaces/PackageDefinition.html +14 -14
  78. package/docs/interfaces/PackageFile.html +5 -5
  79. package/docs/interfaces/PasswordChallenge.html +9 -9
  80. package/docs/interfaces/RemoveOptions.html +3 -3
  81. package/docs/interfaces/ShellEnv.html +4 -4
  82. package/docs/interfaces/ShellModule.html +8 -8
  83. package/docs/interfaces/ShellProperties.html +5 -5
  84. package/docs/interfaces/ShellStream.html +7 -7
  85. package/docs/interfaces/SudoChallenge.html +9 -9
  86. package/docs/interfaces/VfsBaseNode.html +7 -7
  87. package/docs/interfaces/VfsDiff.html +6 -6
  88. package/docs/interfaces/VfsDiffEntry.html +4 -4
  89. package/docs/interfaces/VfsDiffModified.html +6 -6
  90. package/docs/interfaces/VfsDirectoryNode.html +8 -8
  91. package/docs/interfaces/VfsFileNode.html +9 -9
  92. package/docs/interfaces/VfsOptions.html +6 -6
  93. package/docs/interfaces/VfsSnapshot.html +3 -3
  94. package/docs/interfaces/VfsSnapshotBaseNode.html +4 -4
  95. package/docs/interfaces/VfsSnapshotDirectoryNode.html +5 -5
  96. package/docs/interfaces/VfsSnapshotFileNode.html +6 -6
  97. package/docs/interfaces/VirtualActiveSession.html +7 -7
  98. package/docs/interfaces/VirtualSftpServerOptions.html +3 -3
  99. package/docs/interfaces/VirtualShellVfsLike.html +3 -3
  100. package/docs/interfaces/VirtualShellVfsOptions.html +3 -3
  101. package/docs/interfaces/WriteFileOptions.html +4 -4
  102. package/docs/modules.html +1 -1
  103. package/docs/types/ArgParseOptions.html +3 -3
  104. package/docs/types/CommandMode.html +2 -2
  105. package/docs/types/CommandOutcome.html +2 -2
  106. package/docs/types/IdleState.html +1 -1
  107. package/docs/types/VfsNodeStats.html +2 -2
  108. package/docs/types/VfsNodeType.html +2 -2
  109. package/docs/types/VfsPersistenceMode.html +2 -2
  110. package/docs/types/VfsSnapshotNode.html +2 -2
  111. package/package.json +6 -5
  112. package/scripts/build-all.mjs +198 -0
  113. package/scripts/build-names.mjs +44 -0
  114. package/src/SSHMimic/exec.ts +2 -2
  115. package/src/SSHMimic/index.ts +2 -1
  116. package/src/SSHMimic/sftp.ts +4 -3
  117. package/src/VirtualFileSystem/index.ts +46 -3
  118. package/src/VirtualFileSystem/journal.ts +12 -5
  119. package/src/VirtualShell/shell.ts +12 -12
  120. package/src/VirtualUserManager/index.ts +11 -11
  121. package/src/commands/apt.ts +3 -3
  122. package/src/commands/cd.ts +2 -1
  123. package/src/commands/helpers.ts +3 -2
  124. package/src/commands/index.ts +1 -1
  125. package/src/commands/lsb-release.ts +1 -1
  126. package/src/commands/runtime.ts +6 -1
  127. package/src/modules/linuxRootfs.ts +1293 -207
  128. package/src/self-standalone.ts +26 -12
  129. package/tests/new-features.test.ts +2 -2
  130. package/tests/sftp.test.ts +13 -13
  131. package/builds/self-standalone.js +0 -1299
  132. package/builds/standalone-wo-sftp.js +0 -1300
  133. package/builds/standalone.cjs +0 -1301
  134. /package/builds/{web-full-api.min.js → fortune-nyx-v1.5.1-web-full.min.js} +0 -0
  135. /package/builds/{web.min.js → fortune-nyx-v1.5.1-web.min.js} +0 -0
@@ -5,7 +5,7 @@ import { stdin, stdout } from "node:process";
5
5
  import { createInterface, type Interface } from "node:readline";
6
6
 
7
7
  import { getCommandNames } from "./commands/registry";
8
- import { makeDefaultEnv, runCommand } from "./commands/runtime";
8
+ import { makeDefaultEnv, runCommand, userHome } from "./commands/runtime";
9
9
  import { spawnNanoEditorProcess } from "./modules/shellInteractive";
10
10
  import { resolvePath } from "./modules/shellRuntime";
11
11
  import { buildLoginBanner, type LoginBannerState } from "./SSHMimic/loginBanner";
@@ -66,7 +66,7 @@ async function flushVfs(): Promise<void> {
66
66
  }
67
67
 
68
68
  function loadHistory(authUser: string): string[] {
69
- const historyPath = `/home/${authUser}/.bash_history`;
69
+ const historyPath = `${userHome(authUser)}/.bash_history`;
70
70
  if (!virtualShell.vfs.exists(historyPath)) {
71
71
  virtualShell.vfs.writeFile(historyPath, "");
72
72
  return [];
@@ -80,7 +80,7 @@ function loadHistory(authUser: string): string[] {
80
80
 
81
81
  function saveHistory(history: string[], authUser: string): void {
82
82
  const data = history.length > 0 ? `${history.join("\n")}\n` : "";
83
- virtualShell.vfs.writeFile(`/home/${authUser}/.bash_history`, data);
83
+ virtualShell.vfs.writeFile(`${userHome(authUser)}/.bash_history`, data);
84
84
  }
85
85
 
86
86
  // ── Tab completion ────────────────────────────────────────────────────────────
@@ -173,10 +173,10 @@ function applySessionState(
173
173
  let cwd = cwdState;
174
174
  if (result.switchUser) {
175
175
  authUser = result.switchUser;
176
- cwd = result.nextCwd ?? `/home/${authUser}`;
176
+ cwd = result.nextCwd ?? userHome(authUser);
177
177
  shellEnvState.vars.USER = authUser;
178
178
  shellEnvState.vars.LOGNAME = authUser;
179
- shellEnvState.vars.HOME = `/home/${authUser}`;
179
+ shellEnvState.vars.HOME = userHome(authUser);
180
180
  shellEnvState.vars.PWD = cwd;
181
181
  } else if (result.nextCwd) {
182
182
  cwd = result.nextCwd;
@@ -203,9 +203,23 @@ async function runReadlineShell(): Promise<void> {
203
203
  process.exit(1);
204
204
  }
205
205
 
206
+ // Ensure home dir and README.txt exist (mirrors SSHMimic/VirtualUserManager behaviour)
207
+ const homePath = selectedUser === "root" ? "/root" : userHome(selectedUser);
208
+ if (!virtualShell.vfs.exists(homePath)) {
209
+ virtualShell.vfs.mkdir(homePath, selectedUser === "root" ? 0o700 : 0o755);
210
+ }
211
+ const readmePath = `${homePath}/README.txt`;
212
+ if (!virtualShell.vfs.exists(readmePath)) {
213
+ virtualShell.vfs.writeFile(
214
+ readmePath,
215
+ `Welcome to ${hostname}\n`,
216
+ );
217
+ await virtualShell.vfs.stopAutoFlush();
218
+ }
219
+
206
220
  const shellEnv = makeDefaultEnv(selectedUser, hostname);
207
221
  let authUser = selectedUser;
208
- let cwd = `/home/${authUser}`;
222
+ let cwd = userHome(authUser);
209
223
  shellEnv.vars.PWD = cwd;
210
224
  const remoteAddress = "localhost";
211
225
  const terminalSize = { cols: stdout.columns ?? 80, rows: stdout.rows ?? 24 };
@@ -309,15 +323,15 @@ async function runReadlineShell(): Promise<void> {
309
323
 
310
324
  if (!challenge.commandLine) {
311
325
  authUser = challenge.targetUser;
312
- cwd = `/home/${authUser}`;
326
+ cwd = userHome(authUser);
313
327
  shellEnv.vars.USER = authUser;
314
328
  shellEnv.vars.LOGNAME = authUser;
315
- shellEnv.vars.HOME = `/home/${authUser}`;
329
+ shellEnv.vars.HOME = userHome(authUser);
316
330
  shellEnv.vars.PWD = cwd;
317
331
  return;
318
332
  }
319
333
 
320
- const runCwd = challenge.loginShell ? `/home/${challenge.targetUser}` : cwd;
334
+ const runCwd = challenge.loginShell ? userHome(challenge.targetUser) : cwd;
321
335
  const nestedResult = await runCommand(
322
336
  challenge.commandLine,
323
337
  challenge.targetUser,
@@ -360,10 +374,10 @@ async function runReadlineShell(): Promise<void> {
360
374
  break;
361
375
  case "su":
362
376
  authUser = challenge.targetUsername;
363
- cwd = `/home/${authUser}`;
377
+ cwd = userHome(authUser);
364
378
  shellEnv.vars.USER = authUser;
365
379
  shellEnv.vars.LOGNAME = authUser;
366
- shellEnv.vars.HOME = `/home/${authUser}`;
380
+ shellEnv.vars.HOME = userHome(authUser);
367
381
  shellEnv.vars.PWD = cwd;
368
382
  break;
369
383
  }
@@ -417,7 +431,7 @@ async function runReadlineShell(): Promise<void> {
417
431
  // ── Prompt helper ──────────────────────────────────────────────────────────
418
432
 
419
433
  const renderPrompt = (): string => {
420
- const cwdLabel = cwd === `/home/${authUser}` ? "~" : basename(cwd) || "/";
434
+ const cwdLabel = cwd === userHome(authUser) ? "~" : basename(cwd) || "/";
421
435
  return buildPrompt(authUser, hostname, cwdLabel);
422
436
  };
423
437
 
@@ -55,7 +55,7 @@ describe("Linux rootfs", () => {
55
55
  expect(r.stdout).toContain("1.0.0+itsrealfortune");
56
56
  });
57
57
 
58
- test("/sys/devices/virtual/dmi/id/sys_vendor exists", async () => {
58
+ test("/ exists", async () => {
59
59
  const r = await client.cat("/sys/devices/virtual/dmi/id/sys_vendor");
60
60
  expect(r.exitCode).toBe(0);
61
61
  expect(r.stdout?.trim()).toBe("Fortune Systems");
@@ -381,7 +381,7 @@ describe("Extra commands", () => {
381
381
  test("lsb_release -a returns distro info", async () => {
382
382
  const r = await client.exec("lsb_release -a");
383
383
  expect(r.stdout).toContain("Fortune");
384
- expect(r.stdout).toContain("aurora");
384
+ expect(r.stdout).toContain("nyx");
385
385
  });
386
386
 
387
387
  test("lsb_release -d returns description", async () => {
@@ -69,7 +69,7 @@ describe("SftpMimic", () => {
69
69
 
70
70
  await users.initialize();
71
71
 
72
- const rootPath = "/home/root";
72
+ const rootPath = "/root";
73
73
  if (!vfs.exists(rootPath)) {
74
74
  vfs.mkdir(rootPath, 0o755);
75
75
  }
@@ -89,7 +89,7 @@ describe("SftpMimic", () => {
89
89
  const list = await new Promise<FileEntryWithStats[]>(
90
90
  (resolve, reject) => {
91
91
  sftp.readdir(
92
- "/home/root",
92
+ "/root",
93
93
  (err?: Error | null, list?: FileEntryWithStats[]) => {
94
94
  if (err) {
95
95
  reject(err);
@@ -105,7 +105,7 @@ describe("SftpMimic", () => {
105
105
 
106
106
  const content = await new Promise<string>((resolve, reject) => {
107
107
  sftp.readFile(
108
- "/home/root/TEST.txt",
108
+ "/root/TEST.txt",
109
109
  "utf8",
110
110
  (err?: Error | null, data?: Buffer) => {
111
111
  if (err) {
@@ -130,7 +130,7 @@ describe("SftpMimic", () => {
130
130
 
131
131
  await users.initialize();
132
132
 
133
- const rootPath = "/home/root";
133
+ const rootPath = "/root";
134
134
  if (!vfs.exists(rootPath)) {
135
135
  vfs.mkdir(rootPath, 0o755);
136
136
  }
@@ -146,7 +146,7 @@ describe("SftpMimic", () => {
146
146
  try {
147
147
  const { client, sftp } = await connectSftp(port);
148
148
 
149
- // /etc/passwd is outside /home/root — should be rejected
149
+ // /etc/passwd is outside /root — should be rejected
150
150
  const traversalAttempt = await new Promise<Error | null>((resolve) => {
151
151
  sftp.stat("/etc/passwd", (err?: Error | null) => {
152
152
  resolve(err ?? null);
@@ -156,11 +156,11 @@ describe("SftpMimic", () => {
156
156
  expect(traversalAttempt).not.toBeNull();
157
157
  expect(traversalAttempt?.message).toContain("Permission denied");
158
158
 
159
- // /home/root itself should work
159
+ // /root itself should work
160
160
  const homeAccess = await new Promise<FileEntryWithStats[]>(
161
161
  (resolve, reject) => {
162
162
  sftp.readdir(
163
- "/home/root",
163
+ "/root",
164
164
  (err?: Error | null, list?: FileEntryWithStats[]) => {
165
165
  if (err) {
166
166
  reject(err);
@@ -175,7 +175,7 @@ describe("SftpMimic", () => {
175
175
 
176
176
  // Path traversal via ../.. should also be rejected
177
177
  const upTraversalAttempt = await new Promise<Error | null>((resolve) => {
178
- sftp.readdir("/home/root/../../etc", (err?: Error | null) => {
178
+ sftp.readdir("/root/../../etc", (err?: Error | null) => {
179
179
  resolve(err ?? null);
180
180
  });
181
181
  });
@@ -267,8 +267,8 @@ describe("SftpMimic", () => {
267
267
 
268
268
  await users.initialize();
269
269
 
270
- if (!vfs.exists("/home/root")) {
271
- vfs.mkdir("/home/root", 0o755);
270
+ if (!vfs.exists("/root")) {
271
+ vfs.mkdir("/root", 0o755);
272
272
  }
273
273
 
274
274
  const server = new SftpMimic({
@@ -284,7 +284,7 @@ describe("SftpMimic", () => {
284
284
 
285
285
  await new Promise<void>((resolve, reject) => {
286
286
  sftp.writeFile(
287
- "/home/root/written.txt",
287
+ "/root/written.txt",
288
288
  Buffer.from("written via sftp"),
289
289
  (err?: Error | null) => {
290
290
  if (err) {
@@ -298,7 +298,7 @@ describe("SftpMimic", () => {
298
298
 
299
299
  const content = await new Promise<string>((resolve, reject) => {
300
300
  sftp.readFile(
301
- "/home/root/written.txt",
301
+ "/root/written.txt",
302
302
  "utf8",
303
303
  (err?: Error | null, data?: Buffer) => {
304
304
  if (err) {
@@ -313,7 +313,7 @@ describe("SftpMimic", () => {
313
313
  expect(content).toBe("written via sftp");
314
314
 
315
315
  // Also verify it landed in the in-memory VFS
316
- expect(vfs.readFile("/home/root/written.txt")).toBe("written via sftp");
316
+ expect(vfs.readFile("/root/written.txt")).toBe("written via sftp");
317
317
 
318
318
  client.end();
319
319
  } finally {