typescript-virtual-container 1.5.3 → 1.5.4

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 (365) hide show
  1. package/README.md +43 -23
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/SSHMimic/executor.js +23 -5
  4. package/dist/VirtualPackageManager/index.js +10 -0
  5. package/dist/commands/basename.d.ts +13 -0
  6. package/dist/commands/basename.js +45 -0
  7. package/dist/commands/file.d.ts +8 -0
  8. package/dist/commands/file.js +57 -0
  9. package/dist/commands/fun.d.ts +32 -0
  10. package/dist/commands/fun.js +172 -0
  11. package/dist/commands/ifconfig.d.ts +7 -0
  12. package/dist/commands/ifconfig.js +52 -0
  13. package/dist/commands/last.d.ts +13 -0
  14. package/dist/commands/last.js +68 -0
  15. package/dist/commands/manuals-bundle.js +598 -6
  16. package/dist/commands/registry.js +24 -2
  17. package/dist/commands/runtime.js +22 -2
  18. package/dist/commands/sh.js +5 -0
  19. package/dist/commands/tput.d.ts +13 -0
  20. package/dist/commands/tput.js +76 -0
  21. package/dist/commands/w.d.ts +7 -0
  22. package/dist/commands/w.js +38 -0
  23. package/dist/utils/expand.d.ts +12 -0
  24. package/dist/utils/expand.js +84 -0
  25. package/package.json +9 -3
  26. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -50
  27. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -31
  28. package/.github/dependabot.yml +0 -27
  29. package/.github/pull_request_template.md +0 -21
  30. package/.github/workflows/create-pull-request.yml +0 -85
  31. package/.github/workflows/publish.yml +0 -25
  32. package/.github/workflows/test-battery.yml +0 -102
  33. package/.vscode/settings.json +0 -20
  34. package/CODE_OF_CONDUCT.md +0 -39
  35. package/CONTRIBUTING.md +0 -59
  36. package/HONEYPOT.md +0 -358
  37. package/SECURITY.md +0 -33
  38. package/benchmark-results.txt +0 -40
  39. package/benchmark-virtualshell.ts +0 -88
  40. package/biome.json +0 -37
  41. package/build.js +0 -22
  42. package/builds/fortune-nyx-v1.5.3-directbash-k6.1.0.mjs +0 -1764
  43. package/builds/fortune-nyx-v1.5.3-ssh-nosftp.js +0 -1764
  44. package/builds/fortune-nyx-v1.5.3-ssh.cjs +0 -1765
  45. package/builds/fortune-nyx-v1.5.3-web.min.js +0 -17036
  46. package/bun.lock +0 -244
  47. package/docs/.nojekyll +0 -1
  48. package/docs/app.js +0 -1751
  49. package/docs/assets/hierarchy.js +0 -1
  50. package/docs/assets/highlight.css +0 -162
  51. package/docs/assets/icons.js +0 -18
  52. package/docs/assets/icons.svg +0 -1
  53. package/docs/assets/main.js +0 -60
  54. package/docs/assets/navigation.js +0 -1
  55. package/docs/assets/search.js +0 -1
  56. package/docs/assets/style.css +0 -1633
  57. package/docs/classes/HoneyPot.html +0 -31
  58. package/docs/classes/IdleManager.html +0 -162
  59. package/docs/classes/SshClient.html +0 -66
  60. package/docs/classes/VirtualFileSystem.html +0 -279
  61. package/docs/classes/VirtualPackageManager.html +0 -63
  62. package/docs/classes/VirtualSftpServer.html +0 -169
  63. package/docs/classes/VirtualShell.html +0 -285
  64. package/docs/classes/VirtualSshServer.html +0 -182
  65. package/docs/classes/VirtualUserManager.html +0 -276
  66. package/docs/demo.html +0 -82
  67. package/docs/functions/assertDiff.html +0 -6
  68. package/docs/functions/diffSnapshots.html +0 -7
  69. package/docs/functions/formatDiff.html +0 -6
  70. package/docs/functions/getArg.html +0 -13
  71. package/docs/functions/getFlag.html +0 -15
  72. package/docs/functions/ifFlag.html +0 -11
  73. package/docs/hierarchy.html +0 -1
  74. package/docs/index.html +0 -1869
  75. package/docs/interfaces/AuditLogEntry.html +0 -6
  76. package/docs/interfaces/CommandContext.html +0 -22
  77. package/docs/interfaces/CommandResult.html +0 -26
  78. package/docs/interfaces/ExecStream.html +0 -11
  79. package/docs/interfaces/HoneyPotStats.html +0 -16
  80. package/docs/interfaces/IdleManagerOptions.html +0 -7
  81. package/docs/interfaces/InstalledPackage.html +0 -20
  82. package/docs/interfaces/NanoEditorSession.html +0 -8
  83. package/docs/interfaces/PackageDefinition.html +0 -30
  84. package/docs/interfaces/PackageFile.html +0 -8
  85. package/docs/interfaces/PasswordChallenge.html +0 -16
  86. package/docs/interfaces/RemoveOptions.html +0 -4
  87. package/docs/interfaces/ShellEnv.html +0 -6
  88. package/docs/interfaces/ShellModule.html +0 -14
  89. package/docs/interfaces/ShellProperties.html +0 -14
  90. package/docs/interfaces/ShellStream.html +0 -11
  91. package/docs/interfaces/SudoChallenge.html +0 -24
  92. package/docs/interfaces/VfsBaseNode.html +0 -12
  93. package/docs/interfaces/VfsDiff.html +0 -10
  94. package/docs/interfaces/VfsDiffEntry.html +0 -6
  95. package/docs/interfaces/VfsDiffModified.html +0 -10
  96. package/docs/interfaces/VfsDirectoryNode.html +0 -15
  97. package/docs/interfaces/VfsFileNode.html +0 -17
  98. package/docs/interfaces/VfsOptions.html +0 -26
  99. package/docs/interfaces/VfsSnapshot.html +0 -3
  100. package/docs/interfaces/VfsSnapshotBaseNode.html +0 -8
  101. package/docs/interfaces/VfsSnapshotDirectoryNode.html +0 -10
  102. package/docs/interfaces/VfsSnapshotFileNode.html +0 -12
  103. package/docs/interfaces/VirtualActiveSession.html +0 -12
  104. package/docs/interfaces/VirtualSftpServerOptions.html +0 -7
  105. package/docs/interfaces/VirtualShellVfsLike.html +0 -15
  106. package/docs/interfaces/VirtualShellVfsOptions.html +0 -3
  107. package/docs/interfaces/WriteFileOptions.html +0 -6
  108. package/docs/media/LICENSE +0 -21
  109. package/docs/modules.html +0 -1
  110. package/docs/types/ArgParseOptions.html +0 -4
  111. package/docs/types/CommandMode.html +0 -2
  112. package/docs/types/CommandOutcome.html +0 -2
  113. package/docs/types/IdleState.html +0 -1
  114. package/docs/types/VfsNodeStats.html +0 -2
  115. package/docs/types/VfsNodeType.html +0 -2
  116. package/docs/types/VfsPersistenceMode.html +0 -5
  117. package/docs/types/VfsSnapshotNode.html +0 -2
  118. package/examples/README.md +0 -288
  119. package/examples/app.js +0 -1751
  120. package/examples/app.ts +0 -299
  121. package/examples/build.js +0 -27
  122. package/examples/demo.html +0 -33
  123. package/examples/honeypot-audit.ts +0 -180
  124. package/examples/honeypot-export.ts +0 -253
  125. package/examples/honeypot-quickstart.ts +0 -110
  126. package/examples/index.html +0 -82
  127. package/examples/server.js +0 -55
  128. package/polyfills/buffer.js +0 -117
  129. package/polyfills/node_child_process/index.js +0 -2
  130. package/polyfills/node_crypto/index.js +0 -167
  131. package/polyfills/node_events/index.js +0 -9
  132. package/polyfills/node_fs/index.js +0 -202
  133. package/polyfills/node_fs/promises.js +0 -4
  134. package/polyfills/node_os/index.js +0 -9
  135. package/polyfills/node_path/index.js +0 -28
  136. package/polyfills/node_vm/index.js +0 -7
  137. package/polyfills/node_zlib/index.js +0 -3
  138. package/polyfills/process.js +0 -14
  139. package/polyfills/ssh2/index.js +0 -75
  140. package/scripts/build-all.mjs +0 -226
  141. package/scripts/build-names.mjs +0 -43
  142. package/scripts/generate-manuals-bundle.mjs +0 -49
  143. package/scripts/postinstall.js +0 -42
  144. package/scripts/publish-package.sh +0 -70
  145. package/src/Honeypot/index.ts +0 -457
  146. package/src/SSHClient/index.ts +0 -270
  147. package/src/SSHMimic/exec.ts +0 -49
  148. package/src/SSHMimic/executor.ts +0 -251
  149. package/src/SSHMimic/hostKey.ts +0 -21
  150. package/src/SSHMimic/index.ts +0 -337
  151. package/src/SSHMimic/loginBanner.ts +0 -36
  152. package/src/SSHMimic/loginFormat.ts +0 -10
  153. package/src/SSHMimic/prompt.ts +0 -14
  154. package/src/SSHMimic/sftp.ts +0 -883
  155. package/src/VirtualFileSystem/binaryPack.ts +0 -258
  156. package/src/VirtualFileSystem/index.ts +0 -1193
  157. package/src/VirtualFileSystem/internalTypes.ts +0 -43
  158. package/src/VirtualFileSystem/journal.ts +0 -171
  159. package/src/VirtualFileSystem/path.ts +0 -74
  160. package/src/VirtualPackageManager/index.ts +0 -996
  161. package/src/VirtualShell/idleManager.ts +0 -137
  162. package/src/VirtualShell/index.ts +0 -475
  163. package/src/VirtualShell/shell.ts +0 -700
  164. package/src/VirtualShell/shellParser.ts +0 -285
  165. package/src/VirtualUserManager/index.ts +0 -758
  166. package/src/bun.d.ts +0 -1
  167. package/src/commands/adduser.ts +0 -103
  168. package/src/commands/alias.ts +0 -69
  169. package/src/commands/apt.ts +0 -233
  170. package/src/commands/awk.ts +0 -168
  171. package/src/commands/base64.ts +0 -29
  172. package/src/commands/cat.ts +0 -52
  173. package/src/commands/cd.ts +0 -25
  174. package/src/commands/chmod.ts +0 -85
  175. package/src/commands/clear.ts +0 -15
  176. package/src/commands/command-helpers.ts +0 -286
  177. package/src/commands/cp.ts +0 -83
  178. package/src/commands/curl.ts +0 -147
  179. package/src/commands/cut.ts +0 -36
  180. package/src/commands/date.ts +0 -30
  181. package/src/commands/declare.ts +0 -49
  182. package/src/commands/deluser.ts +0 -98
  183. package/src/commands/df.ts +0 -23
  184. package/src/commands/diff.ts +0 -43
  185. package/src/commands/dpkg.ts +0 -180
  186. package/src/commands/du.ts +0 -56
  187. package/src/commands/echo.ts +0 -58
  188. package/src/commands/env.ts +0 -23
  189. package/src/commands/exit.ts +0 -18
  190. package/src/commands/export.ts +0 -34
  191. package/src/commands/find.ts +0 -68
  192. package/src/commands/free.ts +0 -47
  193. package/src/commands/grep.ts +0 -116
  194. package/src/commands/groups.ts +0 -19
  195. package/src/commands/gzip.ts +0 -88
  196. package/src/commands/head.ts +0 -52
  197. package/src/commands/help.ts +0 -152
  198. package/src/commands/helpers.ts +0 -234
  199. package/src/commands/history.ts +0 -34
  200. package/src/commands/hostname.ts +0 -14
  201. package/src/commands/htop.ts +0 -20
  202. package/src/commands/id.ts +0 -19
  203. package/src/commands/index.ts +0 -9
  204. package/src/commands/kill.ts +0 -19
  205. package/src/commands/ln.ts +0 -71
  206. package/src/commands/ls.ts +0 -243
  207. package/src/commands/lsb-release.ts +0 -63
  208. package/src/commands/man.ts +0 -31
  209. package/src/commands/manuals/adduser.txt +0 -11
  210. package/src/commands/manuals/apt-cache.txt +0 -12
  211. package/src/commands/manuals/apt.txt +0 -20
  212. package/src/commands/manuals/awk.txt +0 -13
  213. package/src/commands/manuals/cat.txt +0 -14
  214. package/src/commands/manuals/cd.txt +0 -16
  215. package/src/commands/manuals/chmod.txt +0 -16
  216. package/src/commands/manuals/clear.txt +0 -10
  217. package/src/commands/manuals/cp.txt +0 -10
  218. package/src/commands/manuals/curl.txt +0 -20
  219. package/src/commands/manuals/date.txt +0 -14
  220. package/src/commands/manuals/declare.txt +0 -12
  221. package/src/commands/manuals/deluser.txt +0 -10
  222. package/src/commands/manuals/df.txt +0 -10
  223. package/src/commands/manuals/dpkg-query.txt +0 -11
  224. package/src/commands/manuals/dpkg.txt +0 -14
  225. package/src/commands/manuals/du.txt +0 -11
  226. package/src/commands/manuals/echo.txt +0 -11
  227. package/src/commands/manuals/false.txt +0 -10
  228. package/src/commands/manuals/find.txt +0 -11
  229. package/src/commands/manuals/free.txt +0 -12
  230. package/src/commands/manuals/grep.txt +0 -13
  231. package/src/commands/manuals/groups.txt +0 -10
  232. package/src/commands/manuals/gzip.txt +0 -11
  233. package/src/commands/manuals/head.txt +0 -10
  234. package/src/commands/manuals/help.txt +0 -11
  235. package/src/commands/manuals/history.txt +0 -11
  236. package/src/commands/manuals/hostname.txt +0 -10
  237. package/src/commands/manuals/id.txt +0 -10
  238. package/src/commands/manuals/kill.txt +0 -13
  239. package/src/commands/manuals/ls.txt +0 -20
  240. package/src/commands/manuals/lsb_release.txt +0 -14
  241. package/src/commands/manuals/mkdir.txt +0 -10
  242. package/src/commands/manuals/mv.txt +0 -10
  243. package/src/commands/manuals/nano.txt +0 -11
  244. package/src/commands/manuals/neofetch.txt +0 -10
  245. package/src/commands/manuals/node.txt +0 -13
  246. package/src/commands/manuals/npm.txt +0 -13
  247. package/src/commands/manuals/npx.txt +0 -13
  248. package/src/commands/manuals/passwd.txt +0 -11
  249. package/src/commands/manuals/ping.txt +0 -10
  250. package/src/commands/manuals/printf.txt +0 -11
  251. package/src/commands/manuals/ps.txt +0 -10
  252. package/src/commands/manuals/pwd.txt +0 -10
  253. package/src/commands/manuals/python3.txt +0 -13
  254. package/src/commands/manuals/readlink.txt +0 -10
  255. package/src/commands/manuals/return.txt +0 -10
  256. package/src/commands/manuals/rm.txt +0 -10
  257. package/src/commands/manuals/sed.txt +0 -11
  258. package/src/commands/manuals/set.txt +0 -11
  259. package/src/commands/manuals/shift.txt +0 -10
  260. package/src/commands/manuals/sleep.txt +0 -10
  261. package/src/commands/manuals/sort.txt +0 -12
  262. package/src/commands/manuals/source.txt +0 -11
  263. package/src/commands/manuals/ssh.txt +0 -11
  264. package/src/commands/manuals/stat.txt +0 -10
  265. package/src/commands/manuals/su.txt +0 -13
  266. package/src/commands/manuals/sudo.txt +0 -11
  267. package/src/commands/manuals/tail.txt +0 -10
  268. package/src/commands/manuals/tar.txt +0 -19
  269. package/src/commands/manuals/tee.txt +0 -10
  270. package/src/commands/manuals/test.txt +0 -11
  271. package/src/commands/manuals/touch.txt +0 -11
  272. package/src/commands/manuals/tr.txt +0 -10
  273. package/src/commands/manuals/trap.txt +0 -10
  274. package/src/commands/manuals/true.txt +0 -10
  275. package/src/commands/manuals/type.txt +0 -10
  276. package/src/commands/manuals/uname.txt +0 -12
  277. package/src/commands/manuals/uniq.txt +0 -12
  278. package/src/commands/manuals/unset.txt +0 -10
  279. package/src/commands/manuals/uptime.txt +0 -11
  280. package/src/commands/manuals/wc.txt +0 -12
  281. package/src/commands/manuals/wget.txt +0 -12
  282. package/src/commands/manuals/which.txt +0 -10
  283. package/src/commands/manuals/whoami.txt +0 -10
  284. package/src/commands/manuals/xargs.txt +0 -10
  285. package/src/commands/manuals-bundle.ts +0 -898
  286. package/src/commands/mkdir.ts +0 -31
  287. package/src/commands/mv.ts +0 -50
  288. package/src/commands/nano.ts +0 -38
  289. package/src/commands/neofetch.ts +0 -53
  290. package/src/commands/node.ts +0 -341
  291. package/src/commands/npm.ts +0 -132
  292. package/src/commands/passwd.ts +0 -50
  293. package/src/commands/ping.ts +0 -32
  294. package/src/commands/printf.ts +0 -129
  295. package/src/commands/ps.ts +0 -58
  296. package/src/commands/pwd.ts +0 -9
  297. package/src/commands/python.ts +0 -2229
  298. package/src/commands/read.ts +0 -46
  299. package/src/commands/registry.ts +0 -249
  300. package/src/commands/rm.ts +0 -42
  301. package/src/commands/runtime.ts +0 -421
  302. package/src/commands/sed.ts +0 -68
  303. package/src/commands/seq.ts +0 -43
  304. package/src/commands/set.ts +0 -29
  305. package/src/commands/sh.ts +0 -467
  306. package/src/commands/shift.ts +0 -63
  307. package/src/commands/sleep.ts +0 -20
  308. package/src/commands/sort.ts +0 -46
  309. package/src/commands/source.ts +0 -52
  310. package/src/commands/stat.ts +0 -61
  311. package/src/commands/su.ts +0 -72
  312. package/src/commands/sudo.ts +0 -76
  313. package/src/commands/tail.ts +0 -53
  314. package/src/commands/tar.ts +0 -102
  315. package/src/commands/tee.ts +0 -36
  316. package/src/commands/test.ts +0 -137
  317. package/src/commands/touch.ts +0 -28
  318. package/src/commands/tr.ts +0 -70
  319. package/src/commands/tree.ts +0 -20
  320. package/src/commands/true.ts +0 -27
  321. package/src/commands/type.ts +0 -48
  322. package/src/commands/uname.ts +0 -29
  323. package/src/commands/uniq.ts +0 -39
  324. package/src/commands/unset.ts +0 -17
  325. package/src/commands/uptime.ts +0 -54
  326. package/src/commands/wc.ts +0 -55
  327. package/src/commands/wget.ts +0 -148
  328. package/src/commands/which.ts +0 -37
  329. package/src/commands/who.ts +0 -25
  330. package/src/commands/whoami.ts +0 -14
  331. package/src/commands/xargs.ts +0 -31
  332. package/src/index.ts +0 -67
  333. package/src/modules/linuxRootfs.ts +0 -1961
  334. package/src/modules/neofetch.ts +0 -358
  335. package/src/modules/shellInteractive.ts +0 -57
  336. package/src/modules/shellRuntime.ts +0 -76
  337. package/src/self-standalone.ts +0 -542
  338. package/src/standalone-wo-sftp.ts +0 -38
  339. package/src/standalone.ts +0 -72
  340. package/src/types/commands.ts +0 -146
  341. package/src/types/pipeline.ts +0 -52
  342. package/src/types/streams.ts +0 -32
  343. package/src/types/tar-stream.d.ts +0 -38
  344. package/src/types/vfs.ts +0 -98
  345. package/src/utils/expand.ts +0 -491
  346. package/src/utils/perfLogger.ts +0 -72
  347. package/src/utils/tokenize.ts +0 -98
  348. package/src/utils/vfsDiff.ts +0 -275
  349. package/tests/command-helpers.test.ts +0 -116
  350. package/tests/commands-admin-net.test.ts +0 -441
  351. package/tests/commands-advanced.test.ts +0 -456
  352. package/tests/commands-core.test.ts +0 -562
  353. package/tests/commands-missing.test.ts +0 -570
  354. package/tests/commands-specific-units.test.ts +0 -327
  355. package/tests/commands-text-sys.test.ts +0 -445
  356. package/tests/expand.test.ts +0 -170
  357. package/tests/helpers.test.ts +0 -97
  358. package/tests/new-features.test.ts +0 -1036
  359. package/tests/parser-executor.test.ts +0 -37
  360. package/tests/sftp.test.ts +0 -323
  361. package/tests/ssh-exec.test.ts +0 -45
  362. package/tests/test-helper.ts +0 -79
  363. package/tests/users.test.ts +0 -86
  364. package/tsconfig.json +0 -49
  365. package/typedoc.json +0 -47
@@ -1,49 +0,0 @@
1
- import { makeDefaultEnv, runCommand, userHome } from "../commands";
2
- import type { ExecStream } from "../types/streams";
3
- import type { VirtualShell } from "../VirtualShell";
4
-
5
- function toTtyLines(text: string): string {
6
- return text
7
- .replace(/\r\n/g, "\n")
8
- .replace(/\r/g, "\n")
9
- .replace(/\n/g, "\r\n");
10
- }
11
-
12
- export function runExec(
13
- stream: ExecStream,
14
- cmd: string,
15
- authUser: string,
16
- hostname: string,
17
- shell: VirtualShell,
18
- ): void {
19
- Promise.resolve(
20
- runCommand(
21
- cmd,
22
- authUser,
23
- hostname,
24
- "exec",
25
- userHome(authUser),
26
- shell,
27
- undefined,
28
- makeDefaultEnv(authUser, hostname),
29
- ),
30
- )
31
- .then((result) => {
32
- if (result.stdout) {
33
- stream.write(`${toTtyLines(result.stdout)}\r\n`);
34
- }
35
-
36
- if (result.stderr) {
37
- stream.stderr.write(`${toTtyLines(result.stderr)}\r\n`);
38
- }
39
-
40
- stream.exit(result.exitCode ?? 0);
41
- stream.end();
42
- })
43
- .catch((error) => {
44
- console.error("Exec error:", error);
45
- stream.stderr.write(`Error: ${String(error)}\r\n`);
46
- stream.exit(1);
47
- stream.end();
48
- });
49
- }
@@ -1,251 +0,0 @@
1
- import { runCommandDirect } from "../commands";
2
- import { resolvePath } from "../commands/helpers";
3
- import type { CommandMode, CommandResult, ShellEnv } from "../types/commands";
4
- import type {
5
- Pipeline,
6
- PipelineCommand,
7
- Statement,
8
- } from "../types/pipeline";
9
- import type { VirtualShell } from "../VirtualShell";
10
-
11
- // ── Script executor (handles &&/||/;) ────────────────────────────────────────
12
-
13
-
14
- export async function executeStatements(
15
- statements: Statement[],
16
- authUser: string,
17
- hostname: string,
18
- mode: CommandMode,
19
- cwd: string,
20
- shell: VirtualShell,
21
- env: ShellEnv,
22
- ): Promise<CommandResult> {
23
- let last: CommandResult = { exitCode: 0 };
24
- const accumulatedStdout: string[] = [];
25
- let currentCwd = cwd; // track cwd changes from cd, su, etc.
26
- let i = 0;
27
-
28
- while (i < statements.length) {
29
- const stmt = statements[i]!;
30
- last = await executePipeline(
31
- stmt.pipeline,
32
- authUser,
33
- hostname,
34
- mode,
35
- currentCwd,
36
- shell,
37
- env,
38
- );
39
- env.lastExitCode = last.exitCode ?? 0;
40
-
41
- // Propagate cwd changes (cd, su -l, etc.)
42
- if (last.nextCwd && (last.exitCode ?? 0) === 0) {
43
- currentCwd = last.nextCwd;
44
- }
45
-
46
- // Collect stdout from each statement (for echo a; echo b → "a\nb\n")
47
- if (last.stdout) accumulatedStdout.push(last.stdout);
48
-
49
- if (last.closeSession || last.switchUser) {
50
- return {
51
- ...last,
52
- stdout: accumulatedStdout.join("") || last.stdout,
53
- };
54
- }
55
-
56
- const op = stmt.op;
57
- if (!op || op === ";") {
58
- // always run next
59
- } else if (op === "&&") {
60
- if ((last.exitCode ?? 0) !== 0) {
61
- // skip until next ; or end
62
- while (i < statements.length && statements[i]?.op === "&&") i++;
63
- }
64
- } else if (op === "||") {
65
- if ((last.exitCode ?? 0) === 0) {
66
- // skip until next ; or end
67
- while (i < statements.length && statements[i]?.op === "||") i++;
68
- }
69
- }
70
- i++;
71
- }
72
- // Merge accumulated stdout (for "echo a; echo b" → "a\nb\n")
73
- const merged = accumulatedStdout.join("");
74
- // Preserve the deepest cwd change across the whole pipeline
75
- return { ...last, stdout: merged || last.stdout, nextCwd: currentCwd !== cwd ? currentCwd : undefined };
76
- }
77
-
78
- // ── Pipeline executor ─────────────────────────────────────────────────────────
79
-
80
- export async function executePipeline(
81
- pipeline: Pipeline,
82
- authUser: string,
83
- hostname: string,
84
- mode: CommandMode,
85
- cwd: string,
86
- shell: VirtualShell,
87
- env?: ShellEnv,
88
- ): Promise<CommandResult> {
89
- if (!pipeline.isValid)
90
- return { stderr: pipeline.error || "Syntax error", exitCode: 1 };
91
- if (pipeline.commands.length === 0) return { exitCode: 0 };
92
-
93
- const shellEnv: ShellEnv = env ?? { vars: {}, lastExitCode: 0 };
94
-
95
- if (pipeline.commands.length === 1) {
96
- return executeSingleCommandWithRedirections(
97
- pipeline.commands[0] as PipelineCommand,
98
- authUser,
99
- hostname,
100
- mode,
101
- cwd,
102
- shell,
103
- shellEnv,
104
- );
105
- }
106
-
107
- return executePipelineChain(
108
- pipeline.commands as PipelineCommand[],
109
- authUser,
110
- hostname,
111
- mode,
112
- cwd,
113
- shell,
114
- shellEnv,
115
- );
116
- }
117
-
118
- async function executeSingleCommandWithRedirections(
119
- cmd: PipelineCommand,
120
- authUser: string,
121
- hostname: string,
122
- mode: CommandMode,
123
- cwd: string,
124
- shell: VirtualShell,
125
- env: ShellEnv,
126
- ): Promise<CommandResult> {
127
- let stdin: string | undefined;
128
- if (cmd.inputFile) {
129
- const inputPath = resolvePath(cwd, cmd.inputFile);
130
- try {
131
- stdin = shell.vfs.readFile(inputPath);
132
- } catch {
133
- return {
134
- stderr: `${cmd.inputFile}: No such file or directory`,
135
- exitCode: 1,
136
- };
137
- }
138
- }
139
-
140
- const result = await runCommandDirect(
141
- cmd.name,
142
- cmd.args,
143
- authUser,
144
- hostname,
145
- mode,
146
- cwd,
147
- shell,
148
- stdin,
149
- env,
150
- );
151
-
152
- if (cmd.outputFile) {
153
- const outputPath = resolvePath(cwd, cmd.outputFile);
154
- const output = result.stdout || "";
155
- try {
156
- if (cmd.appendOutput) {
157
- const existing = (() => {
158
- try {
159
- return shell.vfs.readFile(outputPath);
160
- } catch {
161
- return "";
162
- }
163
- })();
164
- shell.writeFileAsUser(authUser, outputPath, existing + output);
165
- } else {
166
- shell.writeFileAsUser(authUser, outputPath, output);
167
- }
168
- return { ...result, stdout: "" };
169
- } catch {
170
- return {
171
- ...result,
172
- stderr: `Failed to write to ${cmd.outputFile}`,
173
- exitCode: 1,
174
- };
175
- }
176
- }
177
-
178
- return result;
179
- }
180
-
181
- async function executePipelineChain(
182
- commands: PipelineCommand[],
183
- authUser: string,
184
- hostname: string,
185
- mode: CommandMode,
186
- cwd: string,
187
- shell: VirtualShell,
188
- env: ShellEnv,
189
- ): Promise<CommandResult> {
190
- let currentOutput = "";
191
- let exitCode = 0;
192
-
193
- for (let i = 0; i < commands.length; i++) {
194
- const cmd = commands[i] as PipelineCommand;
195
-
196
- if (i === 0 && cmd.inputFile) {
197
- const inputPath = resolvePath(cwd, cmd.inputFile);
198
- try {
199
- currentOutput = shell.vfs.readFile(inputPath);
200
- } catch {
201
- return {
202
- stderr: `${cmd.inputFile}: No such file or directory`,
203
- exitCode: 1,
204
- };
205
- }
206
- }
207
-
208
- const result = await runCommandDirect(
209
- cmd.name,
210
- cmd.args,
211
- authUser,
212
- hostname,
213
- mode,
214
- cwd,
215
- shell,
216
- currentOutput,
217
- env,
218
- );
219
- exitCode = result.exitCode ?? 0;
220
-
221
- if (i === commands.length - 1 && cmd.outputFile) {
222
- const outputPath = resolvePath(cwd, cmd.outputFile);
223
- const output = result.stdout || "";
224
- try {
225
- if (cmd.appendOutput) {
226
- const existing = (() => {
227
- try {
228
- return shell.vfs.readFile(outputPath);
229
- } catch {
230
- return "";
231
- }
232
- })();
233
- shell.writeFileAsUser(authUser, outputPath, existing + output);
234
- } else {
235
- shell.writeFileAsUser(authUser, outputPath, output);
236
- }
237
- currentOutput = "";
238
- } catch {
239
- return { stderr: `Failed to write to ${cmd.outputFile}`, exitCode: 1 };
240
- }
241
- } else {
242
- currentOutput = result.stdout || "";
243
- }
244
-
245
- if (result.stderr && exitCode !== 0)
246
- return { stderr: result.stderr, exitCode };
247
- if (result.closeSession || result.switchUser) return result;
248
- }
249
-
250
- return { stdout: currentOutput, exitCode };
251
- }
@@ -1,21 +0,0 @@
1
- import { generateKeyPairSync } from "node:crypto";
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import { dirname, resolve } from "node:path";
4
-
5
- export function loadOrCreateHostKey(baseDir: string = process.cwd()): string {
6
- const hostKeyPath = resolve(baseDir, ".ssh-mimic", "host_rsa");
7
-
8
- if (existsSync(hostKeyPath)) {
9
- return readFileSync(hostKeyPath, "utf8");
10
- }
11
-
12
- const privateKey = generateKeyPairSync("rsa", {
13
- modulusLength: 2048,
14
- privateKeyEncoding: { type: "pkcs1", format: "pem" },
15
- publicKeyEncoding: { type: "pkcs1", format: "pem" },
16
- }).privateKey;
17
-
18
- mkdirSync(dirname(hostKeyPath), { recursive: true });
19
- writeFileSync(hostKeyPath, privateKey, { mode: 0o600 });
20
- return privateKey;
21
- }
@@ -1,337 +0,0 @@
1
- import { EventEmitter } from "node:events";
2
- import { Server as SshServer } from "ssh2";
3
- import { VirtualShell } from "../VirtualShell";
4
- import { userHome } from "../commands";
5
- import { createPerfLogger, type PerfLogger } from "../utils/perfLogger";
6
- import { runExec } from "./exec";
7
- import { loadOrCreateHostKey } from "./hostKey";
8
-
9
- /**
10
- * SSH server facade that wires the virtual shell runtime into ssh2 sessions.
11
- *
12
- * This class is exported as `VirtualSshServer` for public API compatibility.
13
- * Create an instance, call {@link SshMimic.start}, and stop it with
14
- * {@link SshMimic.stop} when your process exits.
15
- *
16
- * Features:
17
- * - Password authentication
18
- * - Public-key authentication
19
- * - Per-IP rate limiting / lockout for brute-force protection
20
- * - Interactive shell sessions
21
- * - Non-interactive exec sessions
22
- */
23
- const perf: PerfLogger = createPerfLogger("SshMimic");
24
-
25
- // ── Dev-mode logger ───────────────────────────────────────────────────────────
26
- const DEV = !!process.env.DEV_MODE;
27
- const devLog = DEV ? console.log.bind(console) : () => {};
28
-
29
-
30
- /** @internal */
31
- interface RateLimitEntry {
32
- attempts: number;
33
- lockedUntil: number;
34
- }
35
-
36
- class SshMimic extends EventEmitter {
37
- port: number;
38
- server: SshServer | null;
39
- private shell: VirtualShell;
40
-
41
- /** Max failed auth attempts before an IP is temporarily locked. */
42
- private readonly maxAuthAttempts: number;
43
- /** How long (ms) a locked IP must wait before retrying. */
44
- private readonly lockoutDurationMs: number;
45
- private readonly authAttempts = new Map<string, RateLimitEntry>();
46
-
47
- /**
48
- * Creates a new SSH mimic server instance.
49
- *
50
- * @param options - Configuration object for the SSH server.
51
- * @param options.port - TCP port to bind on localhost.
52
- * @param options.hostname - Virtual hostname used for the SSH ident and default shell label.
53
- * @param options.shell - Optional preconfigured virtual shell instance to reuse.
54
- * @param options.maxAuthAttempts - Max failed attempts per IP before lockout (default: 5).
55
- * @param options.lockoutDurationMs - Lockout window in ms after exceeding attempts (default: 60 000).
56
- */
57
- constructor({
58
- port,
59
- hostname = "typescript-vm",
60
- shell = new VirtualShell(hostname),
61
- maxAuthAttempts = 5,
62
- lockoutDurationMs = 60_000,
63
- }: {
64
- port: number;
65
- hostname?: string;
66
- shell?: VirtualShell;
67
- maxAuthAttempts?: number;
68
- lockoutDurationMs?: number;
69
- }) {
70
- super();
71
- perf.mark("constructor");
72
- this.port = port;
73
- this.server = null;
74
- this.shell = shell;
75
- this.maxAuthAttempts = maxAuthAttempts;
76
- this.lockoutDurationMs = lockoutDurationMs;
77
- }
78
-
79
- // ── Rate limiting ────────────────────────────────────────────────────────
80
-
81
- private isLockedOut(ip: string): boolean {
82
- const entry = this.authAttempts.get(ip);
83
- if (!entry) return false;
84
- if (Date.now() < entry.lockedUntil) return true;
85
- if (entry.lockedUntil > 0) {
86
- this.authAttempts.delete(ip);
87
- }
88
- return false;
89
- }
90
-
91
- private recordFailure(ip: string): void {
92
- const entry = this.authAttempts.get(ip) ?? { attempts: 0, lockedUntil: 0 };
93
- entry.attempts += 1;
94
- if (entry.attempts >= this.maxAuthAttempts) {
95
- entry.lockedUntil = Date.now() + this.lockoutDurationMs;
96
- this.emit("auth:lockout", { ip, until: new Date(entry.lockedUntil) });
97
- }
98
- this.authAttempts.set(ip, entry);
99
- }
100
-
101
- private recordSuccess(ip: string): void {
102
- this.authAttempts.delete(ip);
103
- }
104
-
105
- // ── Home directory bootstrap ─────────────────────────────────────────────
106
-
107
- private ensureHomeDir(authUser: string): void {
108
- const homePath = userHome(authUser);
109
- if (!this.shell.vfs.exists(homePath)) {
110
- this.shell.vfs.mkdir(homePath, 0o755);
111
- this.shell.vfs.writeFile(
112
- `${homePath}/README.txt`,
113
- `Welcome to ${this.shell.hostname}\n`,
114
- );
115
- void this.shell.vfs.stopAutoFlush();
116
- }
117
- }
118
-
119
- // ── Server lifecycle ─────────────────────────────────────────────────────
120
-
121
- /**
122
- * Starts server and initializes virtual filesystem, users, and handlers.
123
- *
124
- * @returns Promise resolved with bound listening port.
125
- */
126
- public async start(): Promise<number> {
127
- perf.mark("start");
128
- const shell = this.shell;
129
- const privateKey = loadOrCreateHostKey();
130
-
131
- await shell.ensureInitialized();
132
-
133
- this.server = new SshServer(
134
- {
135
- hostKeys: [privateKey],
136
- ident: `SSH-2.0-${shell.hostname}`,
137
- },
138
- (client) => {
139
- let authUser = "root";
140
- let remoteAddress = "unknown";
141
- let sessionId: string | null = null;
142
-
143
- this.emit("client:connect");
144
-
145
- client.on("authentication", (ctx) => {
146
- const candidateUser = ctx.username || "root";
147
- remoteAddress = (ctx as { ip?: string }).ip ?? remoteAddress;
148
-
149
- // Rate-limit check
150
- if (this.isLockedOut(remoteAddress)) {
151
- this.emit("auth:failure", {
152
- username: candidateUser,
153
- remoteAddress,
154
- reason: "lockout",
155
- });
156
- ctx.reject();
157
- return;
158
- }
159
-
160
- // ── Password auth ──────────────────────────────────────
161
- if (ctx.method === "password") {
162
- if (!shell.users.hasPassword(candidateUser)) {
163
- authUser = candidateUser;
164
- sessionId = shell.users.registerSession(
165
- authUser,
166
- remoteAddress,
167
- ).id;
168
- this.recordSuccess(remoteAddress);
169
- this.emit("auth:success", { username: authUser, remoteAddress });
170
- this.ensureHomeDir(authUser);
171
- ctx.accept();
172
- return;
173
- }
174
-
175
- if (
176
- !ctx.password ||
177
- ctx.password === "" ||
178
- !shell.users.verifyPassword(candidateUser, ctx.password)
179
- ) {
180
- this.recordFailure(remoteAddress);
181
- this.emit("auth:failure", {
182
- username: candidateUser,
183
- remoteAddress,
184
- });
185
- ctx.reject();
186
- return;
187
- }
188
-
189
- authUser = candidateUser;
190
- sessionId = shell.users.registerSession(authUser, remoteAddress).id;
191
- this.recordSuccess(remoteAddress);
192
- this.emit("auth:success", { username: authUser, remoteAddress });
193
- this.ensureHomeDir(authUser);
194
- ctx.accept();
195
- return;
196
- }
197
-
198
- // ── Public-key auth ────────────────────────────────────
199
- if (ctx.method === "publickey") {
200
- const authorizedKeys = shell.users.getAuthorizedKeys(candidateUser);
201
- if (authorizedKeys.length === 0) {
202
- // No keys configured — reject cleanly
203
- ctx.reject();
204
- return;
205
- }
206
-
207
- const incomingKey = ctx.key;
208
- const keyMatches = authorizedKeys.some(
209
- (k) =>
210
- k.algo === incomingKey.algo && k.data.equals(incomingKey.data),
211
- );
212
-
213
- if (!keyMatches) {
214
- this.recordFailure(remoteAddress);
215
- this.emit("auth:failure", {
216
- username: candidateUser,
217
- remoteAddress,
218
- method: "publickey",
219
- });
220
- ctx.reject();
221
- return;
222
- }
223
-
224
- // Key matched — if this is a signature check step, accept
225
- if (ctx.signature) {
226
- authUser = candidateUser;
227
- sessionId = shell.users.registerSession(
228
- authUser,
229
- remoteAddress,
230
- ).id;
231
- this.recordSuccess(remoteAddress);
232
- this.emit("auth:success", {
233
- username: authUser,
234
- remoteAddress,
235
- method: "publickey",
236
- });
237
- this.ensureHomeDir(authUser);
238
- ctx.accept();
239
- } else {
240
- // Key exists but no signature yet — ssh2 will call again with signature
241
- ctx.accept();
242
- }
243
- return;
244
- }
245
-
246
- ctx.reject(["password", "publickey"]);
247
- });
248
-
249
- client.on("close", () => {
250
- shell.users.unregisterSession(sessionId);
251
- this.emit("client:disconnect", { user: authUser });
252
- sessionId = null;
253
- });
254
-
255
- client.on("ready", () => {
256
- client.on("session", (accept) => {
257
- const session = accept();
258
- const terminalSize = { cols: 80, rows: 24 };
259
-
260
- session.on("pty", (acceptPty, _rejectPty, info) => {
261
- terminalSize.cols = info?.cols ?? terminalSize.cols;
262
- terminalSize.rows = info?.rows ?? terminalSize.rows;
263
- acceptPty();
264
- });
265
-
266
- session.on(
267
- "window-change",
268
- (_acceptChange, _rejectChange, info) => {
269
- terminalSize.cols = info?.cols ?? terminalSize.cols;
270
- terminalSize.rows = info?.rows ?? terminalSize.rows;
271
- },
272
- );
273
-
274
- session.on("shell", (acceptShell) => {
275
- const stream = acceptShell();
276
- shell?.startInteractiveSession(
277
- stream,
278
- authUser,
279
- sessionId,
280
- remoteAddress,
281
- terminalSize,
282
- );
283
- });
284
-
285
- session.on("exec", (acceptExec, _rejectExec, info) => {
286
- const stream = acceptExec();
287
- if (stream) {
288
- runExec(
289
- stream,
290
- info.command.trim(),
291
- authUser,
292
- shell.hostname,
293
- shell,
294
- );
295
- }
296
- });
297
- });
298
- });
299
- },
300
- );
301
-
302
- return new Promise<number>((resolve, reject) => {
303
- this.server?.once("error", (err: unknown) => reject(err));
304
- this.server?.listen(this.port, "0.0.0.0", () => {
305
- devLog(`SSH Mimic listening on port ${this.port}`);
306
- this.emit("start", { port: this.port });
307
- resolve(this.port);
308
- });
309
- });
310
- }
311
-
312
- /**
313
- * Stops server if running.
314
- */
315
- public stop(): void {
316
- perf.mark("stop");
317
- // Flush pending WAL journal before closing
318
- void this.shell.vfs.stopAutoFlush();
319
- if (this.server) {
320
- this.server.close(() => {
321
- devLog("SSH Mimic stopped");
322
- this.emit("stop");
323
- });
324
- }
325
- }
326
-
327
- /**
328
- * Manually clears the rate-limit record for an IP address.
329
- * Useful in tests or admin tooling.
330
- */
331
- public clearLockout(ip: string): void {
332
- this.authAttempts.delete(ip);
333
- }
334
- }
335
-
336
- export { SftpMimic } from "./sftp";
337
- export { SshMimic };
@@ -1,36 +0,0 @@
1
- import type { ShellProperties } from "../VirtualShell";
2
- import { formatLoginDate } from "./loginFormat";
3
-
4
- export interface LoginBannerState {
5
- at: string;
6
- from: string;
7
- }
8
-
9
- export function buildLoginBanner(
10
- hostname: string,
11
- properties: ShellProperties,
12
- lastLogin: LoginBannerState | null,
13
- ): string {
14
- const lines = [
15
- `Linux ${hostname} ${properties.kernel} ${properties.arch}`,
16
- "",
17
- "The programs included with the Fortune GNU/Linux system are free software;",
18
- "the exact distribution terms for each program are described in the",
19
- "individual files in /usr/share/doc/*/copyright.",
20
- "",
21
- "Fortune GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent",
22
- "permitted by applicable law.",
23
- ];
24
-
25
- if (lastLogin) {
26
- const when = new Date(lastLogin.at);
27
- const displayed = Number.isNaN(when.getTime())
28
- ? lastLogin.at
29
- : formatLoginDate(when);
30
- lines.push(`Last login: ${displayed} from ${lastLogin.from || "unknown"}`);
31
- }
32
-
33
- lines.push("");
34
-
35
- return `${lines.map((line) => `${line}\r\n`).join("")}`;
36
- }
@@ -1,10 +0,0 @@
1
- export function formatLoginDate(date: Date): string {
2
- const weekday = date.toLocaleString("en-US", { weekday: "short" });
3
- const month = date.toLocaleString("en-US", { month: "short" });
4
- const day = date.getDate().toString().padStart(2, "0");
5
- const hh = date.getHours().toString().padStart(2, "0");
6
- const mm = date.getMinutes().toString().padStart(2, "0");
7
- const ss = date.getSeconds().toString().padStart(2, "0");
8
- const year = date.getFullYear();
9
- return `${weekday} ${month} ${day} ${hh}:${mm}:${ss} ${year}`;
10
- }