typescript-virtual-container 1.5.3 → 1.5.5

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 (371) hide show
  1. package/README.md +44 -532
  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/VirtualShell/shell.js +158 -11
  6. package/dist/commands/basename.d.ts +13 -0
  7. package/dist/commands/basename.js +45 -0
  8. package/dist/commands/bc.d.ts +2 -0
  9. package/dist/commands/bc.js +28 -0
  10. package/dist/commands/file.d.ts +8 -0
  11. package/dist/commands/file.js +57 -0
  12. package/dist/commands/fun.d.ts +32 -0
  13. package/dist/commands/fun.js +172 -0
  14. package/dist/commands/ip.d.ts +7 -0
  15. package/dist/commands/ip.js +52 -0
  16. package/dist/commands/jobs.d.ts +4 -0
  17. package/dist/commands/jobs.js +27 -0
  18. package/dist/commands/last.d.ts +13 -0
  19. package/dist/commands/last.js +68 -0
  20. package/dist/commands/manuals-bundle.js +598 -6
  21. package/dist/commands/registry.js +30 -2
  22. package/dist/commands/runtime.js +24 -3
  23. package/dist/commands/set.js +20 -0
  24. package/dist/commands/sh.js +74 -1
  25. package/dist/commands/tput.d.ts +13 -0
  26. package/dist/commands/tput.js +76 -0
  27. package/dist/commands/w.d.ts +7 -0
  28. package/dist/commands/w.js +38 -0
  29. package/dist/utils/expand.d.ts +12 -0
  30. package/dist/utils/expand.js +87 -1
  31. package/package.json +9 -3
  32. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -50
  33. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -31
  34. package/.github/dependabot.yml +0 -27
  35. package/.github/pull_request_template.md +0 -21
  36. package/.github/workflows/create-pull-request.yml +0 -85
  37. package/.github/workflows/publish.yml +0 -25
  38. package/.github/workflows/test-battery.yml +0 -102
  39. package/.vscode/settings.json +0 -20
  40. package/CODE_OF_CONDUCT.md +0 -39
  41. package/CONTRIBUTING.md +0 -59
  42. package/HONEYPOT.md +0 -358
  43. package/SECURITY.md +0 -33
  44. package/benchmark-results.txt +0 -40
  45. package/benchmark-virtualshell.ts +0 -88
  46. package/biome.json +0 -37
  47. package/build.js +0 -22
  48. package/builds/fortune-nyx-v1.5.3-directbash-k6.1.0.mjs +0 -1764
  49. package/builds/fortune-nyx-v1.5.3-ssh-nosftp.js +0 -1764
  50. package/builds/fortune-nyx-v1.5.3-ssh.cjs +0 -1765
  51. package/builds/fortune-nyx-v1.5.3-web.min.js +0 -17036
  52. package/bun.lock +0 -244
  53. package/docs/.nojekyll +0 -1
  54. package/docs/app.js +0 -1751
  55. package/docs/assets/hierarchy.js +0 -1
  56. package/docs/assets/highlight.css +0 -162
  57. package/docs/assets/icons.js +0 -18
  58. package/docs/assets/icons.svg +0 -1
  59. package/docs/assets/main.js +0 -60
  60. package/docs/assets/navigation.js +0 -1
  61. package/docs/assets/search.js +0 -1
  62. package/docs/assets/style.css +0 -1633
  63. package/docs/classes/HoneyPot.html +0 -31
  64. package/docs/classes/IdleManager.html +0 -162
  65. package/docs/classes/SshClient.html +0 -66
  66. package/docs/classes/VirtualFileSystem.html +0 -279
  67. package/docs/classes/VirtualPackageManager.html +0 -63
  68. package/docs/classes/VirtualSftpServer.html +0 -169
  69. package/docs/classes/VirtualShell.html +0 -285
  70. package/docs/classes/VirtualSshServer.html +0 -182
  71. package/docs/classes/VirtualUserManager.html +0 -276
  72. package/docs/demo.html +0 -82
  73. package/docs/functions/assertDiff.html +0 -6
  74. package/docs/functions/diffSnapshots.html +0 -7
  75. package/docs/functions/formatDiff.html +0 -6
  76. package/docs/functions/getArg.html +0 -13
  77. package/docs/functions/getFlag.html +0 -15
  78. package/docs/functions/ifFlag.html +0 -11
  79. package/docs/hierarchy.html +0 -1
  80. package/docs/index.html +0 -1869
  81. package/docs/interfaces/AuditLogEntry.html +0 -6
  82. package/docs/interfaces/CommandContext.html +0 -22
  83. package/docs/interfaces/CommandResult.html +0 -26
  84. package/docs/interfaces/ExecStream.html +0 -11
  85. package/docs/interfaces/HoneyPotStats.html +0 -16
  86. package/docs/interfaces/IdleManagerOptions.html +0 -7
  87. package/docs/interfaces/InstalledPackage.html +0 -20
  88. package/docs/interfaces/NanoEditorSession.html +0 -8
  89. package/docs/interfaces/PackageDefinition.html +0 -30
  90. package/docs/interfaces/PackageFile.html +0 -8
  91. package/docs/interfaces/PasswordChallenge.html +0 -16
  92. package/docs/interfaces/RemoveOptions.html +0 -4
  93. package/docs/interfaces/ShellEnv.html +0 -6
  94. package/docs/interfaces/ShellModule.html +0 -14
  95. package/docs/interfaces/ShellProperties.html +0 -14
  96. package/docs/interfaces/ShellStream.html +0 -11
  97. package/docs/interfaces/SudoChallenge.html +0 -24
  98. package/docs/interfaces/VfsBaseNode.html +0 -12
  99. package/docs/interfaces/VfsDiff.html +0 -10
  100. package/docs/interfaces/VfsDiffEntry.html +0 -6
  101. package/docs/interfaces/VfsDiffModified.html +0 -10
  102. package/docs/interfaces/VfsDirectoryNode.html +0 -15
  103. package/docs/interfaces/VfsFileNode.html +0 -17
  104. package/docs/interfaces/VfsOptions.html +0 -26
  105. package/docs/interfaces/VfsSnapshot.html +0 -3
  106. package/docs/interfaces/VfsSnapshotBaseNode.html +0 -8
  107. package/docs/interfaces/VfsSnapshotDirectoryNode.html +0 -10
  108. package/docs/interfaces/VfsSnapshotFileNode.html +0 -12
  109. package/docs/interfaces/VirtualActiveSession.html +0 -12
  110. package/docs/interfaces/VirtualSftpServerOptions.html +0 -7
  111. package/docs/interfaces/VirtualShellVfsLike.html +0 -15
  112. package/docs/interfaces/VirtualShellVfsOptions.html +0 -3
  113. package/docs/interfaces/WriteFileOptions.html +0 -6
  114. package/docs/media/LICENSE +0 -21
  115. package/docs/modules.html +0 -1
  116. package/docs/types/ArgParseOptions.html +0 -4
  117. package/docs/types/CommandMode.html +0 -2
  118. package/docs/types/CommandOutcome.html +0 -2
  119. package/docs/types/IdleState.html +0 -1
  120. package/docs/types/VfsNodeStats.html +0 -2
  121. package/docs/types/VfsNodeType.html +0 -2
  122. package/docs/types/VfsPersistenceMode.html +0 -5
  123. package/docs/types/VfsSnapshotNode.html +0 -2
  124. package/examples/README.md +0 -288
  125. package/examples/app.js +0 -1751
  126. package/examples/app.ts +0 -299
  127. package/examples/build.js +0 -27
  128. package/examples/demo.html +0 -33
  129. package/examples/honeypot-audit.ts +0 -180
  130. package/examples/honeypot-export.ts +0 -253
  131. package/examples/honeypot-quickstart.ts +0 -110
  132. package/examples/index.html +0 -82
  133. package/examples/server.js +0 -55
  134. package/polyfills/buffer.js +0 -117
  135. package/polyfills/node_child_process/index.js +0 -2
  136. package/polyfills/node_crypto/index.js +0 -167
  137. package/polyfills/node_events/index.js +0 -9
  138. package/polyfills/node_fs/index.js +0 -202
  139. package/polyfills/node_fs/promises.js +0 -4
  140. package/polyfills/node_os/index.js +0 -9
  141. package/polyfills/node_path/index.js +0 -28
  142. package/polyfills/node_vm/index.js +0 -7
  143. package/polyfills/node_zlib/index.js +0 -3
  144. package/polyfills/process.js +0 -14
  145. package/polyfills/ssh2/index.js +0 -75
  146. package/scripts/build-all.mjs +0 -226
  147. package/scripts/build-names.mjs +0 -43
  148. package/scripts/generate-manuals-bundle.mjs +0 -49
  149. package/scripts/postinstall.js +0 -42
  150. package/scripts/publish-package.sh +0 -70
  151. package/src/Honeypot/index.ts +0 -457
  152. package/src/SSHClient/index.ts +0 -270
  153. package/src/SSHMimic/exec.ts +0 -49
  154. package/src/SSHMimic/executor.ts +0 -251
  155. package/src/SSHMimic/hostKey.ts +0 -21
  156. package/src/SSHMimic/index.ts +0 -337
  157. package/src/SSHMimic/loginBanner.ts +0 -36
  158. package/src/SSHMimic/loginFormat.ts +0 -10
  159. package/src/SSHMimic/prompt.ts +0 -14
  160. package/src/SSHMimic/sftp.ts +0 -883
  161. package/src/VirtualFileSystem/binaryPack.ts +0 -258
  162. package/src/VirtualFileSystem/index.ts +0 -1193
  163. package/src/VirtualFileSystem/internalTypes.ts +0 -43
  164. package/src/VirtualFileSystem/journal.ts +0 -171
  165. package/src/VirtualFileSystem/path.ts +0 -74
  166. package/src/VirtualPackageManager/index.ts +0 -996
  167. package/src/VirtualShell/idleManager.ts +0 -137
  168. package/src/VirtualShell/index.ts +0 -475
  169. package/src/VirtualShell/shell.ts +0 -700
  170. package/src/VirtualShell/shellParser.ts +0 -285
  171. package/src/VirtualUserManager/index.ts +0 -758
  172. package/src/bun.d.ts +0 -1
  173. package/src/commands/adduser.ts +0 -103
  174. package/src/commands/alias.ts +0 -69
  175. package/src/commands/apt.ts +0 -233
  176. package/src/commands/awk.ts +0 -168
  177. package/src/commands/base64.ts +0 -29
  178. package/src/commands/cat.ts +0 -52
  179. package/src/commands/cd.ts +0 -25
  180. package/src/commands/chmod.ts +0 -85
  181. package/src/commands/clear.ts +0 -15
  182. package/src/commands/command-helpers.ts +0 -286
  183. package/src/commands/cp.ts +0 -83
  184. package/src/commands/curl.ts +0 -147
  185. package/src/commands/cut.ts +0 -36
  186. package/src/commands/date.ts +0 -30
  187. package/src/commands/declare.ts +0 -49
  188. package/src/commands/deluser.ts +0 -98
  189. package/src/commands/df.ts +0 -23
  190. package/src/commands/diff.ts +0 -43
  191. package/src/commands/dpkg.ts +0 -180
  192. package/src/commands/du.ts +0 -56
  193. package/src/commands/echo.ts +0 -58
  194. package/src/commands/env.ts +0 -23
  195. package/src/commands/exit.ts +0 -18
  196. package/src/commands/export.ts +0 -34
  197. package/src/commands/find.ts +0 -68
  198. package/src/commands/free.ts +0 -47
  199. package/src/commands/grep.ts +0 -116
  200. package/src/commands/groups.ts +0 -19
  201. package/src/commands/gzip.ts +0 -88
  202. package/src/commands/head.ts +0 -52
  203. package/src/commands/help.ts +0 -152
  204. package/src/commands/helpers.ts +0 -234
  205. package/src/commands/history.ts +0 -34
  206. package/src/commands/hostname.ts +0 -14
  207. package/src/commands/htop.ts +0 -20
  208. package/src/commands/id.ts +0 -19
  209. package/src/commands/index.ts +0 -9
  210. package/src/commands/kill.ts +0 -19
  211. package/src/commands/ln.ts +0 -71
  212. package/src/commands/ls.ts +0 -243
  213. package/src/commands/lsb-release.ts +0 -63
  214. package/src/commands/man.ts +0 -31
  215. package/src/commands/manuals/adduser.txt +0 -11
  216. package/src/commands/manuals/apt-cache.txt +0 -12
  217. package/src/commands/manuals/apt.txt +0 -20
  218. package/src/commands/manuals/awk.txt +0 -13
  219. package/src/commands/manuals/cat.txt +0 -14
  220. package/src/commands/manuals/cd.txt +0 -16
  221. package/src/commands/manuals/chmod.txt +0 -16
  222. package/src/commands/manuals/clear.txt +0 -10
  223. package/src/commands/manuals/cp.txt +0 -10
  224. package/src/commands/manuals/curl.txt +0 -20
  225. package/src/commands/manuals/date.txt +0 -14
  226. package/src/commands/manuals/declare.txt +0 -12
  227. package/src/commands/manuals/deluser.txt +0 -10
  228. package/src/commands/manuals/df.txt +0 -10
  229. package/src/commands/manuals/dpkg-query.txt +0 -11
  230. package/src/commands/manuals/dpkg.txt +0 -14
  231. package/src/commands/manuals/du.txt +0 -11
  232. package/src/commands/manuals/echo.txt +0 -11
  233. package/src/commands/manuals/false.txt +0 -10
  234. package/src/commands/manuals/find.txt +0 -11
  235. package/src/commands/manuals/free.txt +0 -12
  236. package/src/commands/manuals/grep.txt +0 -13
  237. package/src/commands/manuals/groups.txt +0 -10
  238. package/src/commands/manuals/gzip.txt +0 -11
  239. package/src/commands/manuals/head.txt +0 -10
  240. package/src/commands/manuals/help.txt +0 -11
  241. package/src/commands/manuals/history.txt +0 -11
  242. package/src/commands/manuals/hostname.txt +0 -10
  243. package/src/commands/manuals/id.txt +0 -10
  244. package/src/commands/manuals/kill.txt +0 -13
  245. package/src/commands/manuals/ls.txt +0 -20
  246. package/src/commands/manuals/lsb_release.txt +0 -14
  247. package/src/commands/manuals/mkdir.txt +0 -10
  248. package/src/commands/manuals/mv.txt +0 -10
  249. package/src/commands/manuals/nano.txt +0 -11
  250. package/src/commands/manuals/neofetch.txt +0 -10
  251. package/src/commands/manuals/node.txt +0 -13
  252. package/src/commands/manuals/npm.txt +0 -13
  253. package/src/commands/manuals/npx.txt +0 -13
  254. package/src/commands/manuals/passwd.txt +0 -11
  255. package/src/commands/manuals/ping.txt +0 -10
  256. package/src/commands/manuals/printf.txt +0 -11
  257. package/src/commands/manuals/ps.txt +0 -10
  258. package/src/commands/manuals/pwd.txt +0 -10
  259. package/src/commands/manuals/python3.txt +0 -13
  260. package/src/commands/manuals/readlink.txt +0 -10
  261. package/src/commands/manuals/return.txt +0 -10
  262. package/src/commands/manuals/rm.txt +0 -10
  263. package/src/commands/manuals/sed.txt +0 -11
  264. package/src/commands/manuals/set.txt +0 -11
  265. package/src/commands/manuals/shift.txt +0 -10
  266. package/src/commands/manuals/sleep.txt +0 -10
  267. package/src/commands/manuals/sort.txt +0 -12
  268. package/src/commands/manuals/source.txt +0 -11
  269. package/src/commands/manuals/ssh.txt +0 -11
  270. package/src/commands/manuals/stat.txt +0 -10
  271. package/src/commands/manuals/su.txt +0 -13
  272. package/src/commands/manuals/sudo.txt +0 -11
  273. package/src/commands/manuals/tail.txt +0 -10
  274. package/src/commands/manuals/tar.txt +0 -19
  275. package/src/commands/manuals/tee.txt +0 -10
  276. package/src/commands/manuals/test.txt +0 -11
  277. package/src/commands/manuals/touch.txt +0 -11
  278. package/src/commands/manuals/tr.txt +0 -10
  279. package/src/commands/manuals/trap.txt +0 -10
  280. package/src/commands/manuals/true.txt +0 -10
  281. package/src/commands/manuals/type.txt +0 -10
  282. package/src/commands/manuals/uname.txt +0 -12
  283. package/src/commands/manuals/uniq.txt +0 -12
  284. package/src/commands/manuals/unset.txt +0 -10
  285. package/src/commands/manuals/uptime.txt +0 -11
  286. package/src/commands/manuals/wc.txt +0 -12
  287. package/src/commands/manuals/wget.txt +0 -12
  288. package/src/commands/manuals/which.txt +0 -10
  289. package/src/commands/manuals/whoami.txt +0 -10
  290. package/src/commands/manuals/xargs.txt +0 -10
  291. package/src/commands/manuals-bundle.ts +0 -898
  292. package/src/commands/mkdir.ts +0 -31
  293. package/src/commands/mv.ts +0 -50
  294. package/src/commands/nano.ts +0 -38
  295. package/src/commands/neofetch.ts +0 -53
  296. package/src/commands/node.ts +0 -341
  297. package/src/commands/npm.ts +0 -132
  298. package/src/commands/passwd.ts +0 -50
  299. package/src/commands/ping.ts +0 -32
  300. package/src/commands/printf.ts +0 -129
  301. package/src/commands/ps.ts +0 -58
  302. package/src/commands/pwd.ts +0 -9
  303. package/src/commands/python.ts +0 -2229
  304. package/src/commands/read.ts +0 -46
  305. package/src/commands/registry.ts +0 -249
  306. package/src/commands/rm.ts +0 -42
  307. package/src/commands/runtime.ts +0 -421
  308. package/src/commands/sed.ts +0 -68
  309. package/src/commands/seq.ts +0 -43
  310. package/src/commands/set.ts +0 -29
  311. package/src/commands/sh.ts +0 -467
  312. package/src/commands/shift.ts +0 -63
  313. package/src/commands/sleep.ts +0 -20
  314. package/src/commands/sort.ts +0 -46
  315. package/src/commands/source.ts +0 -52
  316. package/src/commands/stat.ts +0 -61
  317. package/src/commands/su.ts +0 -72
  318. package/src/commands/sudo.ts +0 -76
  319. package/src/commands/tail.ts +0 -53
  320. package/src/commands/tar.ts +0 -102
  321. package/src/commands/tee.ts +0 -36
  322. package/src/commands/test.ts +0 -137
  323. package/src/commands/touch.ts +0 -28
  324. package/src/commands/tr.ts +0 -70
  325. package/src/commands/tree.ts +0 -20
  326. package/src/commands/true.ts +0 -27
  327. package/src/commands/type.ts +0 -48
  328. package/src/commands/uname.ts +0 -29
  329. package/src/commands/uniq.ts +0 -39
  330. package/src/commands/unset.ts +0 -17
  331. package/src/commands/uptime.ts +0 -54
  332. package/src/commands/wc.ts +0 -55
  333. package/src/commands/wget.ts +0 -148
  334. package/src/commands/which.ts +0 -37
  335. package/src/commands/who.ts +0 -25
  336. package/src/commands/whoami.ts +0 -14
  337. package/src/commands/xargs.ts +0 -31
  338. package/src/index.ts +0 -67
  339. package/src/modules/linuxRootfs.ts +0 -1961
  340. package/src/modules/neofetch.ts +0 -358
  341. package/src/modules/shellInteractive.ts +0 -57
  342. package/src/modules/shellRuntime.ts +0 -76
  343. package/src/self-standalone.ts +0 -542
  344. package/src/standalone-wo-sftp.ts +0 -38
  345. package/src/standalone.ts +0 -72
  346. package/src/types/commands.ts +0 -146
  347. package/src/types/pipeline.ts +0 -52
  348. package/src/types/streams.ts +0 -32
  349. package/src/types/tar-stream.d.ts +0 -38
  350. package/src/types/vfs.ts +0 -98
  351. package/src/utils/expand.ts +0 -491
  352. package/src/utils/perfLogger.ts +0 -72
  353. package/src/utils/tokenize.ts +0 -98
  354. package/src/utils/vfsDiff.ts +0 -275
  355. package/tests/command-helpers.test.ts +0 -116
  356. package/tests/commands-admin-net.test.ts +0 -441
  357. package/tests/commands-advanced.test.ts +0 -456
  358. package/tests/commands-core.test.ts +0 -562
  359. package/tests/commands-missing.test.ts +0 -570
  360. package/tests/commands-specific-units.test.ts +0 -327
  361. package/tests/commands-text-sys.test.ts +0 -445
  362. package/tests/expand.test.ts +0 -170
  363. package/tests/helpers.test.ts +0 -97
  364. package/tests/new-features.test.ts +0 -1036
  365. package/tests/parser-executor.test.ts +0 -37
  366. package/tests/sftp.test.ts +0 -323
  367. package/tests/ssh-exec.test.ts +0 -45
  368. package/tests/test-helper.ts +0 -79
  369. package/tests/users.test.ts +0 -86
  370. package/tsconfig.json +0 -49
  371. 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
- }