typescript-virtual-container 1.3.3 → 1.4.0

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 (282) hide show
  1. package/.vscode/settings.json +0 -1
  2. package/README.md +674 -1504
  3. package/benchmark-results.txt +21 -21
  4. package/builds/self-standalone.js +274 -208
  5. package/builds/self-standalone.js.map +4 -4
  6. package/builds/standalone-wo-sftp.js +201 -149
  7. package/builds/standalone-wo-sftp.js.map +4 -4
  8. package/builds/standalone.js +263 -211
  9. package/builds/standalone.js.map +4 -4
  10. package/builds/web-full-api.min.js +3 -3
  11. package/builds/web-full-api.min.js.map +4 -4
  12. package/builds/web.min.js +2 -2
  13. package/builds/web.min.js.map +4 -4
  14. package/bun.lock +14 -12
  15. package/dist/SSHClient/index.d.ts.map +1 -1
  16. package/dist/SSHClient/index.js +5 -3
  17. package/dist/SSHMimic/executor.d.ts +1 -3
  18. package/dist/SSHMimic/executor.d.ts.map +1 -1
  19. package/dist/SSHMimic/executor.js +20 -22
  20. package/dist/SSHMimic/index.d.ts.map +1 -1
  21. package/dist/SSHMimic/index.js +5 -3
  22. package/dist/SSHMimic/sftp.d.ts.map +1 -1
  23. package/dist/SSHMimic/sftp.js +26 -21
  24. package/dist/VirtualShell/shell.d.ts.map +1 -1
  25. package/dist/VirtualShell/shell.js +25 -3
  26. package/dist/VirtualShell/shellParser.d.ts +1 -8
  27. package/dist/VirtualShell/shellParser.d.ts.map +1 -1
  28. package/dist/VirtualShell/shellParser.js +2 -81
  29. package/dist/VirtualUserManager/index.d.ts +7 -1
  30. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  31. package/dist/VirtualUserManager/index.js +47 -19
  32. package/dist/commands/adduser.d.ts +10 -4
  33. package/dist/commands/adduser.d.ts.map +1 -1
  34. package/dist/commands/adduser.js +75 -12
  35. package/dist/commands/alias.d.ts +5 -0
  36. package/dist/commands/alias.d.ts.map +1 -1
  37. package/dist/commands/alias.js +5 -0
  38. package/dist/commands/apt.d.ts +5 -0
  39. package/dist/commands/apt.d.ts.map +1 -1
  40. package/dist/commands/apt.js +5 -0
  41. package/dist/commands/awk.d.ts +10 -8
  42. package/dist/commands/awk.d.ts.map +1 -1
  43. package/dist/commands/awk.js +156 -28
  44. package/dist/commands/cd.d.ts.map +1 -1
  45. package/dist/commands/cd.js +0 -3
  46. package/dist/commands/clear.d.ts +5 -0
  47. package/dist/commands/clear.d.ts.map +1 -1
  48. package/dist/commands/clear.js +5 -0
  49. package/dist/commands/command-helpers.d.ts.map +1 -1
  50. package/dist/commands/command-helpers.js +8 -0
  51. package/dist/commands/declare.d.ts +5 -0
  52. package/dist/commands/declare.d.ts.map +1 -1
  53. package/dist/commands/declare.js +5 -0
  54. package/dist/commands/deluser.d.ts +12 -0
  55. package/dist/commands/deluser.d.ts.map +1 -1
  56. package/dist/commands/deluser.js +72 -6
  57. package/dist/commands/df.d.ts +5 -0
  58. package/dist/commands/df.d.ts.map +1 -1
  59. package/dist/commands/df.js +5 -0
  60. package/dist/commands/du.d.ts +5 -0
  61. package/dist/commands/du.d.ts.map +1 -1
  62. package/dist/commands/du.js +5 -0
  63. package/dist/commands/export.d.ts +5 -0
  64. package/dist/commands/export.d.ts.map +1 -1
  65. package/dist/commands/export.js +5 -0
  66. package/dist/commands/grep.d.ts.map +1 -1
  67. package/dist/commands/grep.js +22 -4
  68. package/dist/commands/groups.d.ts +5 -0
  69. package/dist/commands/groups.d.ts.map +1 -1
  70. package/dist/commands/groups.js +5 -0
  71. package/dist/commands/gzip.d.ts +5 -2
  72. package/dist/commands/gzip.d.ts.map +1 -1
  73. package/dist/commands/gzip.js +48 -28
  74. package/dist/commands/head.d.ts.map +1 -1
  75. package/dist/commands/head.js +12 -3
  76. package/dist/commands/htop.d.ts +5 -0
  77. package/dist/commands/htop.d.ts.map +1 -1
  78. package/dist/commands/htop.js +5 -0
  79. package/dist/commands/kill.d.ts +5 -0
  80. package/dist/commands/kill.d.ts.map +1 -1
  81. package/dist/commands/kill.js +5 -0
  82. package/dist/commands/ln.d.ts +2 -0
  83. package/dist/commands/ln.d.ts.map +1 -1
  84. package/dist/commands/ln.js +22 -0
  85. package/dist/commands/ls.d.ts.map +1 -1
  86. package/dist/commands/ls.js +15 -0
  87. package/dist/commands/lsb-release.d.ts +5 -0
  88. package/dist/commands/lsb-release.d.ts.map +1 -1
  89. package/dist/commands/lsb-release.js +5 -0
  90. package/dist/commands/mkdir.d.ts +5 -0
  91. package/dist/commands/mkdir.d.ts.map +1 -1
  92. package/dist/commands/mkdir.js +5 -0
  93. package/dist/commands/mv.d.ts +5 -0
  94. package/dist/commands/mv.d.ts.map +1 -1
  95. package/dist/commands/mv.js +5 -0
  96. package/dist/commands/nano.d.ts +5 -0
  97. package/dist/commands/nano.d.ts.map +1 -1
  98. package/dist/commands/nano.js +5 -0
  99. package/dist/commands/neofetch.d.ts +5 -0
  100. package/dist/commands/neofetch.d.ts.map +1 -1
  101. package/dist/commands/neofetch.js +8 -5
  102. package/dist/commands/passwd.d.ts +8 -0
  103. package/dist/commands/passwd.d.ts.map +1 -1
  104. package/dist/commands/passwd.js +32 -11
  105. package/dist/commands/ping.d.ts +5 -0
  106. package/dist/commands/ping.d.ts.map +1 -1
  107. package/dist/commands/ping.js +5 -0
  108. package/dist/commands/printf.d.ts +5 -0
  109. package/dist/commands/printf.d.ts.map +1 -1
  110. package/dist/commands/printf.js +43 -12
  111. package/dist/commands/ps.d.ts +5 -0
  112. package/dist/commands/ps.d.ts.map +1 -1
  113. package/dist/commands/ps.js +5 -0
  114. package/dist/commands/read.d.ts +5 -0
  115. package/dist/commands/read.d.ts.map +1 -1
  116. package/dist/commands/read.js +5 -0
  117. package/dist/commands/registry.d.ts.map +1 -1
  118. package/dist/commands/registry.js +4 -1
  119. package/dist/commands/rm.d.ts +5 -0
  120. package/dist/commands/rm.d.ts.map +1 -1
  121. package/dist/commands/rm.js +5 -0
  122. package/dist/commands/runtime.d.ts.map +1 -1
  123. package/dist/commands/runtime.js +1 -57
  124. package/dist/commands/sed.d.ts +5 -0
  125. package/dist/commands/sed.d.ts.map +1 -1
  126. package/dist/commands/sed.js +5 -0
  127. package/dist/commands/set.d.ts +5 -6
  128. package/dist/commands/set.d.ts.map +1 -1
  129. package/dist/commands/set.js +5 -22
  130. package/dist/commands/sh.d.ts +6 -0
  131. package/dist/commands/sh.d.ts.map +1 -1
  132. package/dist/commands/sh.js +6 -0
  133. package/dist/commands/shift.d.ts +10 -0
  134. package/dist/commands/shift.d.ts.map +1 -1
  135. package/dist/commands/shift.js +10 -0
  136. package/dist/commands/sleep.d.ts +5 -0
  137. package/dist/commands/sleep.d.ts.map +1 -1
  138. package/dist/commands/sleep.js +5 -0
  139. package/dist/commands/sort.d.ts +5 -0
  140. package/dist/commands/sort.d.ts.map +1 -1
  141. package/dist/commands/sort.js +5 -0
  142. package/dist/commands/source.d.ts +5 -0
  143. package/dist/commands/source.d.ts.map +1 -1
  144. package/dist/commands/source.js +5 -0
  145. package/dist/commands/stat.d.ts +7 -0
  146. package/dist/commands/stat.d.ts.map +1 -0
  147. package/dist/commands/stat.js +56 -0
  148. package/dist/commands/su.d.ts +13 -0
  149. package/dist/commands/su.d.ts.map +1 -1
  150. package/dist/commands/su.js +45 -14
  151. package/dist/commands/sudo.d.ts.map +1 -1
  152. package/dist/commands/sudo.js +5 -0
  153. package/dist/commands/tail.d.ts +5 -0
  154. package/dist/commands/tail.d.ts.map +1 -1
  155. package/dist/commands/tail.js +15 -3
  156. package/dist/commands/tar.d.ts +5 -0
  157. package/dist/commands/tar.d.ts.map +1 -1
  158. package/dist/commands/tar.js +40 -10
  159. package/dist/commands/tee.d.ts +5 -0
  160. package/dist/commands/tee.d.ts.map +1 -1
  161. package/dist/commands/tee.js +5 -0
  162. package/dist/commands/touch.d.ts +5 -0
  163. package/dist/commands/touch.d.ts.map +1 -1
  164. package/dist/commands/touch.js +5 -0
  165. package/dist/commands/tr.d.ts.map +1 -1
  166. package/dist/commands/tr.js +45 -10
  167. package/dist/commands/tree.d.ts +5 -0
  168. package/dist/commands/tree.d.ts.map +1 -1
  169. package/dist/commands/tree.js +5 -0
  170. package/dist/commands/true.d.ts +10 -0
  171. package/dist/commands/true.d.ts.map +1 -1
  172. package/dist/commands/true.js +10 -0
  173. package/dist/commands/type.d.ts +5 -0
  174. package/dist/commands/type.d.ts.map +1 -1
  175. package/dist/commands/type.js +5 -0
  176. package/dist/commands/uname.d.ts +5 -0
  177. package/dist/commands/uname.d.ts.map +1 -1
  178. package/dist/commands/uname.js +5 -0
  179. package/dist/commands/uniq.d.ts +5 -0
  180. package/dist/commands/uniq.d.ts.map +1 -1
  181. package/dist/commands/uniq.js +5 -0
  182. package/dist/commands/unset.d.ts +5 -0
  183. package/dist/commands/unset.d.ts.map +1 -1
  184. package/dist/commands/unset.js +5 -0
  185. package/dist/commands/uptime.d.ts +5 -0
  186. package/dist/commands/uptime.d.ts.map +1 -1
  187. package/dist/commands/uptime.js +5 -0
  188. package/dist/commands/wc.d.ts +5 -0
  189. package/dist/commands/wc.d.ts.map +1 -1
  190. package/dist/commands/wc.js +5 -0
  191. package/dist/commands/wget.d.ts +5 -0
  192. package/dist/commands/wget.d.ts.map +1 -1
  193. package/dist/commands/wget.js +5 -0
  194. package/dist/commands/who.d.ts +5 -0
  195. package/dist/commands/who.d.ts.map +1 -1
  196. package/dist/commands/who.js +5 -0
  197. package/dist/commands/whoami.d.ts +5 -0
  198. package/dist/commands/whoami.d.ts.map +1 -1
  199. package/dist/commands/whoami.js +5 -0
  200. package/dist/commands/xargs.d.ts +5 -0
  201. package/dist/commands/xargs.d.ts.map +1 -1
  202. package/dist/commands/xargs.js +5 -0
  203. package/dist/self-standalone.js +254 -30
  204. package/dist/types/commands.d.ts +36 -0
  205. package/dist/types/commands.d.ts.map +1 -1
  206. package/dist/utils/tokenize.d.ts +20 -0
  207. package/dist/utils/tokenize.d.ts.map +1 -0
  208. package/dist/utils/tokenize.js +74 -0
  209. package/examples/web.min.js +2 -2
  210. package/package.json +1 -1
  211. package/src/SSHClient/index.ts +6 -3
  212. package/src/SSHMimic/executor.ts +21 -44
  213. package/src/SSHMimic/index.ts +7 -5
  214. package/src/SSHMimic/sftp.ts +28 -21
  215. package/src/VirtualShell/shell.ts +34 -4
  216. package/src/VirtualShell/shellParser.ts +2 -103
  217. package/src/VirtualUserManager/index.ts +44 -20
  218. package/src/commands/adduser.ts +86 -13
  219. package/src/commands/alias.ts +5 -0
  220. package/src/commands/apt.ts +5 -0
  221. package/src/commands/awk.ts +154 -29
  222. package/src/commands/cd.ts +0 -4
  223. package/src/commands/clear.ts +5 -0
  224. package/src/commands/command-helpers.ts +9 -0
  225. package/src/commands/declare.ts +5 -0
  226. package/src/commands/deluser.ts +84 -7
  227. package/src/commands/df.ts +5 -0
  228. package/src/commands/du.ts +5 -0
  229. package/src/commands/export.ts +5 -0
  230. package/src/commands/grep.ts +21 -8
  231. package/src/commands/groups.ts +5 -0
  232. package/src/commands/gzip.ts +54 -28
  233. package/src/commands/head.ts +14 -4
  234. package/src/commands/htop.ts +5 -0
  235. package/src/commands/kill.ts +5 -0
  236. package/src/commands/ln.ts +22 -0
  237. package/src/commands/ls.ts +17 -0
  238. package/src/commands/lsb-release.ts +5 -0
  239. package/src/commands/mkdir.ts +5 -0
  240. package/src/commands/mv.ts +5 -0
  241. package/src/commands/nano.ts +5 -0
  242. package/src/commands/neofetch.ts +8 -6
  243. package/src/commands/passwd.ts +35 -12
  244. package/src/commands/ping.ts +5 -0
  245. package/src/commands/printf.ts +30 -13
  246. package/src/commands/ps.ts +5 -0
  247. package/src/commands/read.ts +5 -0
  248. package/src/commands/registry.ts +4 -1
  249. package/src/commands/rm.ts +5 -0
  250. package/src/commands/runtime.ts +1 -61
  251. package/src/commands/sed.ts +5 -0
  252. package/src/commands/set.ts +5 -24
  253. package/src/commands/sh.ts +9 -3
  254. package/src/commands/shift.ts +10 -0
  255. package/src/commands/sleep.ts +5 -0
  256. package/src/commands/sort.ts +5 -0
  257. package/src/commands/source.ts +5 -0
  258. package/src/commands/stat.ts +61 -0
  259. package/src/commands/su.ts +54 -16
  260. package/src/commands/sudo.ts +5 -0
  261. package/src/commands/tail.ts +17 -3
  262. package/src/commands/tar.ts +38 -15
  263. package/src/commands/tee.ts +5 -0
  264. package/src/commands/touch.ts +5 -0
  265. package/src/commands/tr.ts +54 -10
  266. package/src/commands/tree.ts +5 -0
  267. package/src/commands/true.ts +10 -0
  268. package/src/commands/type.ts +5 -0
  269. package/src/commands/uname.ts +5 -0
  270. package/src/commands/uniq.ts +5 -0
  271. package/src/commands/unset.ts +5 -0
  272. package/src/commands/uptime.ts +5 -0
  273. package/src/commands/wc.ts +5 -0
  274. package/src/commands/wget.ts +5 -0
  275. package/src/commands/who.ts +5 -0
  276. package/src/commands/whoami.ts +5 -0
  277. package/src/commands/xargs.ts +5 -0
  278. package/src/self-standalone.ts +316 -33
  279. package/src/types/commands.ts +37 -0
  280. package/src/utils/tokenize.ts +78 -0
  281. package/builds/web-iife.min.js +0 -13
  282. package/builds/web-iife.min.js.map +0 -7
@@ -19,6 +19,9 @@ import { loadOrCreateHostKey } from "./hostKey";
19
19
  * - Non-interactive exec sessions
20
20
  */
21
21
  const perf = createPerfLogger("SshMimic");
22
+ // ── Dev-mode logger ───────────────────────────────────────────────────────────
23
+ const DEV = !!process.env.DEV_MODE;
24
+ const devLog = DEV ? console.log.bind(console) : () => { };
22
25
  class SshMimic extends EventEmitter {
23
26
  port;
24
27
  server;
@@ -114,7 +117,6 @@ class SshMimic extends EventEmitter {
114
117
  // ── Password auth ──────────────────────────────────────
115
118
  if (ctx.method === "password") {
116
119
  if (!shell.users.hasPassword(candidateUser)) {
117
- console.log(`User ${candidateUser} has no password set, allowing login without verification`);
118
120
  authUser = candidateUser;
119
121
  sessionId = shell.users.registerSession(authUser, remoteAddress).id;
120
122
  this.recordSuccess(remoteAddress);
@@ -217,7 +219,7 @@ class SshMimic extends EventEmitter {
217
219
  return new Promise((resolve, reject) => {
218
220
  this.server?.once("error", (err) => reject(err));
219
221
  this.server?.listen(this.port, "0.0.0.0", () => {
220
- console.log(`SSH Mimic listening on port ${this.port}`);
222
+ devLog(`SSH Mimic listening on port ${this.port}`);
221
223
  this.emit("start", { port: this.port });
222
224
  resolve(this.port);
223
225
  });
@@ -230,7 +232,7 @@ class SshMimic extends EventEmitter {
230
232
  perf.mark("stop");
231
233
  if (this.server) {
232
234
  this.server.close(() => {
233
- console.log("SSH Mimic stopped");
235
+ devLog("SSH Mimic stopped");
234
236
  this.emit("stop");
235
237
  });
236
238
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sftp.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/sftp.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAI3C,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AA4HhE,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,iBAAiB,CAAC;IACxB,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC3B;AAED,qBAAa,SAAU,SAAQ,YAAY;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,OAAO,CAAiC;gBAEpC,EACX,IAAI,EACJ,QAA0B,EAC1B,KAAK,EACL,GAAG,EACH,KAAK,GACL,EAAE,gBAAgB;IAwBnB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAIH,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAwK9B,IAAI,IAAI,IAAI;IAUnB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAiB1B;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,kBAAkB;CA6a1B"}
1
+ {"version":3,"file":"sftp.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/sftp.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAI3C,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAmIhE,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,iBAAiB,CAAC;IACxB,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC3B;AAED,qBAAa,SAAU,SAAQ,YAAY;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,OAAO,CAAiC;gBAEpC,EACX,IAAI,EACJ,QAA0B,EAC1B,KAAK,EACL,GAAG,EACH,KAAK,GACL,EAAE,gBAAgB;IAwBnB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAIH,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAwK9B,IAAI,IAAI,IAAI;IAUnB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAiB1B;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,kBAAkB;CA6a1B"}
@@ -5,6 +5,11 @@ import { Server as SshServer } from "ssh2";
5
5
  import { createPerfLogger } from "../utils/perfLogger";
6
6
  import { VirtualShell } from "../VirtualShell";
7
7
  import { loadOrCreateHostKey } from "./hostKey";
8
+ // ── Dev-mode logger — silent in production ────────────────────────────────────
9
+ const DEV = !!process.env.DEV_MODE;
10
+ const devLog = DEV ? console.log.bind(console) : () => { };
11
+ const devWarn = DEV ? console.warn.bind(console) : () => { };
12
+ const devErr = DEV ? console.error.bind(console) : () => { };
8
13
  const SFTP_STATUS_CODE = {
9
14
  OK: 0,
10
15
  EOF: 1,
@@ -89,7 +94,7 @@ export class SftpMimic extends EventEmitter {
89
94
  this.emit("client:connect");
90
95
  // Add error handling for the client
91
96
  client.on("error", (error) => {
92
- console.error(`[SFTP] Client error:`, error);
97
+ devErr(`[SFTP] Client error:`, error);
93
98
  });
94
99
  const acceptSession = (username) => {
95
100
  authUser = username;
@@ -108,7 +113,7 @@ export class SftpMimic extends EventEmitter {
108
113
  client.on("authentication", (ctx) => {
109
114
  const candidateUser = ctx.username || "root";
110
115
  remoteAddress = ctx.ip ?? remoteAddress;
111
- console.log(`[SFTP] Auth attempt: user=${candidateUser}, method=${ctx.method}, ip=${remoteAddress}`);
116
+ devLog(`[SFTP] Auth attempt: user=${candidateUser}, method=${ctx.method}, ip=${remoteAddress}`);
112
117
  if (ctx.method === "password") {
113
118
  // If no password is set for the user, allow login without verification
114
119
  if (!this.getUsers().hasPassword(candidateUser)) {
@@ -170,7 +175,7 @@ export class SftpMimic extends EventEmitter {
170
175
  const session = accept();
171
176
  // Add error handling for the session
172
177
  session.on("error", (error) => {
173
- console.error(`[SFTP] Session error for user=${authUser}:`, error);
178
+ devErr(`[SFTP] Session error for user=${authUser}:`, error);
174
179
  });
175
180
  session.on("sftp", (acceptSftp) => {
176
181
  const sftp = acceptSftp();
@@ -186,7 +191,7 @@ export class SftpMimic extends EventEmitter {
186
191
  const actualPort = address && typeof address === "object" && "port" in address
187
192
  ? address.port
188
193
  : this.port;
189
- console.log(`SFTP Mimic listening on port ${actualPort}`);
194
+ devLog(`SFTP Mimic listening on port ${actualPort}`);
190
195
  this.emit("start", { port: actualPort });
191
196
  resolve(actualPort);
192
197
  });
@@ -196,7 +201,7 @@ export class SftpMimic extends EventEmitter {
196
201
  perf.mark("stop");
197
202
  if (this.server) {
198
203
  this.server.close(() => {
199
- console.log("SFTP Mimic stopped");
204
+ devLog("SFTP Mimic stopped");
200
205
  this.emit("stop");
201
206
  });
202
207
  }
@@ -273,7 +278,7 @@ export class SftpMimic extends EventEmitter {
273
278
  const targetPath = this.resolveRequestPath(filename, authUser);
274
279
  // Security: Confine to home directory
275
280
  if (!this.isPathWithinHome(targetPath, authUser)) {
276
- console.warn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
281
+ devWarn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
277
282
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
278
283
  return;
279
284
  }
@@ -318,7 +323,7 @@ export class SftpMimic extends EventEmitter {
318
323
  sftp.handle(reqid, handle);
319
324
  }
320
325
  catch (error) {
321
- console.error("SFTP OPEN error:", error);
326
+ devErr("SFTP OPEN error:", error);
322
327
  sftp.status(reqid, SFTP_STATUS_CODE.FAILURE);
323
328
  }
324
329
  });
@@ -377,7 +382,7 @@ export class SftpMimic extends EventEmitter {
377
382
  void getVfs().flushMirror();
378
383
  }
379
384
  catch (error) {
380
- console.error("SFTP CLOSE write error:", error);
385
+ devErr("SFTP CLOSE write error:", error);
381
386
  sftp.status(reqid, SFTP_STATUS_CODE.FAILURE);
382
387
  this.closeHandle(handle);
383
388
  return;
@@ -390,7 +395,7 @@ export class SftpMimic extends EventEmitter {
390
395
  const targetPath = this.resolveRequestPath(requestPath, authUser);
391
396
  // Security: Confine to home directory
392
397
  if (!this.isPathWithinHome(targetPath, authUser)) {
393
- console.warn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
398
+ devWarn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
394
399
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
395
400
  return;
396
401
  }
@@ -434,7 +439,7 @@ export class SftpMimic extends EventEmitter {
434
439
  const targetPath = this.resolveRequestPath(requestPath, authUser);
435
440
  // Security: Confine to home directory
436
441
  if (!this.isPathWithinHome(targetPath, authUser)) {
437
- console.warn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
442
+ devWarn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
438
443
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
439
444
  return;
440
445
  }
@@ -450,7 +455,7 @@ export class SftpMimic extends EventEmitter {
450
455
  const targetPath = this.resolveRequestPath(requestPath, authUser);
451
456
  // Security: Confine to home directory
452
457
  if (!this.isPathWithinHome(targetPath, authUser)) {
453
- console.warn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
458
+ devWarn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
454
459
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
455
460
  return;
456
461
  }
@@ -482,7 +487,7 @@ export class SftpMimic extends EventEmitter {
482
487
  const targetPath = this.resolveRequestPath(requestPath, authUser);
483
488
  // Security: Confine to home directory
484
489
  if (!this.isPathWithinHome(targetPath, authUser)) {
485
- console.warn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
490
+ devWarn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
486
491
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
487
492
  return;
488
493
  }
@@ -500,7 +505,7 @@ export class SftpMimic extends EventEmitter {
500
505
  const normalized = this.resolveRequestPath(requestPath, authUser);
501
506
  // Security: Confine to home directory
502
507
  if (!this.isPathWithinHome(normalized, authUser)) {
503
- console.warn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${normalized}`);
508
+ devWarn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${normalized}`);
504
509
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
505
510
  return;
506
511
  }
@@ -523,7 +528,7 @@ export class SftpMimic extends EventEmitter {
523
528
  const targetPath = this.resolveRequestPath(requestPath, authUser);
524
529
  // Security: Confine to home directory
525
530
  if (!this.isPathWithinHome(targetPath, authUser)) {
526
- console.warn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
531
+ devWarn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
527
532
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
528
533
  return;
529
534
  }
@@ -540,7 +545,7 @@ export class SftpMimic extends EventEmitter {
540
545
  const targetPath = this.resolveRequestPath(requestPath, authUser);
541
546
  // Security: Confine to home directory
542
547
  if (!this.isPathWithinHome(targetPath, authUser)) {
543
- console.warn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
548
+ devWarn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
544
549
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
545
550
  return;
546
551
  }
@@ -557,7 +562,7 @@ export class SftpMimic extends EventEmitter {
557
562
  const targetPath = this.resolveRequestPath(requestPath, authUser);
558
563
  // Security: Confine to home directory
559
564
  if (!this.isPathWithinHome(targetPath, authUser)) {
560
- console.warn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
565
+ devWarn(`[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`);
561
566
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
562
567
  return;
563
568
  }
@@ -576,7 +581,7 @@ export class SftpMimic extends EventEmitter {
576
581
  // Security: Confine both source and destination to home directory
577
582
  if (!this.isPathWithinHome(fromPath, authUser) ||
578
583
  !this.isPathWithinHome(toPath, authUser)) {
579
- console.warn(`[SFTP] Path traversal attempt blocked: user=${authUser}, from=${fromPath}, to=${toPath}`);
584
+ devWarn(`[SFTP] Path traversal attempt blocked: user=${authUser}, from=${fromPath}, to=${toPath}`);
580
585
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
581
586
  return;
582
587
  }
@@ -596,18 +601,18 @@ export class SftpMimic extends EventEmitter {
596
601
  sftp.status(reqid, SFTP_STATUS_CODE.OP_UNSUPPORTED);
597
602
  });
598
603
  sftp.on("error", (error) => {
599
- console.error(`[SFTP] Stream error for user=${authUser}:`, error);
604
+ devErr(`[SFTP] Stream error for user=${authUser}:`, error);
600
605
  });
601
606
  sftp.on("close", () => {
602
- console.log(`[SFTP] Stream closed for user=${authUser}`);
607
+ devLog(`[SFTP] Stream closed for user=${authUser}`);
603
608
  this.handles.clear();
604
609
  });
605
610
  sftp.on("end", () => {
606
- console.log(`[SFTP] end event for user=${authUser}`);
611
+ devLog(`[SFTP] end event for user=${authUser}`);
607
612
  this.handles.clear();
608
613
  });
609
614
  sftp.on("END", () => {
610
- console.log(`[SFTP] END event for user=${authUser}`);
615
+ devLog(`[SFTP] END event for user=${authUser}`);
611
616
  this.handles.clear();
612
617
  });
613
618
  }
@@ -1 +1 @@
1
- {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/shell.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AAMvD,OAAO,EAGN,KAAK,YAAY,EAEjB,MAAM,yBAAyB,CAAC;AAIjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAmBpD,wBAAgB,UAAU,CACzB,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,oBAAY,EACzB,YAAY,EAAE,YAAY,YAAyB,EACnD,KAAK,EAAE,YAAY,GACjB,IAAI,CAsmBN"}
1
+ {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/shell.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AAMvD,OAAO,EAGN,KAAK,YAAY,EAEjB,MAAM,yBAAyB,CAAC;AAIjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAwBpD,wBAAgB,UAAU,CACzB,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,oBAAY,EACzB,YAAY,EAAE,YAAY,YAAyB,EACnD,KAAK,EAAE,YAAY,GACjB,IAAI,CA+nBN"}
@@ -72,7 +72,9 @@ export function startShell(properties, stream, authUser, hostname, sessionId, re
72
72
  }
73
73
  if (!challenge.commandLine) {
74
74
  authUser = challenge.targetUser;
75
- cwd = `/home/${authUser}`;
75
+ if (challenge.loginShell) {
76
+ cwd = `/home/${authUser}`;
77
+ }
76
78
  shell.users.updateSession(sessionId, authUser, remoteAddress);
77
79
  stream.write("\r\n");
78
80
  renderLine();
@@ -300,9 +302,29 @@ export function startShell(properties, stream, authUser, hostname, sessionId, re
300
302
  continue;
301
303
  }
302
304
  if (ch === "\r" || ch === "\n") {
303
- const password = pendingSudo.buffer;
305
+ const typed = pendingSudo.buffer;
304
306
  pendingSudo.buffer = "";
305
- const valid = shell.users.verifyPassword(pendingSudo.username, password);
307
+ // ── Generic onPassword handler (passwd / confirm modes) ────
308
+ if (pendingSudo.onPassword) {
309
+ const { result, nextPrompt } = await pendingSudo.onPassword(typed, shell);
310
+ stream.write("\r\n");
311
+ if (result !== null) {
312
+ pendingSudo = null;
313
+ if (result.stdout)
314
+ stream.write(result.stdout.replace(/\n/g, "\r\n"));
315
+ if (result.stderr)
316
+ stream.write(result.stderr.replace(/\n/g, "\r\n"));
317
+ renderLine();
318
+ }
319
+ else {
320
+ if (nextPrompt)
321
+ pendingSudo.prompt = nextPrompt;
322
+ stream.write(pendingSudo.prompt);
323
+ }
324
+ return;
325
+ }
326
+ // ── Default sudo mode — verify current user's password ─────
327
+ const valid = shell.users.verifyPassword(pendingSudo.username, typed);
306
328
  await finishSudoPrompt(valid);
307
329
  return;
308
330
  }
@@ -5,15 +5,8 @@ import type { Pipeline, Script } from "../types/pipeline";
5
5
  * by |).
6
6
  */
7
7
  export declare function parseScript(rawInput: string): Script;
8
- /** Legacy compat: parse a single pipeline (no &&/||/;) */
8
+ /** Parse a single pipeline string (no &&/||/;) into a `Pipeline` object. */
9
9
  export declare function parseShellPipeline(rawInput: string): Pipeline;
10
- /**
11
- * Expand ~ and $VAR / ${VAR} / ${VAR:-default} / $(cmd placeholder) in a
12
- * token, given the current env vars and home path.
13
- * Command substitution $(…) is NOT executed here — it's left as a marker so
14
- * the executor can handle it.
15
- */
16
- export declare function expandToken(token: string, env: Record<string, string>, authUser: string, lastExitCode?: number): string;
17
10
  /**
18
11
  * Expand glob patterns (*, ?, [abc]) against a list of entries.
19
12
  * Returns the original pattern if no match.
@@ -1 +1 @@
1
- {"version":3,"file":"shellParser.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/shellParser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,QAAQ,EAER,MAAM,EAGN,MAAM,mBAAmB,CAAC;AAI3B;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUpD;AAED,0DAA0D;AAC1D,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAS7D;AAID;;;;;GAKG;AACH,wBAAgB,WAAW,CAC1B,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3B,QAAQ,EAAE,MAAM,EAChB,YAAY,SAAI,GACd,MAAM,CA8BR;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAKvE"}
1
+ {"version":3,"file":"shellParser.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/shellParser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,QAAQ,EAER,MAAM,EAGN,MAAM,mBAAmB,CAAC;AAK3B;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUpD;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAS7D;AAID;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAKvE"}
@@ -1,3 +1,4 @@
1
+ import { tokenizeCommand } from "../utils/tokenize";
1
2
  // ── Public API ───────────────────────────────────────────────────────────────
2
3
  /**
3
4
  * Parse a shell input line into a Script (sequence of statements connected
@@ -16,7 +17,7 @@ export function parseScript(rawInput) {
16
17
  return { statements: [], isValid: false, error: e.message };
17
18
  }
18
19
  }
19
- /** Legacy compat: parse a single pipeline (no &&/||/;) */
20
+ /** Parse a single pipeline string (no &&/||/;) into a `Pipeline` object. */
20
21
  export function parseShellPipeline(rawInput) {
21
22
  const trimmed = rawInput.trim();
22
23
  if (!trimmed)
@@ -30,30 +31,6 @@ export function parseShellPipeline(rawInput) {
30
31
  }
31
32
  }
32
33
  // ── Variable & tilde expansion ────────────────────────────────────────────────
33
- /**
34
- * Expand ~ and $VAR / ${VAR} / ${VAR:-default} / $(cmd placeholder) in a
35
- * token, given the current env vars and home path.
36
- * Command substitution $(…) is NOT executed here — it's left as a marker so
37
- * the executor can handle it.
38
- */
39
- export function expandToken(token, env, authUser, lastExitCode = 0) {
40
- // tilde expansion
41
- token = token.replace(/^~(\/|$)/, `/home/${authUser}$1`);
42
- // $? special var
43
- token = token.replace(/\$\?/g, String(lastExitCode));
44
- // $$ PID (mock)
45
- token = token.replace(/\$\$/g, "1");
46
- // $# argc (0 for interactive)
47
- token = token.replace(/\$#/g, "0");
48
- // ${VAR:-default} and ${VAR:+value}
49
- token = token.replace(/\$\{([^}:]+):-([^}]*)\}/g, (_, name, def) => env[name] ?? def);
50
- token = token.replace(/\$\{([^}:]+):\+([^}]*)\}/g, (_, name, val) => env[name] ? val : "");
51
- // ${VAR}
52
- token = token.replace(/\$\{([^}]+)\}/g, (_, name) => env[name] ?? "");
53
- // $VAR (greedy: match longest valid identifier)
54
- token = token.replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, name) => env[name] ?? "");
55
- return token;
56
- }
57
34
  /**
58
35
  * Expand glob patterns (*, ?, [abc]) against a list of entries.
59
36
  * Returns the original pattern if no match.
@@ -258,59 +235,3 @@ function parseCommandWithRedirections(token) {
258
235
  const name = (cmdParts[0] ?? "").toLowerCase();
259
236
  return { name, args: cmdParts.slice(1), inputFile, outputFile, appendOutput };
260
237
  }
261
- function tokenizeCommand(input) {
262
- const tokens = [];
263
- let current = "";
264
- let inQ = false;
265
- let qChar = "";
266
- let i = 0;
267
- while (i < input.length) {
268
- const ch = input[i];
269
- const next = input[i + 1];
270
- if ((ch === '"' || ch === "'") && !inQ) {
271
- inQ = true;
272
- qChar = ch;
273
- i++;
274
- continue;
275
- }
276
- if (inQ && ch === qChar) {
277
- inQ = false;
278
- qChar = "";
279
- i++;
280
- continue;
281
- }
282
- if (inQ) {
283
- current += ch;
284
- i++;
285
- continue;
286
- }
287
- if (ch === " ") {
288
- if (current) {
289
- tokens.push(current);
290
- current = "";
291
- }
292
- i++;
293
- continue;
294
- }
295
- if ((ch === ">" || ch === "<") && !inQ) {
296
- if (current) {
297
- tokens.push(current);
298
- current = "";
299
- }
300
- if (ch === ">" && next === ">") {
301
- tokens.push(">>");
302
- i += 2;
303
- }
304
- else {
305
- tokens.push(ch);
306
- i++;
307
- }
308
- continue;
309
- }
310
- current += ch;
311
- i++;
312
- }
313
- if (current)
314
- tokens.push(current);
315
- return tokens;
316
- }
@@ -218,7 +218,13 @@ export declare class VirtualUserManager extends EventEmitter {
218
218
  * @param password Plaintext password string.
219
219
  * @returns Hex-encoded hash string.
220
220
  */
221
- hashPassword(password: string): string;
221
+ /**
222
+ * Hash a password with an optional salt.
223
+ * When salt is provided (verify path), the same salt is used for a
224
+ * deterministic hash. When omitted (create path), an empty salt is used
225
+ * for backward compat — callers should pass the stored salt on verify.
226
+ */
227
+ hashPassword(password: string, salt?: string): string;
222
228
  private validateUsername;
223
229
  private validatePassword;
224
230
  private readonly authorizedKeys;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualUserManager/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualUserManager/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AAE1D,gDAAgD;AAChD,MAAM,WAAW,iBAAiB;IACjC,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,2DAA2D;AAC3D,MAAM,WAAW,oBAAoB;IACpC,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;CAClB;AAYD;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,YAAY;IAqBlD,OAAO,CAAC,QAAQ,CAAC,GAAG;IAGpB,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IAvBrC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAwC;IAC3E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAA6B;IACrE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoC;IAC9D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmC;IAC/D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwC;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA2C;IAC1E,OAAO,CAAC,OAAO,CAAK;IAEpB;;;;;;OAMG;gBAEe,GAAG,EAAE,iBAAiB,EAGtB,mBAAmB,GAAE,OAAc;IAMrD;;;OAGG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAsCxC;;;;;OAKG;IACU,aAAa,CACzB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC;IAehB;;;;OAIG;IACU,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxD;;;;;OAKG;IACI,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAKrD;;;;;OAKG;IACI,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAU9C;;;;;;;;OAQG;IACI,sBAAsB,CAC5B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAAG,MAAM,GAC1B,IAAI;IAoCP;;;;;;OAMG;IACI,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAsBlE;;;;;OAKG;IACU,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BvE;;;;;OAKG;IACI,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAMvD;;;;;;OAMG;IACU,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa3E;;;;;OAKG;IACU,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBxD;;;;;OAKG;IACI,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAK1C;;;;;OAKG;IACU,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvD;;;;;OAKG;IACU,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW1D;;;;;;;;;OASG;IACI,eAAe,CACrB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GACnB,oBAAoB;IAkBvB;;;;;;OAMG;IACI,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAiBpE;;;;;;;;;;OAUG;IACI,aAAa,CACnB,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GACnB,IAAI;IAkBP;;;;;;OAMG;IACI,kBAAkB,IAAI,oBAAoB,EAAE;IAOnD;;;;OAIG;IACI,SAAS,IAAI,MAAM,EAAE;IAI5B,OAAO,CAAC,WAAW;IA4BnB,OAAO,CAAC,kBAAkB;IAgB1B,OAAO,CAAC,iBAAiB;YAwBX,OAAO;IA0CrB,OAAO,CAAC,cAAc;IAiBtB,OAAO,CAAC,YAAY;IAoBpB;;;;;;;OAOG;IACI,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAU7C;;;;;;;;OAQG;IACH;;;;;OAKG;IACI,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,MAAM;IAWxD,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAG3B;IAEJ;;;;;;OAMG;IACI,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAQ3E;;;;OAIG;IACI,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKnD;;;;;OAKG;IACI,iBAAiB,CACvB,QAAQ,EAAE,MAAM,GACd,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAGxC"}
@@ -1,4 +1,4 @@
1
- import { createHash, randomBytes, randomUUID, scryptSync } from "node:crypto";
1
+ import { createHash, randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
2
2
  import { EventEmitter } from "node:events";
3
3
  import * as path from "node:path";
4
4
  import { createPerfLogger } from "../utils/perfLogger";
@@ -65,14 +65,14 @@ export class VirtualUserManager extends EventEmitter {
65
65
  // this.users.set(currentUser, this.createRecord(currentUser, userPassword));
66
66
  // this.sudoers.add(currentUser);
67
67
  // changed = true;
68
- // const homePath = `/home/${currentUser}`;
69
- // if (!this.vfs.exists(homePath)) {
70
- // this.vfs.mkdir(homePath, 0o755);
71
- // this.vfs.writeFile(
72
- // `${homePath}/README.txt`,
73
- // `Welcome to the virtual environment, ${currentUser}`,
74
- // );
75
- // }
68
+ // }
69
+ // const homePath = `/home/root`;
70
+ // if (!this.vfs.exists(homePath)) {
71
+ // this.vfs.mkdir(homePath, 0o755);
72
+ // this.vfs.writeFile(
73
+ // `${homePath}/README.txt`,
74
+ // `Welcome to the virtual environment, root`,
75
+ // );
76
76
  // }
77
77
  if (changed) {
78
78
  await this.persist();
@@ -180,9 +180,23 @@ export class VirtualUserManager extends EventEmitter {
180
180
  perf.mark("verifyPassword");
181
181
  const record = this.users.get(username);
182
182
  if (!record) {
183
+ // Perform a dummy hash to avoid timing leakage on unknown usernames
184
+ this.hashPassword(password, "");
183
185
  return false;
184
186
  }
185
- return this.hashPassword(password) === record.passwordHash;
187
+ const computed = this.hashPassword(password, record.salt);
188
+ const expected = record.passwordHash;
189
+ // timingSafeEqual prevents timing-based password oracle attacks
190
+ try {
191
+ const a = Buffer.from(computed, "hex");
192
+ const b = Buffer.from(expected, "hex");
193
+ if (a.length !== b.length)
194
+ return false;
195
+ return timingSafeEqual(a, b);
196
+ }
197
+ catch {
198
+ return computed === expected;
199
+ }
186
200
  }
187
201
  /**
188
202
  * Creates user, home directory, and sudo access entry.
@@ -481,7 +495,8 @@ export class VirtualUserManager extends EventEmitter {
481
495
  return true;
482
496
  }
483
497
  createRecord(username, password) {
484
- const cacheKey = `${username}:${password}`;
498
+ // Cache key is a hash of the inputs — never store plaintext password in memory
499
+ const cacheKey = createHash("sha256").update(username).update(":").update(password).digest("hex");
485
500
  const cached = VirtualUserManager.recordCache.get(cacheKey);
486
501
  if (cached) {
487
502
  return cached;
@@ -490,7 +505,8 @@ export class VirtualUserManager extends EventEmitter {
490
505
  const record = {
491
506
  username,
492
507
  salt,
493
- passwordHash: this.hashPassword(password),
508
+ // Hash uses the generated salt — verifyPassword must use record.salt
509
+ passwordHash: this.hashPassword(password, salt),
494
510
  };
495
511
  VirtualUserManager.recordCache.set(cacheKey, record);
496
512
  return record;
@@ -505,11 +521,14 @@ export class VirtualUserManager extends EventEmitter {
505
521
  */
506
522
  hasPassword(username) {
507
523
  perf.mark("hasPassword");
508
- if (this.getPasswordHash(username) === this.hashPassword("")) {
509
- return false;
510
- }
511
524
  const record = this.users.get(username);
512
- return !!record && !!record.passwordHash;
525
+ if (!record)
526
+ return false;
527
+ // Empty password hash computed with the record's own salt
528
+ const emptyHash = this.hashPassword("", record.salt);
529
+ if (record.passwordHash === emptyHash)
530
+ return false;
531
+ return !!record.passwordHash;
513
532
  }
514
533
  /**
515
534
  * Hashes a plaintext password using scrypt (or SHA-256 in fast-hash mode).
@@ -520,11 +539,20 @@ export class VirtualUserManager extends EventEmitter {
520
539
  * @param password Plaintext password string.
521
540
  * @returns Hex-encoded hash string.
522
541
  */
523
- hashPassword(password) {
542
+ /**
543
+ * Hash a password with an optional salt.
544
+ * When salt is provided (verify path), the same salt is used for a
545
+ * deterministic hash. When omitted (create path), an empty salt is used
546
+ * for backward compat — callers should pass the stored salt on verify.
547
+ */
548
+ hashPassword(password, salt = "") {
524
549
  if (VirtualUserManager.fastPasswordHash) {
525
- return createHash("sha256").update(`${password}`).digest("hex");
550
+ return createHash("sha256")
551
+ .update(salt)
552
+ .update(password)
553
+ .digest("hex");
526
554
  }
527
- return scryptSync(password, "", 32).toString("hex");
555
+ return scryptSync(password, salt || "", 32).toString("hex");
528
556
  }
529
557
  validateUsername(username) {
530
558
  if (!username || username.trim() === "") {
@@ -1,9 +1,15 @@
1
1
  import type { ShellModule } from "../types/commands";
2
2
  /**
3
- * Add a new user to the virtual user database.
4
- * @category users
5
- * @params ["<username> <password>"]
6
- * @returns ShellModule
3
+ * Add a new user interactively.
4
+ *
5
+ * Usage: `adduser <username>`
6
+ *
7
+ * Prompts for:
8
+ * New password: ****
9
+ * Retype new password: ****
10
+ *
11
+ * Mirrors the real `adduser` behaviour — password is never passed on the
12
+ * command line. Root-only.
7
13
  */
8
14
  export declare const adduserCommand: ShellModule;
9
15
  //# sourceMappingURL=adduser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adduser.d.ts","sourceRoot":"","sources":["../../src/commands/adduser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,WAqB5B,CAAC"}
1
+ {"version":3,"file":"adduser.d.ts","sourceRoot":"","sources":["../../src/commands/adduser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGpE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,EAAE,WAuF5B,CAAC"}