typescript-virtual-container 1.2.5 → 1.2.6

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 (245) hide show
  1. package/README.md +387 -193
  2. package/benchmark-results.txt +21 -21
  3. package/dist/SSHMimic/exec.js +2 -2
  4. package/dist/SSHMimic/executor.d.ts +6 -7
  5. package/dist/SSHMimic/executor.d.ts.map +1 -1
  6. package/dist/SSHMimic/executor.js +77 -60
  7. package/dist/SSHMimic/index.d.ts.map +1 -1
  8. package/dist/SSHMimic/index.js +6 -20
  9. package/dist/SSHMimic/sftp.d.ts.map +1 -1
  10. package/dist/SSHMimic/sftp.js +14 -0
  11. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  12. package/dist/VirtualFileSystem/index.js +13 -36
  13. package/dist/VirtualShell/shell.d.ts.map +1 -1
  14. package/dist/VirtualShell/shell.js +19 -2
  15. package/dist/VirtualShell/shellParser.d.ts +20 -2
  16. package/dist/VirtualShell/shellParser.d.ts.map +1 -1
  17. package/dist/VirtualShell/shellParser.js +229 -120
  18. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  19. package/dist/commands/adduser.d.ts.map +1 -1
  20. package/dist/commands/adduser.js +2 -0
  21. package/dist/commands/awk.d.ts +3 -0
  22. package/dist/commands/awk.d.ts.map +1 -0
  23. package/dist/commands/awk.js +29 -0
  24. package/dist/commands/base64.d.ts +3 -0
  25. package/dist/commands/base64.d.ts.map +1 -0
  26. package/dist/commands/base64.js +20 -0
  27. package/dist/commands/cat.d.ts.map +1 -1
  28. package/dist/commands/cat.js +2 -0
  29. package/dist/commands/cd.d.ts.map +1 -1
  30. package/dist/commands/cd.js +2 -0
  31. package/dist/commands/chmod.d.ts.map +1 -1
  32. package/dist/commands/chmod.js +2 -0
  33. package/dist/commands/clear.d.ts.map +1 -1
  34. package/dist/commands/clear.js +4 -1
  35. package/dist/commands/cp.d.ts.map +1 -1
  36. package/dist/commands/cp.js +2 -0
  37. package/dist/commands/curl.d.ts.map +1 -1
  38. package/dist/commands/curl.js +2 -0
  39. package/dist/commands/cut.d.ts +3 -0
  40. package/dist/commands/cut.d.ts.map +1 -0
  41. package/dist/commands/cut.js +27 -0
  42. package/dist/commands/date.d.ts +3 -0
  43. package/dist/commands/date.d.ts.map +1 -0
  44. package/dist/commands/date.js +22 -0
  45. package/dist/commands/deluser.d.ts.map +1 -1
  46. package/dist/commands/deluser.js +2 -0
  47. package/dist/commands/df.d.ts +3 -0
  48. package/dist/commands/df.d.ts.map +1 -0
  49. package/dist/commands/df.js +16 -0
  50. package/dist/commands/diff.d.ts +3 -0
  51. package/dist/commands/diff.d.ts.map +1 -0
  52. package/dist/commands/diff.js +40 -0
  53. package/dist/commands/du.d.ts +3 -0
  54. package/dist/commands/du.d.ts.map +1 -0
  55. package/dist/commands/du.js +39 -0
  56. package/dist/commands/echo.d.ts.map +1 -1
  57. package/dist/commands/echo.js +2 -0
  58. package/dist/commands/env.d.ts.map +1 -1
  59. package/dist/commands/env.js +6 -14
  60. package/dist/commands/export.d.ts.map +1 -1
  61. package/dist/commands/export.js +11 -21
  62. package/dist/commands/find.d.ts.map +1 -1
  63. package/dist/commands/find.js +2 -0
  64. package/dist/commands/grep.d.ts.map +1 -1
  65. package/dist/commands/grep.js +4 -7
  66. package/dist/commands/groups.d.ts +3 -0
  67. package/dist/commands/groups.d.ts.map +1 -0
  68. package/dist/commands/groups.js +12 -0
  69. package/dist/commands/gzip.d.ts +4 -0
  70. package/dist/commands/gzip.d.ts.map +1 -0
  71. package/dist/commands/gzip.js +40 -0
  72. package/dist/commands/head.d.ts.map +1 -1
  73. package/dist/commands/head.js +2 -0
  74. package/dist/commands/help.d.ts +1 -1
  75. package/dist/commands/help.d.ts.map +1 -1
  76. package/dist/commands/help.js +75 -3
  77. package/dist/commands/hostname.d.ts.map +1 -1
  78. package/dist/commands/hostname.js +2 -0
  79. package/dist/commands/htop.d.ts.map +1 -1
  80. package/dist/commands/htop.js +2 -0
  81. package/dist/commands/id.d.ts +3 -0
  82. package/dist/commands/id.d.ts.map +1 -0
  83. package/dist/commands/id.js +14 -0
  84. package/dist/commands/index.d.ts +5 -2
  85. package/dist/commands/index.d.ts.map +1 -1
  86. package/dist/commands/index.js +89 -62
  87. package/dist/commands/kill.d.ts +3 -0
  88. package/dist/commands/kill.d.ts.map +1 -0
  89. package/dist/commands/kill.js +13 -0
  90. package/dist/commands/ln.d.ts.map +1 -1
  91. package/dist/commands/ln.js +2 -0
  92. package/dist/commands/ls.d.ts.map +1 -1
  93. package/dist/commands/ls.js +2 -0
  94. package/dist/commands/mkdir.d.ts.map +1 -1
  95. package/dist/commands/mkdir.js +2 -0
  96. package/dist/commands/mv.d.ts.map +1 -1
  97. package/dist/commands/mv.js +2 -0
  98. package/dist/commands/nano.d.ts.map +1 -1
  99. package/dist/commands/nano.js +2 -0
  100. package/dist/commands/neofetch.d.ts.map +1 -1
  101. package/dist/commands/neofetch.js +2 -0
  102. package/dist/commands/passwd.d.ts.map +1 -1
  103. package/dist/commands/passwd.js +2 -0
  104. package/dist/commands/ping.d.ts +3 -0
  105. package/dist/commands/ping.d.ts.map +1 -0
  106. package/dist/commands/ping.js +18 -0
  107. package/dist/commands/ps.d.ts +3 -0
  108. package/dist/commands/ps.d.ts.map +1 -0
  109. package/dist/commands/ps.js +17 -0
  110. package/dist/commands/pwd.d.ts.map +1 -1
  111. package/dist/commands/pwd.js +2 -0
  112. package/dist/commands/rm.d.ts.map +1 -1
  113. package/dist/commands/rm.js +2 -0
  114. package/dist/commands/sed.d.ts +3 -0
  115. package/dist/commands/sed.d.ts.map +1 -0
  116. package/dist/commands/sed.js +47 -0
  117. package/dist/commands/set.d.ts +3 -0
  118. package/dist/commands/set.d.ts.map +1 -1
  119. package/dist/commands/set.js +19 -46
  120. package/dist/commands/sh.d.ts +0 -1
  121. package/dist/commands/sh.d.ts.map +1 -1
  122. package/dist/commands/sh.js +228 -35
  123. package/dist/commands/sleep.d.ts +3 -0
  124. package/dist/commands/sleep.d.ts.map +1 -0
  125. package/dist/commands/sleep.js +13 -0
  126. package/dist/commands/sort.d.ts +3 -0
  127. package/dist/commands/sort.d.ts.map +1 -0
  128. package/dist/commands/sort.js +37 -0
  129. package/dist/commands/su.d.ts.map +1 -1
  130. package/dist/commands/su.js +2 -0
  131. package/dist/commands/sudo.d.ts.map +1 -1
  132. package/dist/commands/sudo.js +2 -0
  133. package/dist/commands/tail.d.ts.map +1 -1
  134. package/dist/commands/tail.js +2 -0
  135. package/dist/commands/tar.d.ts +3 -0
  136. package/dist/commands/tar.d.ts.map +1 -0
  137. package/dist/commands/tar.js +64 -0
  138. package/dist/commands/tee.d.ts +3 -0
  139. package/dist/commands/tee.d.ts.map +1 -0
  140. package/dist/commands/tee.js +29 -0
  141. package/dist/commands/touch.d.ts.map +1 -1
  142. package/dist/commands/touch.js +2 -0
  143. package/dist/commands/tr.d.ts +3 -0
  144. package/dist/commands/tr.d.ts.map +1 -0
  145. package/dist/commands/tr.js +24 -0
  146. package/dist/commands/tree.d.ts.map +1 -1
  147. package/dist/commands/tree.js +2 -0
  148. package/dist/commands/uname.d.ts +3 -0
  149. package/dist/commands/uname.d.ts.map +1 -0
  150. package/dist/commands/uname.js +21 -0
  151. package/dist/commands/uniq.d.ts +3 -0
  152. package/dist/commands/uniq.d.ts.map +1 -0
  153. package/dist/commands/uniq.js +33 -0
  154. package/dist/commands/unset.d.ts.map +1 -1
  155. package/dist/commands/unset.js +6 -10
  156. package/dist/commands/wc.d.ts.map +1 -1
  157. package/dist/commands/wc.js +2 -0
  158. package/dist/commands/wget.d.ts.map +1 -1
  159. package/dist/commands/wget.js +2 -0
  160. package/dist/commands/who.d.ts.map +1 -1
  161. package/dist/commands/who.js +2 -0
  162. package/dist/commands/whoami.d.ts.map +1 -1
  163. package/dist/commands/whoami.js +2 -0
  164. package/dist/commands/xargs.d.ts +3 -0
  165. package/dist/commands/xargs.d.ts.map +1 -0
  166. package/dist/commands/xargs.js +16 -0
  167. package/dist/types/commands.d.ts +13 -0
  168. package/dist/types/commands.d.ts.map +1 -1
  169. package/dist/types/pipeline.d.ts +20 -0
  170. package/dist/types/pipeline.d.ts.map +1 -1
  171. package/package.json +1 -1
  172. package/src/SSHMimic/exec.ts +2 -2
  173. package/src/SSHMimic/executor.ts +95 -98
  174. package/src/SSHMimic/index.ts +15 -49
  175. package/src/SSHMimic/sftp.ts +15 -0
  176. package/src/VirtualFileSystem/index.ts +27 -75
  177. package/src/VirtualShell/shell.ts +19 -2
  178. package/src/VirtualShell/shellParser.ts +202 -168
  179. package/src/VirtualUserManager/index.ts +2 -7
  180. package/src/commands/adduser.ts +2 -0
  181. package/src/commands/awk.ts +30 -0
  182. package/src/commands/base64.ts +18 -0
  183. package/src/commands/cat.ts +2 -0
  184. package/src/commands/cd.ts +2 -0
  185. package/src/commands/chmod.ts +2 -0
  186. package/src/commands/clear.ts +4 -1
  187. package/src/commands/cp.ts +2 -0
  188. package/src/commands/curl.ts +2 -0
  189. package/src/commands/cut.ts +29 -0
  190. package/src/commands/date.ts +24 -0
  191. package/src/commands/deluser.ts +2 -0
  192. package/src/commands/df.ts +18 -0
  193. package/src/commands/diff.ts +29 -0
  194. package/src/commands/du.ts +39 -0
  195. package/src/commands/echo.ts +2 -0
  196. package/src/commands/env.ts +6 -16
  197. package/src/commands/export.ts +11 -24
  198. package/src/commands/find.ts +2 -0
  199. package/src/commands/grep.ts +4 -7
  200. package/src/commands/groups.ts +14 -0
  201. package/src/commands/gzip.ts +31 -0
  202. package/src/commands/head.ts +2 -0
  203. package/src/commands/help.ts +81 -3
  204. package/src/commands/hostname.ts +2 -0
  205. package/src/commands/htop.ts +2 -0
  206. package/src/commands/id.ts +16 -0
  207. package/src/commands/index.ts +98 -99
  208. package/src/commands/kill.ts +14 -0
  209. package/src/commands/ln.ts +2 -0
  210. package/src/commands/ls.ts +2 -0
  211. package/src/commands/mkdir.ts +2 -0
  212. package/src/commands/mv.ts +2 -0
  213. package/src/commands/nano.ts +2 -0
  214. package/src/commands/neofetch.ts +2 -0
  215. package/src/commands/passwd.ts +2 -0
  216. package/src/commands/ping.ts +20 -0
  217. package/src/commands/ps.ts +19 -0
  218. package/src/commands/pwd.ts +2 -0
  219. package/src/commands/rm.ts +2 -0
  220. package/src/commands/sed.ts +45 -0
  221. package/src/commands/set.ts +19 -50
  222. package/src/commands/sh.ts +192 -43
  223. package/src/commands/sleep.ts +14 -0
  224. package/src/commands/sort.ts +37 -0
  225. package/src/commands/su.ts +2 -0
  226. package/src/commands/sudo.ts +2 -0
  227. package/src/commands/tail.ts +2 -0
  228. package/src/commands/tar.ts +58 -0
  229. package/src/commands/tee.ts +25 -0
  230. package/src/commands/touch.ts +2 -0
  231. package/src/commands/tr.ts +24 -0
  232. package/src/commands/tree.ts +2 -0
  233. package/src/commands/uname.ts +20 -0
  234. package/src/commands/uniq.ts +28 -0
  235. package/src/commands/unset.ts +5 -12
  236. package/src/commands/wc.ts +2 -0
  237. package/src/commands/wget.ts +2 -0
  238. package/src/commands/who.ts +2 -0
  239. package/src/commands/whoami.ts +2 -0
  240. package/src/commands/xargs.ts +17 -0
  241. package/src/types/commands.ts +14 -0
  242. package/src/types/pipeline.ts +23 -0
  243. package/standalone.js +92 -64
  244. package/standalone.js.map +4 -4
  245. package/tests/users.test.ts +5 -34
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # `typescript-virtual-container`
2
2
 
3
- > Pure in-memory SSH/SFTP server with a virtual filesystem and typed programmatic API for testing, automation, honeypots, and interactive shell scripting in TypeScript/JavaScript.
3
+ > Pure in-memory SSH/SFTP server with a virtual filesystem, a real shell interpreter, and a typed programmatic API for testing, automation, honeypots, and interactive shell scripting in TypeScript/JavaScript.
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/typescript-virtual-container.svg)](https://www.npmjs.com/package/typescript-virtual-container)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -27,6 +27,7 @@
27
27
  - [Key Types](#key-types)
28
28
  - [Usage Examples](#usage-examples)
29
29
  - [Built-in Commands](#built-in-commands)
30
+ - [Shell Scripting](#shell-scripting)
30
31
  - [Configuration](#configuration)
31
32
  - [Performance & Scalability](#performance--scalability)
32
33
  - [Types & TypeScript](#types--typescript)
@@ -51,9 +52,11 @@
51
52
  - **Rate limiting / brute-force protection**: Configurable per-IP lockout after N failed auth attempts.
52
53
  - **User Management**: Create, authenticate, and manage virtual users with scrypt password hashing, sudo-like privilege elevation, and optional per-user disk quotas.
53
54
  - **Programmatic Shell API**: Execute shell commands and query filesystem state directly from TypeScript without SSH overhead.
54
- - **Event-Driven Architecture**: All core classes extend `EventEmitter` for lifecycle and operation tracking. Listen to auth events, filesystem operations, session lifecycle, and command execution for auditing and integration.
55
+ - **Real shell interpreter**: `&&` / `||` / `;` operators, `if`/`elif`/`else`/`fi`, `for`/`do`/`done`, `while`/`do`/`done`, variable expansion (`$VAR`, `${VAR:-default}`), `$?`, per-session environment.
56
+ - **`.bashrc` support**: Loaded automatically at interactive session start from `/home/<user>/.bashrc`.
57
+ - **Event-Driven Architecture**: All core classes extend `EventEmitter` for lifecycle and operation tracking.
55
58
  - **Security Auditing**: Built-in `HoneyPot` utility for comprehensive activity logging, event tracking, statistics collection, and anomaly detection across all components.
56
- - **40+ Built-in Commands**: `ls`, `cd`, `cat`, `cp`, `mv`, `ln`, `find`, `grep`, `wc`, `head`, `tail`, `chmod`, `mkdir`, `touch`, `rm`, `tree`, `nano`, `curl`, `wget`, `sudo`, `su`, `adduser`, `deluser`, and more. Shell compatibility is still being expanded.
59
+ - **60+ Built-in Commands**: Full navigation, text processing, archiving, system info, and user management commands grouped and documented in the interactive `help` system.
57
60
  - **Full TypeScript Support**: Complete JSDoc coverage, exported types, and first-class async/await for all operations.
58
61
 
59
62
  ---
@@ -63,7 +66,7 @@
63
66
  ### What This Is
64
67
 
65
68
  - A virtual shell runtime written in TypeScript with a **pure in-memory filesystem**.
66
- - A virtual environment with its own filesystem, user management, and command runtime.
69
+ - A virtual environment with its own filesystem, user management, and a real shell interpreter.
67
70
  - A practical tool for deterministic testing, automation pipelines, and SSH-like workflows without running real containers.
68
71
  - A honeypot framework for capturing and auditing attacker behavior.
69
72
 
@@ -85,6 +88,7 @@ This package is designed for teams that need a realistic SSH-like runtime withou
85
88
  - **Deterministic test environments**: Repeatable state for CI pipelines and integration tests. Build a fixture snapshot once, hydrate for each test.
86
89
  - **Low operational overhead**: No Docker daemon, no kernel namespaces, no privileged setup.
87
90
  - **Fast feedback loops**: Programmatic API for command execution and filesystem assertions.
91
+ - **Real shell scripting**: `&&`/`||`/`;`, `if`/`for`/`while`, variable expansion — not just command dispatch.
88
92
  - **Developer-friendly internals**: Typed APIs, clear boundaries, composable building blocks, and full JSDoc.
89
93
 
90
94
  ---
@@ -208,7 +212,7 @@ ssh.stop();
208
212
 
209
213
  ### Execution Modes
210
214
 
211
- 1. **SSH Shell Mode**: Interactive terminal session over SSH with readline, prompt, history, TTY resizing.
215
+ 1. **SSH Shell Mode**: Interactive terminal session over SSH with readline, history, `.bashrc` loading, TTY resizing, `Ctrl+W` word delete, `Ctrl+U` line clear.
212
216
  2. **SSH Exec Mode**: Non-interactive command execution (e.g. `ssh user@host "ls -la"`).
213
217
  3. **SFTP Mode**: Remote file operations (`readdir`, `stat`, `readFile`, `writeFile`, `mkdir`, `rename`, etc.) with home-directory confinement.
214
218
  4. **Programmatic Mode**: Direct TypeScript API via `SshClient` — no SSH protocol overhead.
@@ -222,8 +226,9 @@ ssh.stop();
222
226
 
223
227
  ┌──────────▼──────────┐
224
228
  │ VirtualShell │
225
- pipeline parser
226
- │ command executor │
229
+ script parser ← &&/||/; · if/for/while
230
+ │ command executor │ ← per-session ShellEnv
231
+ │ .bashrc loader │ ← /home/<user>/.bashrc
227
232
  │ session manager │
228
233
  └──┬──────────────┬───┘
229
234
  │ │
@@ -338,9 +343,9 @@ new VirtualSftpServer({
338
343
 
339
344
  #### Behavior Notes
340
345
 
341
- - Supports `password` and `keyboard-interactive` authentication.
346
+ - Supports `password` and `keyboard-interactive` authentication. Users without a password set are accepted on any attempt.
342
347
  - Resolves relative SFTP paths from `/home/<user>`.
343
- - Confines all SFTP operations to `/home/<user>` — blocks traversal attempts.
348
+ - Confines all SFTP operations to `/home/<user>` — blocks traversal attempts outside home.
344
349
  - Unsupported operations (`READLINK`, `SYMLINK`) return `OP_UNSUPPORTED`.
345
350
 
346
351
  #### Events
@@ -578,6 +583,7 @@ new VirtualUserManager(
578
583
  | `hashPassword(password): string` | Hash a password using the configured algorithm. |
579
584
  | `addUser(username, password): Promise<void>` | Create user with home directory. |
580
585
  | `deleteUser(username): Promise<void>` | Delete user. Cannot delete root. |
586
+ | `setPassword(username, password): Promise<void>` | Update password for an existing user. |
581
587
  | `isSudoer(username): boolean` | Check if user has sudo privileges. |
582
588
  | `addSudoer(username): Promise<void>` | Grant sudo privileges. |
583
589
  | `removeSudoer(username): Promise<void>` | Revoke sudo privileges. Cannot remove root. |
@@ -672,6 +678,8 @@ interface AuditLogEntry {
672
678
  }
673
679
  ```
674
680
 
681
+ `detectAnomalies` detects: high authentication failure rates, excessive auth failures, unusual command volume, unusual file write volume.
682
+
675
683
  #### Example
676
684
 
677
685
  ```typescript
@@ -700,12 +708,6 @@ process.on("SIGINT", () => {
700
708
  });
701
709
  ```
702
710
 
703
- **`detectAnomalies` detects:**
704
- - High authentication failure rates
705
- - Excessive authentication failures
706
- - Unusual command execution volume
707
- - Unusual file write volume
708
-
709
711
  ---
710
712
 
711
713
  ### `SshClient` (Programmatic API)
@@ -724,7 +726,7 @@ No password required — the client authenticates by username only.
724
726
 
725
727
  | Method | Description |
726
728
  |--------|-------------|
727
- | `exec(command): Promise<CommandResult>` | Run arbitrary raw command string. |
729
+ | `exec(command): Promise<CommandResult>` | Run arbitrary raw command string (supports `&&`, `\|`, etc.). |
728
730
  | `ls(path?)` | List directory (default: cwd). |
729
731
  | `pwd()` | Print current working directory. |
730
732
  | `cd(path)` | Change directory. Updates internal cwd state on success. |
@@ -758,6 +760,10 @@ console.log(list.stdout); // notes.txt
758
760
 
759
761
  const read = await client.readFile("notes.txt");
760
762
  console.log(read.stdout); // Work in progress
763
+
764
+ // Shell operators work in exec()
765
+ const r = await client.exec("echo hello && echo world");
766
+ console.log(r.stdout); // hello\nworld
761
767
  ```
762
768
 
763
769
  ---
@@ -770,16 +776,57 @@ Returned by all command executions (shell or programmatic).
770
776
 
771
777
  ```typescript
772
778
  interface CommandResult {
773
- stdout?: string; // Standard output
774
- stderr?: string; // Standard error
775
- exitCode?: number; // Exit code (default: 0)
776
- nextCwd?: string; // Updated cwd (set by cd command)
777
- clearScreen?: boolean; // Request terminal clear
778
- closeSession?: boolean; // Request session close
779
- switchUser?: string; // User switch (su/sudo)
780
- openEditor?: NanoEditorSession; // Nano editor launch
781
- openHtop?: boolean; // htop launch
782
- sudoChallenge?: SudoChallenge; // Sudo password challenge
779
+ stdout?: string;
780
+ stderr?: string;
781
+ exitCode?: number;
782
+ nextCwd?: string;
783
+ clearScreen?: boolean;
784
+ closeSession?: boolean;
785
+ switchUser?: string;
786
+ openEditor?: NanoEditorSession;
787
+ openHtop?: boolean;
788
+ sudoChallenge?: SudoChallenge;
789
+ }
790
+ ```
791
+
792
+ #### `ShellEnv`
793
+
794
+ Per-session shell environment. Passed as `env` in `CommandContext`.
795
+
796
+ ```typescript
797
+ interface ShellEnv {
798
+ vars: Record<string, string>; // $VAR accessible in expansions
799
+ lastExitCode: number; // $? value
800
+ }
801
+ ```
802
+
803
+ Default variables initialized per session: `PATH`, `HOME`, `USER`, `LOGNAME`, `SHELL`, `TERM`, `HOSTNAME`, `PS1`.
804
+
805
+ #### `ShellModule`
806
+
807
+ Contract for custom command plugins:
808
+
809
+ ```typescript
810
+ interface ShellModule {
811
+ name: string;
812
+ params: string[];
813
+ aliases?: string[];
814
+ description?: string; // shown in grouped help
815
+ category?: string; // navigation|files|text|archive|system|network|shell|users|misc
816
+ run: (ctx: CommandContext) => CommandResult | Promise<CommandResult>;
817
+ }
818
+
819
+ interface CommandContext {
820
+ authUser: string;
821
+ hostname: string;
822
+ activeSessions: VirtualActiveSession[];
823
+ rawInput: string;
824
+ mode: "shell" | "exec";
825
+ args: string[];
826
+ stdin?: string;
827
+ cwd: string;
828
+ shell: VirtualShell;
829
+ env: ShellEnv; // per-session environment (read/write)
783
830
  }
784
831
  ```
785
832
 
@@ -792,8 +839,8 @@ interface VfsFileNode {
792
839
  type: "file";
793
840
  name: string;
794
841
  path: string;
795
- mode: number; // POSIX mode bits
796
- size: number; // Byte length (compressed size when compressed=true)
842
+ mode: number;
843
+ size: number;
797
844
  compressed: boolean;
798
845
  createdAt: Date;
799
846
  updatedAt: Date;
@@ -814,11 +861,11 @@ interface VfsDirectoryNode {
814
861
 
815
862
  ```typescript
816
863
  interface VirtualActiveSession {
817
- id: string; // UUID
864
+ id: string;
818
865
  username: string;
819
- tty: string; // e.g. "pts/0"
820
- remoteAddress: string; // Client IP or label
821
- startedAt: string; // ISO-8601
866
+ tty: string;
867
+ remoteAddress: string;
868
+ startedAt: string; // ISO-8601
822
869
  }
823
870
  ```
824
871
 
@@ -828,35 +875,9 @@ interface VirtualActiveSession {
828
875
  interface VfsSnapshot {
829
876
  root: VfsSnapshotDirectoryNode;
830
877
  }
831
- // VfsSnapshotNode = VfsSnapshotFileNode | VfsSnapshotDirectoryNode
832
878
  // File nodes store content as base64 in contentBase64.
833
879
  ```
834
880
 
835
- #### `ShellModule`
836
-
837
- Contract for custom command plugins:
838
-
839
- ```typescript
840
- interface ShellModule {
841
- name: string;
842
- params: string[];
843
- aliases?: string[];
844
- run: (ctx: CommandContext) => CommandResult | Promise<CommandResult>;
845
- }
846
-
847
- interface CommandContext {
848
- authUser: string;
849
- hostname: string;
850
- activeSessions: VirtualActiveSession[];
851
- rawInput: string;
852
- mode: "shell" | "exec";
853
- args: string[];
854
- stdin?: string;
855
- cwd: string;
856
- shell: VirtualShell;
857
- }
858
- ```
859
-
860
881
  ---
861
882
 
862
883
  ## Usage Examples
@@ -904,9 +925,6 @@ await client.writeFile("/app/config/settings.json", JSON.stringify({
904
925
  const result = await client.readFile("/app/config/settings.json");
905
926
  console.log("Config:", result.stdout);
906
927
 
907
- const list = await client.ls("/app");
908
- console.log(list.stdout);
909
-
910
928
  const tree = await client.tree("/app");
911
929
  console.log(tree.stdout);
912
930
 
@@ -915,7 +933,7 @@ ssh.stop();
915
933
 
916
934
  ---
917
935
 
918
- ### Example 3: Multi-User Environment
936
+ ### Example 3: Multi-User Environment with Quotas
919
937
 
920
938
  ```typescript
921
939
  import { SshClient, VirtualShell, VirtualSshServer } from "typescript-virtual-container";
@@ -929,11 +947,8 @@ const users = ssh.getUsers()!;
929
947
  await users.addUser("alice", "alice123");
930
948
  await users.addUser("bob", "bob456");
931
949
 
932
- // Alice has sudo; Bob does not
933
950
  await users.removeSudoer("bob");
934
-
935
- // Set a 5 MB quota for bob
936
- await users.setQuotaBytes("bob", 5 * 1024 * 1024);
951
+ await users.setQuotaBytes("bob", 5 * 1024 * 1024); // 5 MB
937
952
 
938
953
  const alice = new SshClient(shell, "alice");
939
954
  await alice.writeFile("/etc/important.conf", "secret=yes");
@@ -958,10 +973,8 @@ import { writeFileSync, readFileSync } from "node:fs";
958
973
  const vfs = new VirtualFileSystem();
959
974
  vfs.writeFile("/data/report.txt", "Baseline data");
960
975
 
961
- // Persist
962
976
  writeFileSync("snapshot.json", JSON.stringify(vfs.toSnapshot()));
963
977
 
964
- // Later, in a new process
965
978
  const snapshot = JSON.parse(readFileSync("snapshot.json", "utf8"));
966
979
  const restored = VirtualFileSystem.fromSnapshot(snapshot);
967
980
  console.log(restored.readFile("/data/report.txt")); // Baseline data
@@ -979,14 +992,12 @@ const shell = new VirtualShell("my-vm", undefined, {
979
992
 
980
993
  const ssh = new VirtualSshServer({ port: 2222, shell });
981
994
  await ssh.start();
982
- // Snapshot is auto-restored on start and auto-written on each flushMirror() call.
983
-
984
995
  process.on("SIGTERM", () => { ssh.stop(); process.exit(0); });
985
996
  ```
986
997
 
987
998
  ---
988
999
 
989
- ### Example 5: Public-key authentication
1000
+ ### Example 5: Public-Key Authentication
990
1001
 
991
1002
  ```typescript
992
1003
  import { VirtualShell, VirtualSshServer } from "typescript-virtual-container";
@@ -997,7 +1008,6 @@ await shell.ensureInitialized();
997
1008
 
998
1009
  await shell.users.addUser("alice", "fallback-password");
999
1010
 
1000
- // Parse from ~/.ssh/id_ed25519.pub
1001
1011
  const pubLine = readFileSync(`${process.env.HOME}/.ssh/id_ed25519.pub`, "utf8").trim();
1002
1012
  const [algo, b64] = pubLine.split(" ");
1003
1013
  shell.users.addAuthorizedKey("alice", algo, Buffer.from(b64, "base64"));
@@ -1014,58 +1024,109 @@ await ssh.start();
1014
1024
  ```typescript
1015
1025
  const ssh = new VirtualSshServer({
1016
1026
  port: 2222,
1017
- maxAuthAttempts: 3, // lock after 3 consecutive failures
1018
- lockoutDurationMs: 300_000, // 5-minute lockout
1027
+ maxAuthAttempts: 3,
1028
+ lockoutDurationMs: 300_000,
1019
1029
  });
1020
1030
 
1021
1031
  ssh.on("auth:lockout", ({ ip, until }) => {
1022
1032
  console.warn(`[SSH] ${ip} locked until ${until}`);
1023
1033
  });
1024
1034
 
1025
- ssh.on("auth:failure", ({ username, remoteAddress }) => {
1026
- console.warn(`[SSH] Failed login: ${username} from ${remoteAddress}`);
1027
- });
1035
+ ssh.clearLockout("192.168.1.100"); // manual override
1036
+ ```
1037
+
1038
+ ---
1039
+
1040
+ ### Example 7: Shell Operators and Variables
1041
+
1042
+ ```typescript
1043
+ import { SshClient, VirtualShell } from "typescript-virtual-container";
1044
+
1045
+ const shell = new VirtualShell("typescript-vm");
1046
+ await shell.ensureInitialized();
1047
+ const client = new SshClient(shell, "root");
1048
+
1049
+ // && and || operators
1050
+ await client.exec("mkdir /tmp/test && echo created || echo failed");
1051
+
1052
+ // Chaining with ;
1053
+ await client.exec("echo a; echo b; echo c");
1054
+
1055
+ // Variable expansion via export then use
1056
+ await client.exec("export GREETING=hello");
1057
+ await client.exec("echo $GREETING world"); // hello world
1028
1058
 
1029
- // Manually lift a lockout (e.g. in an admin endpoint)
1030
- ssh.clearLockout("192.168.1.100");
1059
+ // $? last exit code
1060
+ await client.exec("false; echo exit=$?"); // exit=1
1061
+
1062
+ // Piping
1063
+ const r = await client.exec("echo -e 'banana\\napple\\ncherry' | sort");
1064
+ console.log(r.stdout); // apple\nbanana\ncherry
1031
1065
  ```
1032
1066
 
1033
1067
  ---
1034
1068
 
1035
- ### Example 7: CI/CD Automation & Assertions
1069
+ ### Example 8: .bashrc
1036
1070
 
1037
1071
  ```typescript
1038
- import { SshClient, VirtualShell, VirtualSshServer } from "typescript-virtual-container";
1072
+ import { VirtualShell, VirtualSshServer } from "typescript-virtual-container";
1039
1073
 
1040
- async function testDeployment() {
1041
- const shell = new VirtualShell("typescript-vm");
1042
- const ssh = new VirtualSshServer({ port: 2222, shell });
1043
- await ssh.start();
1074
+ const shell = new VirtualShell("typescript-vm");
1075
+ await shell.ensureInitialized();
1044
1076
 
1045
- const client = new SshClient(shell, "root");
1077
+ // Write a .bashrc for the root user
1078
+ shell.vfs.mkdir("/home/root", 0o755);
1079
+ shell.vfs.writeFile("/home/root/.bashrc", `
1080
+ export PS1="\\u@\\h:\\w\\$ "
1081
+ export EDITOR=nano
1082
+ export PATH="/usr/local/bin:/usr/bin:/bin"
1083
+ alias ll="ls -l"
1084
+ echo "Welcome back, root!"
1085
+ `.trim());
1046
1086
 
1047
- await client.mkdir("/srv/app", true);
1048
- await client.writeFile("/srv/app/package.json", '{"name":"myapp","version":"1.0.0"}');
1087
+ const ssh = new VirtualSshServer({ port: 2222, shell });
1088
+ await ssh.start();
1089
+ // On interactive login, .bashrc is sourced automatically.
1090
+ // "Welcome back, root!" is printed, and $EDITOR is set in the session.
1091
+ ```
1049
1092
 
1050
- // Simulate deployment
1051
- await client.writeFile("/srv/app/app.js", 'console.log("v2.0");');
1093
+ ---
1052
1094
 
1053
- const appContent = await client.readFile("/srv/app/app.js");
1054
- if (appContent.stdout.includes("v2.0")) {
1055
- console.log("✓ Deployment verified");
1056
- } else {
1057
- throw new Error("✗ Deployment failed");
1058
- }
1095
+ ### Example 9: Shell Scripting
1059
1096
 
1060
- ssh.stop();
1061
- }
1097
+ ```typescript
1098
+ import { SshClient, VirtualShell } from "typescript-virtual-container";
1099
+
1100
+ const shell = new VirtualShell("typescript-vm");
1101
+ await shell.ensureInitialized();
1102
+ const client = new SshClient(shell, "root");
1062
1103
 
1063
- testDeployment().catch(console.error);
1104
+ // Write a script to VFS
1105
+ shell.vfs.writeFile("/usr/local/bin/setup.sh", `
1106
+ #!/bin/sh
1107
+ for dir in config logs tmp; do
1108
+ mkdir /app/$dir
1109
+ echo "Created /app/$dir"
1110
+ done
1111
+ if [ -d /app/config ]; then
1112
+ echo "Setup complete"
1113
+ else
1114
+ echo "Setup failed"
1115
+ fi
1116
+ `);
1117
+
1118
+ // Execute it
1119
+ const r = await client.exec("sh /usr/local/bin/setup.sh");
1120
+ console.log(r.stdout);
1121
+ // Created /app/config
1122
+ // Created /app/logs
1123
+ // Created /app/tmp
1124
+ // Setup complete
1064
1125
  ```
1065
1126
 
1066
1127
  ---
1067
1128
 
1068
- ### Example 8: Snapshot-Based Test Fixtures
1129
+ ### Example 10: Snapshot-Based Test Fixtures
1069
1130
 
1070
1131
  ```typescript
1071
1132
  import { VirtualFileSystem } from "typescript-virtual-container";
@@ -1081,7 +1142,6 @@ function buildFixture(): VfsSnapshot {
1081
1142
 
1082
1143
  const FIXTURE = buildFixture();
1083
1144
 
1084
- // Each test gets a clean, isolated VFS from the same fixture
1085
1145
  test("reads config file", () => {
1086
1146
  const vfs = VirtualFileSystem.fromSnapshot(FIXTURE);
1087
1147
  const content = JSON.parse(vfs.readFile("/app/config/settings.json"));
@@ -1091,7 +1151,7 @@ test("reads config file", () => {
1091
1151
 
1092
1152
  ---
1093
1153
 
1094
- ### Example 9: Symlinks
1154
+ ### Example 11: Symlinks
1095
1155
 
1096
1156
  ```typescript
1097
1157
  const vfs = new VirtualFileSystem();
@@ -1105,55 +1165,30 @@ console.log(vfs.resolveSymlink("/usr/local/bin/app")); // /opt/myapp/bin/app
1105
1165
 
1106
1166
  ---
1107
1167
 
1108
- ### Example 10: Security Auditing with HoneyPot
1168
+ ### Example 12: Security Auditing with HoneyPot
1109
1169
 
1110
1170
  ```typescript
1111
- import {
1112
- HoneyPot,
1113
- SshClient,
1114
- VirtualShell,
1115
- VirtualSshServer,
1116
- } from "typescript-virtual-container";
1171
+ import { HoneyPot, SshClient, VirtualShell, VirtualSshServer } from "typescript-virtual-container";
1117
1172
 
1118
1173
  const shell = new VirtualShell("typescript-vm");
1119
1174
  const ssh = new VirtualSshServer({ port: 2222, shell });
1120
1175
  await ssh.start();
1121
1176
 
1122
- const users = ssh.getUsers()!;
1123
- const vfs = ssh.getVfs()!;
1124
-
1125
1177
  const hp = new HoneyPot(5000);
1126
- hp.attach(shell, vfs, users, ssh);
1127
-
1128
- await users.addUser("alice", "alice123");
1129
- await users.addUser("bob", "bob456");
1178
+ hp.attach(shell, shell.vfs, shell.users, ssh);
1130
1179
 
1131
1180
  const alice = new SshClient(shell, "alice");
1132
1181
  await alice.mkdir("/home/alice/projects", true);
1133
1182
  await alice.writeFile("/home/alice/projects/app.txt", "My application");
1134
- await alice.ls("/home/alice/projects");
1135
1183
 
1136
- const bob = new SshClient(shell, "bob");
1137
- await bob.readFile("/etc/shadow"); // will fail
1138
- await bob.writeFile("/etc/passwd", ""); // will fail
1139
-
1140
- // Stats
1141
1184
  const stats = hp.getStats();
1142
- console.log(`Auth attempts: ${stats.authAttempts}`);
1143
- console.log(`Commands run: ${stats.commands}`);
1144
- console.log(`File writes: ${stats.fileWrites}`);
1185
+ console.log(`Commands run: ${stats.commands}`);
1186
+ console.log(`File writes: ${stats.fileWrites}`);
1145
1187
 
1146
- // Recent events
1147
- hp.getRecent(5).forEach(e =>
1148
- console.log(`[${e.timestamp}] ${e.source} → ${e.type}`)
1149
- );
1150
-
1151
- // Anomaly detection
1152
1188
  hp.detectAnomalies().forEach(a =>
1153
1189
  console.log(`[${a.severity.toUpperCase()}] ${a.type}: ${a.message}`)
1154
1190
  );
1155
1191
 
1156
- // Filter by type and source
1157
1192
  const authFailures = hp.getAuditLog("auth:failure");
1158
1193
  const sshEvents = hp.getAuditLog(undefined, "SshMimic");
1159
1194
  console.log(`Auth failures: ${authFailures.length}`);
@@ -1164,15 +1199,9 @@ ssh.stop();
1164
1199
 
1165
1200
  ---
1166
1201
 
1167
- ### Example 11: Error Handling
1202
+ ### Example 13: Error Handling
1168
1203
 
1169
1204
  ```typescript
1170
- import { SshClient, VirtualShell, VirtualSshServer } from "typescript-virtual-container";
1171
-
1172
- const shell = new VirtualShell("typescript-vm");
1173
- const ssh = new VirtualSshServer({ port: 2222, shell });
1174
- await ssh.start();
1175
-
1176
1205
  const client = new SshClient(shell, "root");
1177
1206
 
1178
1207
  const r1 = await client.readFile("/etc/nonexistent.conf");
@@ -1183,13 +1212,11 @@ if (r2.exitCode !== 0) console.error("cd failed");
1183
1212
 
1184
1213
  const r3 = await client.rm("/", true);
1185
1214
  console.log("Remove root:", r3.stderr); // Cannot remove root directory.
1186
-
1187
- ssh.stop();
1188
1215
  ```
1189
1216
 
1190
1217
  ---
1191
1218
 
1192
- ### Example 12: Concurrent Clients
1219
+ ### Example 14: Concurrent Clients
1193
1220
 
1194
1221
  ```typescript
1195
1222
  const shell = new VirtualShell("typescript-vm");
@@ -1206,54 +1233,211 @@ const [r1, r2] = await Promise.all([
1206
1233
 
1207
1234
  ## Built-in Commands
1208
1235
 
1209
- All commands are available in SSH shell mode and via `SshClient.exec()`.
1236
+ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `help` in the shell for a grouped, colorized listing. Type `help <command>` for detailed usage.
1237
+
1238
+ ### Navigation
1210
1239
 
1211
1240
  | Command | Flags | Description |
1212
1241
  |---------|-------|-------------|
1213
- | `adduser <name> <pass>` | | Create user (root only) |
1214
- | `cat <path>` | | Print file contents |
1215
1242
  | `cd <path>` | | Change directory |
1243
+ | `ls [path]` | `-l` | List directory contents |
1244
+ | `pwd` | | Print working directory |
1245
+ | `tree [path]` | | ASCII directory tree |
1246
+
1247
+ ### Files & Filesystem
1248
+
1249
+ | Command | Flags | Description |
1250
+ |---------|-------|-------------|
1251
+ | `cat <path>` | | Print file contents |
1216
1252
  | `chmod <mode> <file>` | | Change file permissions (octal) |
1217
- | `clear` | | Clear terminal screen |
1218
1253
  | `cp <src> <dest>` | `-r` | Copy file or directory |
1219
- | `curl <url>` | | Fetch URL (delegates to host binary) |
1220
- | `deluser <name>` | | Delete user (root only, not root) |
1221
- | `echo <text...>` | | Print text |
1222
- | `env` | | List environment variables |
1223
- | `exit [code]` | | Close session |
1224
- | `export NAME=VALUE` | | Set/export shell variable |
1225
1254
  | `find [path]` | `-name <pat>` `-type f\|d` | Search for files |
1255
+ | `ln <target> <link>` | `-s` | Create hard or symbolic link |
1256
+ | `mkdir <path>` | `-p` | Create directory |
1257
+ | `mv <src> <dest>` | | Move or rename |
1258
+ | `rm <path>` | `-r` | Remove file or directory |
1259
+ | `touch <path>` | | Create or update file |
1260
+
1261
+ ### Text Processing
1262
+
1263
+ | Command | Flags | Description |
1264
+ |---------|-------|-------------|
1265
+ | `awk [-F <sep>] '<prog>'` | | Pattern scanning (print $N) |
1266
+ | `cut` | `-d <sep>` `-f <cols>` | Remove sections from lines |
1267
+ | `diff <f1> <f2>` | | Compare files line by line |
1226
1268
  | `grep <pattern> [files]` | `-i` `-v` `-n` `-r` | Search file content |
1227
1269
  | `head [files]` | `-n <N>` | First N lines (default 10) |
1228
- | `help` | | List all commands |
1270
+ | `sed -e 's/pat/rep/[g]'` | `-i` | Stream editor |
1271
+ | `sort [files]` | `-r` `-n` `-u` | Sort lines |
1272
+ | `tail [files]` | `-n <N>` | Last N lines (default 10) |
1273
+ | `tee [files]` | `-a` | Read stdin, write to stdout and files |
1274
+ | `tr <set1> [set2]` | `-d` | Translate or delete characters |
1275
+ | `uniq` | `-c` `-d` `-u` | Filter repeated lines |
1276
+ | `wc [files]` | `-l` `-w` `-c` | Word/line/byte count |
1277
+ | `xargs [cmd]` | | Build and execute commands from stdin |
1278
+
1279
+ ### Archive & Compression
1280
+
1281
+ | Command | Flags | Description |
1282
+ |---------|-------|-------------|
1283
+ | `base64` | `-d` | Encode/decode base64 |
1284
+ | `gzip <file>` | | Compress file |
1285
+ | `gunzip <file>` | | Decompress file |
1286
+ | `tar <archive> [files]` | `-czf` `-xzf` `-tf` | Archive utility |
1287
+
1288
+ ### System
1289
+
1290
+ | Command | Flags | Description |
1291
+ |---------|-------|-------------|
1292
+ | `date` | `+format` | Print current date and time |
1293
+ | `df` | `-h` | Filesystem disk space usage |
1294
+ | `du [path]` | `-h` `-s` | Estimate file space usage |
1295
+ | `groups [user]` | | Print group memberships |
1229
1296
  | `hostname` | | Print hostname |
1230
1297
  | `htop` | | System monitor (mock) |
1231
- | `ln <target> <link>` | `-s` | Create hard or symbolic link |
1232
- | `ls [path]` | `-l` | List directory |
1233
- | `mkdir <path>` | `-p` | Create directory |
1234
- | `mv <src> <dest>` | | Move or rename |
1298
+ | `id [user]` | | Print user identity (uid/gid/groups) |
1299
+ | `kill [-9] <pid>` | | Send signal to process (mock) |
1300
+ | `neofetch` | | System info display (mock) |
1301
+ | `ping [-c <n>] <host>` | | Send ICMP ECHO_REQUEST (mock) |
1302
+ | `ps` | `-a` `-u` `-x` | Report process status |
1303
+ | `sleep <seconds>` | | Delay execution |
1304
+ | `uname` | `-a` `-r` `-m` | Print system information |
1305
+ | `who` | | List active sessions |
1306
+ | `whoami` | | Print current user |
1307
+
1308
+ ### Network
1309
+
1310
+ | Command | Flags | Description |
1311
+ |---------|-------|-------------|
1312
+ | `curl <url>` | | HTTP client (delegates to host binary) |
1313
+ | `wget <url>` | | File downloader (delegates to host binary) |
1314
+
1315
+ ### Shell
1316
+
1317
+ | Command | Flags | Description |
1318
+ |---------|-------|-------------|
1319
+ | `clear` | | Clear terminal screen (full ANSI reset) |
1320
+ | `echo <text>` | | Display text |
1321
+ | `env` | | Print session environment variables |
1322
+ | `exit [code]` | | Exit session |
1323
+ | `export NAME=VALUE` | | Set shell variable in current session |
1324
+ | `help [command]` | | List commands (grouped) or show command details |
1325
+ | `set [VAR=val]` | | Display or set shell variables |
1326
+ | `sh` | `-c <script>` `[file]` | Execute shell script (supports if/for/while) |
1327
+ | `unset <VAR>` | | Remove shell variable |
1328
+
1329
+ ### Users & Permissions
1330
+
1331
+ | Command | Flags | Description |
1332
+ |---------|-------|-------------|
1333
+ | `adduser <name> <pass>` | | Create user (root only) |
1334
+ | `deluser <name>` | | Delete user (root only) |
1235
1335
  | `nano <path>` | | Interactive text editor |
1236
- | `neofetch` | | System summary (mock) |
1237
1336
  | `passwd [user]` | | Change password |
1238
- | `pwd` | | Print working directory |
1239
- | `rm <path>` | `-r` | Remove file or directory |
1240
- | `set` | | Show shell variables |
1241
- | `sh <script>` | | Run shell script |
1242
1337
  | `su [user]` | | Switch user |
1243
1338
  | `sudo <cmd>` | `-i` | Run as root |
1244
- | `tail [files]` | `-n <N>` | Last N lines (default 10) |
1245
- | `touch <path>` | | Create/update file |
1246
- | `tree [path]` | | ASCII directory tree |
1247
- | `unset <name>` | | Remove shell variable |
1248
- | `wc [files]` | `-l` `-w` `-c` | Line/word/byte count |
1249
- | `wget <url>` | | Download file (delegates to host binary) |
1250
- | `who` | | List active sessions |
1251
- | `whoami` | | Print current user |
1252
1339
 
1253
1340
  Custom commands can be added via `shell.addCommand()`.
1254
1341
 
1255
1342
  ---
1256
1343
 
1344
+ ## Shell Scripting
1345
+
1346
+ The shell interpreter supports a subset of POSIX sh syntax, usable both interactively and via `sh -c '...'` or `sh <file>`.
1347
+
1348
+ ### Logical Operators
1349
+
1350
+ ```bash
1351
+ mkdir /app && echo "created" # run second only if first succeeds
1352
+ rm /missing || echo "not found" # run second only if first fails
1353
+ echo a; echo b; echo c # always run all three
1354
+ ```
1355
+
1356
+ ### Pipes and Redirections
1357
+
1358
+ ```bash
1359
+ cat /etc/hosts | grep local
1360
+ ls /home | sort | head -5
1361
+ echo "hello world" > /tmp/out.txt
1362
+ cat /tmp/out.txt >> /tmp/log.txt
1363
+ ```
1364
+
1365
+ ### Variable Expansion
1366
+
1367
+ ```bash
1368
+ export NAME=world
1369
+ echo "Hello $NAME" # Hello world
1370
+ echo "${NAME:-fallback}" # world (or fallback if unset)
1371
+ echo "${UNSET:-default}" # default
1372
+ echo "Exit: $?" # last exit code
1373
+ ```
1374
+
1375
+ ### Conditionals
1376
+
1377
+ ```bash
1378
+ if [ -f /etc/config ]; then
1379
+ echo "config exists"
1380
+ elif [ -d /etc ]; then
1381
+ echo "etc is a directory"
1382
+ else
1383
+ echo "nothing found"
1384
+ fi
1385
+
1386
+ # String comparison
1387
+ if [ "$USER" = "root" ]; then echo "root"; fi
1388
+
1389
+ # Numeric comparison
1390
+ if [ $COUNT -gt 10 ]; then echo "large"; fi
1391
+ ```
1392
+
1393
+ ### Loops
1394
+
1395
+ ```bash
1396
+ # for loop
1397
+ for name in alice bob charlie; do
1398
+ echo "Hello $name"
1399
+ done
1400
+
1401
+ # while loop
1402
+ COUNT=0
1403
+ while [ $COUNT -lt 3 ]; do
1404
+ echo "Count: $COUNT"
1405
+ export COUNT=$((COUNT + 1))
1406
+ done
1407
+ ```
1408
+
1409
+ ### Script Files
1410
+
1411
+ Write a script to the VFS and execute it:
1412
+
1413
+ ```bash
1414
+ # Via SSH shell:
1415
+ nano /usr/local/bin/deploy.sh
1416
+ # ... write the script ...
1417
+ sh /usr/local/bin/deploy.sh
1418
+
1419
+ # Via programmatic client:
1420
+ await client.writeFile("/usr/local/bin/setup.sh", `
1421
+ for dir in config logs tmp; do
1422
+ mkdir /app/$dir
1423
+ done
1424
+ `);
1425
+ await client.exec("sh /usr/local/bin/setup.sh");
1426
+ ```
1427
+
1428
+ ### .bashrc
1429
+
1430
+ On every interactive SSH login, `/home/<user>/.bashrc` is sourced automatically. Use it to set environment variables, aliases (via `sh -c`), or print a welcome message.
1431
+
1432
+ ```bash
1433
+ # /home/alice/.bashrc
1434
+ export EDITOR=nano
1435
+ export PATH="/usr/local/bin:/usr/bin:/bin"
1436
+ echo "Welcome, Alice!"
1437
+ ```
1438
+
1439
+ ---
1440
+
1257
1441
  ## Configuration
1258
1442
 
1259
1443
  ### Environment Variables
@@ -1323,7 +1507,7 @@ Recent baselines show strong startup behavior up to 100 concurrent shells. The r
1323
1507
 
1324
1508
  - SSH server is event-driven and handles multiple concurrent connections.
1325
1509
  - `SshClient` is sequential per instance — create multiple instances for parallel operations.
1326
- - Each `VirtualShell` instance is fully independent (separate VFS, users, state).
1510
+ - Each `VirtualShell` instance is fully independent (separate VFS, users, env state).
1327
1511
 
1328
1512
  ### Performance Tips
1329
1513
 
@@ -1368,6 +1552,7 @@ import type {
1368
1552
  CommandMode,
1369
1553
  CommandOutcome,
1370
1554
  ShellModule,
1555
+ ShellEnv,
1371
1556
  SudoChallenge,
1372
1557
  NanoEditorSession,
1373
1558
  // Audit
@@ -1387,7 +1572,7 @@ import type {
1387
1572
  No. It emulates SSH sessions, users, and filesystem behavior in a virtual runtime. Ideal for testing, simulations, and automation where full OS isolation is not required.
1388
1573
 
1389
1574
  **Can I use this in production?**
1390
- You can use it in production-like automation contexts (sandboxed command runners, test harnesses, training environments, honeypots). It is not a security boundary like a real container/VM. Shell command fidelity is still being expanded.
1575
+ You can use it in production-like automation contexts (sandboxed command runners, test harnesses, training environments, honeypots). It is not a security boundary like a real container/VM.
1391
1576
 
1392
1577
  **Does the VFS touch the host filesystem?**
1393
1578
  In the default `"memory"` mode: no, all data lives in memory. In `"fs"` mode, it reads/writes a single JSON file (`vfs-snapshot.json`) inside the configured `snapshotPath` directory. No other host paths are accessed.
@@ -1396,16 +1581,22 @@ In the default `"memory"` mode: no, all data lives in memory. In `"fs"` mode, it
1396
1581
  Only if you explicitly use `"fs"` mode or call `toSnapshot()` / `fromSnapshot()` manually. Memory mode is ephemeral.
1397
1582
 
1398
1583
  **Can I run multiple isolated shells?**
1399
- Yes. Each `new VirtualShell(...)` creates a completely independent VFS and user manager.
1584
+ Yes. Each `new VirtualShell(...)` creates a completely independent VFS, user manager, and shell environment.
1400
1585
 
1401
1586
  **Are custom commands shared between shell instances?**
1402
1587
  No. Custom commands registered with `shell.addCommand()` are instance-local.
1403
1588
 
1589
+ **Does the shell support `&&`, `||`, and `;`?**
1590
+ Yes. The shell parser handles logical operators, pipes, and redirections. `if`/`elif`/`else`/`fi`, `for`/`do`/`done`, and `while`/`do`/`done` are supported in scripts.
1591
+
1592
+ **Does `.bashrc` work?**
1593
+ Yes. When an interactive SSH session starts, `/home/<user>/.bashrc` is loaded automatically and each non-comment line is executed in the session environment.
1594
+
1404
1595
  **Is networking fully implemented for curl/wget?**
1405
1596
  `curl` and `wget` delegate to the host binaries. They are intended for realistic workflows, not full GNU tooling parity.
1406
1597
 
1407
1598
  **Can I create custom commands?**
1408
- Yes — use `shell.addCommand()` or implement the `ShellModule` interface directly.
1599
+ Yes — use `shell.addCommand()` or implement the `ShellModule` interface directly. Set `description` and `category` to appear in the grouped `help` output.
1409
1600
 
1410
1601
  **Is SFTP fully supported?**
1411
1602
  Core SFTP operations (open, read, write, stat, mkdir, remove, rename) are implemented. Some optional operations (extended attributes, symlinks) return `OP_UNSUPPORTED`.
@@ -1419,14 +1610,9 @@ Yes — that is one of its primary use-cases. Use `HoneyPot.attach()` to capture
1419
1610
 
1420
1611
  **`Error: listen EADDRINUSE :::2222`**
1421
1612
  The port is already in use. Use a different port or stop the existing process.
1422
- ```typescript
1423
- const ssh = new VirtualSshServer({ port: 3333 });
1424
- ```
1425
1613
 
1426
1614
  **SSH authentication always fails**
1427
- - Check the password (root has no password by default any login is accepted).
1428
- - If you set a password, verify it with `users.verifyPassword(username, password)`.
1429
- - Check if the IP is rate-limited: call `ssh.clearLockout(ip)`.
1615
+ Check the password (root has no password by default). If you set a password, verify it with `users.verifyPassword(username, password)`. Check if the IP is rate-limited: call `ssh.clearLockout(ip)`.
1430
1616
 
1431
1617
  **Auth always fails with "lockout"**
1432
1618
  Call `ssh.clearLockout(ip)` or increase `maxAuthAttempts`. In tests, use `maxAuthAttempts: Infinity`.
@@ -1435,20 +1621,19 @@ Call `ssh.clearLockout(ip)` or increase `maxAuthAttempts`. In tests, use `maxAut
1435
1621
  A symlink chain exceeds 8 hops. Check for circular links or pass a larger `maxDepth` to `resolveSymlink()`.
1436
1622
 
1437
1623
  **`Command 'xyz' not found` (exit code 127)**
1438
- The command is not registered. Register it with `shell.addCommand()` or use `SshClient.exec()` with a handler.
1624
+ The command is not registered. Register it with `shell.addCommand()`.
1625
+
1626
+ **Shell scripting — `if` block not working**
1627
+ Ensure each keyword is on its own line or separated by `;`. The interpreter does not support complex one-liners like `if condition; then cmd; fi` as a single string passed to `sh -c` — split by line or use semicolons.
1439
1628
 
1440
1629
  **File not found errors**
1441
- Create the parent directory first:
1442
- ```typescript
1443
- vfs.mkdir("/home/alice", 0o755);
1444
- vfs.writeFile("/home/alice/file.txt", "content");
1445
- ```
1630
+ Create the parent directory first with `vfs.mkdir(path, 0o755)`.
1446
1631
 
1447
1632
  **`snapshotPath` is required error**
1448
- You set `mode: "fs"` without providing `snapshotPath`:
1449
- ```typescript
1450
- new VirtualFileSystem({ mode: "fs", snapshotPath: "./data" });
1451
- ```
1633
+ You set `mode: "fs"` without providing `snapshotPath`: `new VirtualFileSystem({ mode: "fs", snapshotPath: "./data" })`.
1634
+
1635
+ **Variables not persisting between `exec()` calls**
1636
+ Each `SshClient.exec()` call shares the same `ShellEnv` object per shell instance. Variables set via `export` in one exec call are visible in the next. If you need full isolation, create a new `SshClient` instance.
1452
1637
 
1453
1638
  ---
1454
1639
 
@@ -1466,6 +1651,7 @@ new VirtualFileSystem({ mode: "fs", snapshotPath: "./data" });
1466
1651
  - JSDoc comments on all public API surface
1467
1652
  - Async/await throughout — no callbacks
1468
1653
  - Tests for new commands and VFS behavior
1654
+ - New commands must include `description` and `category` fields for `help`
1469
1655
 
1470
1656
  ---
1471
1657
 
@@ -1476,7 +1662,7 @@ new VirtualFileSystem({ mode: "fs", snapshotPath: "./data" });
1476
1662
  - Sudo privileges are explicit and stored in the VFS under `/virtual-env-js/.auth/sudoers`.
1477
1663
  - Per-IP rate limiting prevents automated brute-force attacks on the SSH server.
1478
1664
  - This project does **not** provide kernel-level or process-level isolation.
1479
- - Do **not** expose a running instance to the public internet without understanding the risks — the virtual shell allows arbitrary command execution within the virtual environment.
1665
+ - Do **not** expose a running instance to the public internet without understanding the risks.
1480
1666
 
1481
1667
  If you discover a vulnerability, avoid public disclosure in GitHub Issues. Contact maintainers privately first — see `SECURITY.md`.
1482
1668
 
@@ -1505,11 +1691,19 @@ MIT — see [LICENSE](./LICENSE).
1505
1691
  - [x] Symlinks (`ln -s`, `isSymlink`, `resolveSymlink`)
1506
1692
  - [x] SSH public-key authentication
1507
1693
  - [x] Per-IP rate limiting and lockout
1508
- - [x] New commands: `cp`, `mv`, `ln`, `find`, `wc`, `head`, `tail`, `chmod`
1694
+ - [x] Shell operators: `&&` / `||` / `;`
1695
+ - [x] Shell scripting: `if`/`elif`/`else`/`fi`, `for`/`do`/`done`, `while`/`do`/`done`
1696
+ - [x] Variable expansion: `$VAR`, `${VAR:-default}`, `$?`
1697
+ - [x] Per-session `ShellEnv` (no more global variable store)
1698
+ - [x] `.bashrc` auto-sourced on interactive login
1699
+ - [x] `clear` with full ANSI screen reset (`\x1b[2J\x1b[H\x1b[3J`)
1700
+ - [x] `Ctrl+W` delete word in interactive shell
1701
+ - [x] Grouped, colorized `help` with per-command detail
1702
+ - [x] New commands: `sort`, `uniq`, `tee`, `cut`, `tr`, `xargs`, `diff`, `sed`, `awk`, `tar`, `gzip`, `gunzip`, `base64`, `date`, `sleep`, `id`, `groups`, `uname`, `ps`, `kill`, `df`, `du`, `ping`
1509
1703
  - [x] Structured event hooks (session open/close, file write, sudo challenge)
1510
1704
  - [ ] Snapshot diff tooling for test assertions
1511
1705
  - [ ] WebSocket-based remote shell client (experimental)
1512
- - [ ] Shell scripting: `if`/`for`/`while` constructs
1706
+ - [ ] `$(cmd)` command substitution in variable expansion
1513
1707
 
1514
1708
  ---
1515
1709