typescript-virtual-container 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/HONEYPOT.md +358 -0
  3. package/README.md +471 -16
  4. package/dist/SSHMimic/exec.d.ts.map +1 -1
  5. package/dist/SSHMimic/exec.js +8 -2
  6. package/dist/SSHMimic/index.d.ts +3 -1
  7. package/dist/SSHMimic/index.d.ts.map +1 -1
  8. package/dist/SSHMimic/index.js +21 -4
  9. package/dist/SSHMimic/sftp.d.ts +48 -0
  10. package/dist/SSHMimic/sftp.d.ts.map +1 -0
  11. package/dist/SSHMimic/sftp.js +595 -0
  12. package/dist/VirtualFileSystem/index.d.ts +8 -5
  13. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  14. package/dist/VirtualFileSystem/index.js +152 -154
  15. package/dist/VirtualShell/index.d.ts +8 -1
  16. package/dist/VirtualShell/index.d.ts.map +1 -1
  17. package/dist/VirtualShell/index.js +22 -5
  18. package/dist/VirtualShell/shell.d.ts.map +1 -1
  19. package/dist/VirtualShell/shell.js +7 -0
  20. package/dist/VirtualUserManager/index.d.ts +3 -1
  21. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  22. package/dist/VirtualUserManager/index.js +34 -1
  23. package/dist/commands/exit.d.ts.map +1 -1
  24. package/dist/commands/exit.js +1 -0
  25. package/dist/honeypot.d.ts +132 -0
  26. package/dist/honeypot.d.ts.map +1 -0
  27. package/dist/honeypot.js +289 -0
  28. package/dist/index.d.ts +4 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +3 -2
  31. package/dist/standalone.js +10 -1
  32. package/examples/README.md +210 -0
  33. package/examples/honeypot-audit.ts +180 -0
  34. package/examples/honeypot-export.ts +253 -0
  35. package/examples/honeypot-quickstart.ts +110 -0
  36. package/package.json +1 -1
  37. package/src/Honeypot/index.ts +422 -0
  38. package/src/SSHMimic/exec.ts +18 -12
  39. package/src/SSHMimic/index.ts +29 -8
  40. package/src/SSHMimic/sftp.ts +853 -0
  41. package/src/VirtualFileSystem/index.ts +167 -190
  42. package/src/VirtualShell/index.ts +25 -9
  43. package/src/VirtualShell/shell.ts +7 -0
  44. package/src/VirtualUserManager/index.ts +41 -3
  45. package/src/commands/exit.ts +1 -0
  46. package/src/index.ts +8 -1
  47. package/src/standalone.ts +11 -1
  48. package/tests/sftp.test.ts +319 -0
  49. package/tests/ssh-exec.test.ts +45 -0
@@ -1,5 +1,7 @@
1
+ import { EventEmitter } from "node:events";
1
2
  import { Server as SshServer } from "ssh2";
2
3
  import { VirtualShell } from "../VirtualShell";
4
+ import { runExec } from "./exec";
3
5
  import { loadOrCreateHostKey } from "./hostKey";
4
6
  /**
5
7
  * SSH server facade that wires the virtual shell runtime into ssh2 sessions.
@@ -8,7 +10,7 @@ import { loadOrCreateHostKey } from "./hostKey";
8
10
  * Create an instance, call {@link SshMimic.start}, and stop it with
9
11
  * {@link SshMimic.stop} when your process exits.
10
12
  */
11
- class SshMimic {
13
+ class SshMimic extends EventEmitter {
12
14
  port;
13
15
  server;
14
16
  shell;
@@ -21,6 +23,7 @@ class SshMimic {
21
23
  * @param shell Optional preconfigured virtual shell instance to reuse.
22
24
  */
23
25
  constructor({ port, hostname = "typescript-vm", shell = new VirtualShell(hostname), }) {
26
+ super();
24
27
  this.port = port;
25
28
  this.shellHostname = hostname;
26
29
  this.server = null;
@@ -34,6 +37,8 @@ class SshMimic {
34
37
  async start() {
35
38
  const shell = this.shell;
36
39
  const privateKey = loadOrCreateHostKey();
40
+ // Ensure VirtualShell is fully initialized before accepting connections
41
+ await shell.ensureInitialized();
37
42
  this.server = new SshServer({
38
43
  hostKeys: [privateKey],
39
44
  ident: `SSH-2.0-${shell.hostname}`,
@@ -41,17 +46,23 @@ class SshMimic {
41
46
  let authUser = "root";
42
47
  let remoteAddress = "unknown";
43
48
  let sessionId = null;
49
+ this.emit("client:connect");
44
50
  client.on("authentication", (ctx) => {
45
51
  shell;
46
52
  if (ctx.method === "password") {
47
53
  const candidateUser = ctx.username || "root";
48
54
  remoteAddress = ctx.ip ?? remoteAddress;
49
55
  if (!shell.users.verifyPassword(candidateUser, ctx.password ?? "")) {
56
+ this.emit("auth:failure", {
57
+ username: candidateUser,
58
+ remoteAddress,
59
+ });
50
60
  ctx.reject();
51
61
  return;
52
62
  }
53
63
  authUser = candidateUser;
54
64
  sessionId = shell.users.registerSession(authUser, remoteAddress).id;
65
+ this.emit("auth:success", { username: authUser, remoteAddress });
55
66
  const homePath = `/home/${authUser}`;
56
67
  if (!shell.vfs.exists(homePath)) {
57
68
  shell.vfs.mkdir(homePath, 0o755);
@@ -65,6 +76,7 @@ class SshMimic {
65
76
  });
66
77
  client.on("close", () => {
67
78
  shell.users.unregisterSession(sessionId);
79
+ this.emit("client:disconnect", { user: authUser });
68
80
  sessionId = null;
69
81
  });
70
82
  client.on("ready", () => {
@@ -85,16 +97,19 @@ class SshMimic {
85
97
  shell?.startInteractiveSession(stream, authUser, sessionId, remoteAddress, terminalSize);
86
98
  });
87
99
  session.on("exec", (acceptExec, _rejectExec, info) => {
88
- const _stream = acceptExec();
89
- shell?.executeCommand(info.command.trim(), authUser, `/home/${authUser}`);
100
+ const stream = acceptExec();
101
+ if (stream) {
102
+ runExec(stream, info.command.trim(), authUser, shell.hostname, shell);
103
+ }
90
104
  });
91
105
  });
92
106
  });
93
107
  });
94
108
  return new Promise((resolve, reject) => {
95
109
  this.server?.once("error", (err) => reject(err));
96
- this.server?.listen(this.port, "127.0.0.1", () => {
110
+ this.server?.listen(this.port, "0.0.0.0", () => {
97
111
  console.log(`SSH Mimic listening on port ${this.port}`);
112
+ this.emit("start", { port: this.port });
98
113
  resolve(this.port);
99
114
  });
100
115
  });
@@ -106,8 +121,10 @@ class SshMimic {
106
121
  if (this.server) {
107
122
  this.server.close(() => {
108
123
  console.log("SSH Mimic stopped");
124
+ this.emit("stop");
109
125
  });
110
126
  }
111
127
  }
112
128
  }
129
+ export { SftpMimic } from "./sftp";
113
130
  export { SshMimic };
@@ -0,0 +1,48 @@
1
+ /** biome-ignore-all lint/style/useNamingConvention: const as enum */
2
+ import { EventEmitter } from "node:events";
3
+ import { Server as SshServer } from "ssh2";
4
+ import type VirtualFileSystem from "../VirtualFileSystem";
5
+ import { VirtualShell } from "../VirtualShell";
6
+ import type { VirtualUserManager } from "../VirtualUserManager";
7
+ export interface SftpMimicOptions {
8
+ port: number;
9
+ hostname?: string;
10
+ shell?: VirtualShell;
11
+ vfs?: VirtualFileSystem;
12
+ users?: VirtualUserManager;
13
+ }
14
+ export declare class SftpMimic extends EventEmitter {
15
+ port: number;
16
+ server: SshServer | null;
17
+ private readonly hostname;
18
+ private readonly shell;
19
+ private readonly vfs;
20
+ private readonly users;
21
+ private nextHandleId;
22
+ private handles;
23
+ constructor({ port, hostname, shell, vfs, users, }: SftpMimicOptions);
24
+ private getVfs;
25
+ private getUsers;
26
+ start(): Promise<number>;
27
+ stop(): void;
28
+ /**
29
+ * Resolves SFTP request paths with proper handling of relative paths.
30
+ * Relative paths (including ".") are resolved relative to the user's home directory.
31
+ * This is standard SFTP behavior where the "working directory" is always the home.
32
+ */
33
+ private resolveRequestPath;
34
+ /**
35
+ * Verifies that a target path is confined within the user's home directory.
36
+ * This implements chroot-like behavior for security.
37
+ * @param targetPath - The normalized target path
38
+ * @param authUser - The authenticated username
39
+ * @returns true if path is within home, false if traversal attempt detected
40
+ */
41
+ private isPathWithinHome;
42
+ private createAttrs;
43
+ private openHandle;
44
+ private getHandle;
45
+ private closeHandle;
46
+ private attachSftpHandlers;
47
+ }
48
+ //# sourceMappingURL=sftp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sftp.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/sftp.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AA2HhE,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,iBAAiB,CAAC;IACxB,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC3B;AAED,qBAAa,SAAU,SAAQ,YAAY;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,OAAO,CAAiC;gBAEpC,EACX,IAAI,EACJ,QAA0B,EAC1B,KAAK,EACL,GAAG,EACH,KAAK,GACL,EAAE,gBAAgB;IAuBnB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAIH,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAwJ9B,IAAI,IAAI,IAAI;IASnB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAiB1B;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,kBAAkB;CA6a1B"}