typescript-virtual-container 1.2.5 → 1.2.7
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.
- package/README.md +387 -193
- package/benchmark-results.txt +21 -21
- package/biome.json +1 -1
- package/bun.lock +15 -41
- package/dist/SSHMimic/exec.js +2 -2
- package/dist/SSHMimic/executor.d.ts +6 -7
- package/dist/SSHMimic/executor.d.ts.map +1 -1
- package/dist/SSHMimic/executor.js +77 -60
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +6 -20
- package/dist/SSHMimic/sftp.d.ts.map +1 -1
- package/dist/SSHMimic/sftp.js +14 -0
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +13 -36
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +19 -2
- package/dist/VirtualShell/shellParser.d.ts +20 -2
- package/dist/VirtualShell/shellParser.d.ts.map +1 -1
- package/dist/VirtualShell/shellParser.js +229 -120
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/commands/adduser.d.ts.map +1 -1
- package/dist/commands/adduser.js +2 -0
- package/dist/commands/awk.d.ts +3 -0
- package/dist/commands/awk.d.ts.map +1 -0
- package/dist/commands/awk.js +29 -0
- package/dist/commands/base64.d.ts +3 -0
- package/dist/commands/base64.d.ts.map +1 -0
- package/dist/commands/base64.js +20 -0
- package/dist/commands/cat.d.ts.map +1 -1
- package/dist/commands/cat.js +2 -0
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +2 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +2 -0
- package/dist/commands/clear.d.ts.map +1 -1
- package/dist/commands/clear.js +4 -1
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +2 -0
- package/dist/commands/curl.d.ts.map +1 -1
- package/dist/commands/curl.js +2 -0
- package/dist/commands/cut.d.ts +3 -0
- package/dist/commands/cut.d.ts.map +1 -0
- package/dist/commands/cut.js +27 -0
- package/dist/commands/date.d.ts +3 -0
- package/dist/commands/date.d.ts.map +1 -0
- package/dist/commands/date.js +22 -0
- package/dist/commands/deluser.d.ts.map +1 -1
- package/dist/commands/deluser.js +2 -0
- package/dist/commands/df.d.ts +3 -0
- package/dist/commands/df.d.ts.map +1 -0
- package/dist/commands/df.js +16 -0
- package/dist/commands/diff.d.ts +3 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +40 -0
- package/dist/commands/du.d.ts +3 -0
- package/dist/commands/du.d.ts.map +1 -0
- package/dist/commands/du.js +39 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +2 -0
- package/dist/commands/env.d.ts +1 -0
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +6 -14
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +11 -21
- package/dist/commands/find.d.ts.map +1 -1
- package/dist/commands/find.js +2 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +4 -7
- package/dist/commands/groups.d.ts +3 -0
- package/dist/commands/groups.d.ts.map +1 -0
- package/dist/commands/groups.js +12 -0
- package/dist/commands/gzip.d.ts +4 -0
- package/dist/commands/gzip.d.ts.map +1 -0
- package/dist/commands/gzip.js +40 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +2 -0
- package/dist/commands/help.d.ts +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +66 -3
- package/dist/commands/hostname.d.ts.map +1 -1
- package/dist/commands/hostname.js +2 -0
- package/dist/commands/htop.d.ts.map +1 -1
- package/dist/commands/htop.js +2 -0
- package/dist/commands/id.d.ts +3 -0
- package/dist/commands/id.d.ts.map +1 -0
- package/dist/commands/id.js +14 -0
- package/dist/commands/index.d.ts +5 -2
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +89 -62
- package/dist/commands/kill.d.ts +3 -0
- package/dist/commands/kill.d.ts.map +1 -0
- package/dist/commands/kill.js +13 -0
- package/dist/commands/ln.d.ts.map +1 -1
- package/dist/commands/ln.js +2 -0
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +2 -0
- package/dist/commands/mkdir.d.ts.map +1 -1
- package/dist/commands/mkdir.js +2 -0
- package/dist/commands/mv.d.ts.map +1 -1
- package/dist/commands/mv.js +2 -0
- package/dist/commands/nano.d.ts.map +1 -1
- package/dist/commands/nano.js +2 -0
- package/dist/commands/neofetch.d.ts.map +1 -1
- package/dist/commands/neofetch.js +2 -0
- package/dist/commands/passwd.d.ts.map +1 -1
- package/dist/commands/passwd.js +2 -0
- package/dist/commands/ping.d.ts +3 -0
- package/dist/commands/ping.d.ts.map +1 -0
- package/dist/commands/ping.js +18 -0
- package/dist/commands/ps.d.ts +3 -0
- package/dist/commands/ps.d.ts.map +1 -0
- package/dist/commands/ps.js +17 -0
- package/dist/commands/pwd.d.ts.map +1 -1
- package/dist/commands/pwd.js +2 -0
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +2 -0
- package/dist/commands/sed.d.ts +3 -0
- package/dist/commands/sed.d.ts.map +1 -0
- package/dist/commands/sed.js +47 -0
- package/dist/commands/set.d.ts +3 -0
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +19 -46
- package/dist/commands/sh.d.ts +0 -1
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +229 -35
- package/dist/commands/sleep.d.ts +3 -0
- package/dist/commands/sleep.d.ts.map +1 -0
- package/dist/commands/sleep.js +13 -0
- package/dist/commands/sort.d.ts +3 -0
- package/dist/commands/sort.d.ts.map +1 -0
- package/dist/commands/sort.js +37 -0
- package/dist/commands/su.d.ts.map +1 -1
- package/dist/commands/su.js +2 -0
- package/dist/commands/sudo.d.ts.map +1 -1
- package/dist/commands/sudo.js +2 -0
- package/dist/commands/tail.d.ts.map +1 -1
- package/dist/commands/tail.js +2 -0
- package/dist/commands/tar.d.ts +3 -0
- package/dist/commands/tar.d.ts.map +1 -0
- package/dist/commands/tar.js +64 -0
- package/dist/commands/tee.d.ts +3 -0
- package/dist/commands/tee.d.ts.map +1 -0
- package/dist/commands/tee.js +29 -0
- package/dist/commands/touch.d.ts.map +1 -1
- package/dist/commands/touch.js +2 -0
- package/dist/commands/tr.d.ts +3 -0
- package/dist/commands/tr.d.ts.map +1 -0
- package/dist/commands/tr.js +24 -0
- package/dist/commands/tree.d.ts.map +1 -1
- package/dist/commands/tree.js +2 -0
- package/dist/commands/uname.d.ts +3 -0
- package/dist/commands/uname.d.ts.map +1 -0
- package/dist/commands/uname.js +21 -0
- package/dist/commands/uniq.d.ts +3 -0
- package/dist/commands/uniq.d.ts.map +1 -0
- package/dist/commands/uniq.js +33 -0
- package/dist/commands/unset.d.ts.map +1 -1
- package/dist/commands/unset.js +6 -10
- package/dist/commands/wc.d.ts.map +1 -1
- package/dist/commands/wc.js +2 -0
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +2 -0
- package/dist/commands/who.d.ts.map +1 -1
- package/dist/commands/who.js +2 -0
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +2 -0
- package/dist/commands/xargs.d.ts +3 -0
- package/dist/commands/xargs.d.ts.map +1 -0
- package/dist/commands/xargs.js +16 -0
- package/dist/types/commands.d.ts +13 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/pipeline.d.ts +20 -0
- package/dist/types/pipeline.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/SSHMimic/exec.ts +2 -2
- package/src/SSHMimic/executor.ts +95 -98
- package/src/SSHMimic/index.ts +15 -49
- package/src/SSHMimic/sftp.ts +15 -0
- package/src/VirtualFileSystem/index.ts +27 -75
- package/src/VirtualShell/shell.ts +19 -2
- package/src/VirtualShell/shellParser.ts +202 -168
- package/src/VirtualUserManager/index.ts +2 -7
- package/src/commands/adduser.ts +2 -0
- package/src/commands/awk.ts +30 -0
- package/src/commands/base64.ts +18 -0
- package/src/commands/cat.ts +2 -0
- package/src/commands/cd.ts +2 -0
- package/src/commands/chmod.ts +2 -0
- package/src/commands/clear.ts +4 -1
- package/src/commands/cp.ts +2 -0
- package/src/commands/curl.ts +2 -0
- package/src/commands/cut.ts +29 -0
- package/src/commands/date.ts +24 -0
- package/src/commands/deluser.ts +2 -0
- package/src/commands/df.ts +18 -0
- package/src/commands/diff.ts +29 -0
- package/src/commands/du.ts +39 -0
- package/src/commands/echo.ts +2 -0
- package/src/commands/env.ts +7 -16
- package/src/commands/export.ts +11 -24
- package/src/commands/find.ts +2 -0
- package/src/commands/grep.ts +4 -7
- package/src/commands/groups.ts +14 -0
- package/src/commands/gzip.ts +31 -0
- package/src/commands/head.ts +2 -0
- package/src/commands/help.ts +72 -3
- package/src/commands/hostname.ts +2 -0
- package/src/commands/htop.ts +2 -0
- package/src/commands/id.ts +16 -0
- package/src/commands/index.ts +98 -99
- package/src/commands/kill.ts +14 -0
- package/src/commands/ln.ts +2 -0
- package/src/commands/ls.ts +2 -0
- package/src/commands/mkdir.ts +2 -0
- package/src/commands/mv.ts +2 -0
- package/src/commands/nano.ts +2 -0
- package/src/commands/neofetch.ts +2 -0
- package/src/commands/passwd.ts +2 -0
- package/src/commands/ping.ts +20 -0
- package/src/commands/ps.ts +19 -0
- package/src/commands/pwd.ts +2 -0
- package/src/commands/rm.ts +2 -0
- package/src/commands/sed.ts +45 -0
- package/src/commands/set.ts +19 -50
- package/src/commands/sh.ts +193 -43
- package/src/commands/sleep.ts +14 -0
- package/src/commands/sort.ts +37 -0
- package/src/commands/su.ts +2 -0
- package/src/commands/sudo.ts +2 -0
- package/src/commands/tail.ts +2 -0
- package/src/commands/tar.ts +58 -0
- package/src/commands/tee.ts +25 -0
- package/src/commands/touch.ts +2 -0
- package/src/commands/tr.ts +24 -0
- package/src/commands/tree.ts +2 -0
- package/src/commands/uname.ts +20 -0
- package/src/commands/uniq.ts +28 -0
- package/src/commands/unset.ts +5 -12
- package/src/commands/wc.ts +2 -0
- package/src/commands/wget.ts +2 -0
- package/src/commands/who.ts +2 -0
- package/src/commands/whoami.ts +2 -0
- package/src/commands/xargs.ts +17 -0
- package/src/types/commands.ts +14 -0
- package/src/types/pipeline.ts +23 -0
- package/standalone.js +92 -64
- package/standalone.js.map +4 -4
- 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
|
[](https://www.npmjs.com/package/typescript-virtual-container)
|
|
6
6
|
[](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
|
-
- **
|
|
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
|
-
- **
|
|
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
|
|
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,
|
|
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
|
-
│
|
|
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;
|
|
774
|
-
stderr?: string;
|
|
775
|
-
exitCode?: number;
|
|
776
|
-
nextCwd?: string;
|
|
777
|
-
clearScreen?: boolean;
|
|
778
|
-
closeSession?: boolean;
|
|
779
|
-
switchUser?: string;
|
|
780
|
-
openEditor?: NanoEditorSession;
|
|
781
|
-
openHtop?: boolean;
|
|
782
|
-
sudoChallenge?: SudoChallenge;
|
|
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;
|
|
796
|
-
size: number;
|
|
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;
|
|
864
|
+
id: string;
|
|
818
865
|
username: string;
|
|
819
|
-
tty: string;
|
|
820
|
-
remoteAddress: string;
|
|
821
|
-
startedAt: string;
|
|
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-
|
|
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,
|
|
1018
|
-
lockoutDurationMs: 300_000,
|
|
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.
|
|
1026
|
-
|
|
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
|
-
//
|
|
1030
|
-
|
|
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
|
|
1069
|
+
### Example 8: .bashrc
|
|
1036
1070
|
|
|
1037
1071
|
```typescript
|
|
1038
|
-
import {
|
|
1072
|
+
import { VirtualShell, VirtualSshServer } from "typescript-virtual-container";
|
|
1039
1073
|
|
|
1040
|
-
|
|
1041
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1048
|
-
|
|
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
|
-
|
|
1051
|
-
await client.writeFile("/srv/app/app.js", 'console.log("v2.0");');
|
|
1093
|
+
---
|
|
1052
1094
|
|
|
1053
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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(`
|
|
1143
|
-
console.log(`
|
|
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
|
|
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
|
|
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
|
-
| `
|
|
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
|
-
| `
|
|
1232
|
-
| `
|
|
1233
|
-
| `
|
|
1234
|
-
| `
|
|
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.
|
|
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
|
|
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
|
-
|
|
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()
|
|
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
|
-
|
|
1450
|
-
|
|
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
|
|
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]
|
|
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
|
-
- [ ]
|
|
1706
|
+
- [ ] `$(cmd)` command substitution in variable expansion
|
|
1513
1707
|
|
|
1514
1708
|
---
|
|
1515
1709
|
|