typescript-virtual-container 1.1.4 → 1.1.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 (39) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/HONEYPOT.md +358 -0
  3. package/README.md +471 -16
  4. package/dist/Honeypot/index.d.ts +132 -0
  5. package/dist/Honeypot/index.d.ts.map +1 -0
  6. package/dist/Honeypot/index.js +289 -0
  7. package/dist/SSHMimic/index.d.ts +2 -1
  8. package/dist/SSHMimic/index.d.ts.map +1 -1
  9. package/dist/SSHMimic/index.js +12 -1
  10. package/dist/SSHMimic/sftp.d.ts +3 -1
  11. package/dist/SSHMimic/sftp.d.ts.map +1 -1
  12. package/dist/SSHMimic/sftp.js +20 -1
  13. package/dist/VirtualFileSystem/index.d.ts +2 -1
  14. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  15. package/dist/VirtualFileSystem/index.js +8 -1
  16. package/dist/VirtualShell/index.d.ts +2 -1
  17. package/dist/VirtualShell/index.d.ts.map +1 -1
  18. package/dist/VirtualShell/index.js +6 -1
  19. package/dist/VirtualUserManager/index.d.ts +2 -1
  20. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  21. package/dist/VirtualUserManager/index.js +19 -1
  22. package/dist/honeypot.d.ts +132 -0
  23. package/dist/honeypot.d.ts.map +1 -0
  24. package/dist/honeypot.js +289 -0
  25. package/dist/index.d.ts +3 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +2 -1
  28. package/examples/README.md +210 -0
  29. package/examples/honeypot-audit.ts +180 -0
  30. package/examples/honeypot-export.ts +253 -0
  31. package/examples/honeypot-quickstart.ts +110 -0
  32. package/package.json +1 -1
  33. package/src/Honeypot/index.ts +422 -0
  34. package/src/SSHMimic/index.ts +13 -1
  35. package/src/SSHMimic/sftp.ts +21 -1
  36. package/src/VirtualFileSystem/index.ts +8 -1
  37. package/src/VirtualShell/index.ts +6 -1
  38. package/src/VirtualUserManager/index.ts +21 -3
  39. package/src/index.ts +6 -0
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Honeypot tracking and auditing module for virtual shell events.
3
+ *
4
+ * Attaches listeners to VirtualShell, VirtualFileSystem, VirtualUserManager,
5
+ * SshMimic, and SftpMimic instances to log all activity for security auditing,
6
+ * anomaly detection, and forensic analysis.
7
+ *
8
+ * @module honeypot
9
+ */
10
+ import type { SshMimic } from "../SSHMimic";
11
+ import type { SftpMimic } from "../SSHMimic/sftp";
12
+ import type VirtualFileSystem from "../VirtualFileSystem";
13
+ import type { VirtualShell } from "../VirtualShell";
14
+ import type { VirtualUserManager } from "../VirtualUserManager";
15
+ /**
16
+ * Audit log entry recorded for each event.
17
+ */
18
+ export interface AuditLogEntry {
19
+ timestamp: string;
20
+ type: string;
21
+ source: string;
22
+ details: Record<string, unknown>;
23
+ }
24
+ /**
25
+ * Statistics tracker for honeypot activity.
26
+ */
27
+ export interface HoneyPotStats {
28
+ authAttempts: number;
29
+ authSuccesses: number;
30
+ authFailures: number;
31
+ commands: number;
32
+ fileWrites: number;
33
+ fileReads: number;
34
+ sessionStarts: number;
35
+ sessionEnds: number;
36
+ userCreated: number;
37
+ userDeleted: number;
38
+ clientConnects: number;
39
+ clientDisconnects: number;
40
+ }
41
+ /**
42
+ * HoneyPot audit and event tracking utility.
43
+ *
44
+ * Singleton-like helper that attaches listeners to virtual shell components
45
+ * and maintains an audit log of all activity.
46
+ */
47
+ export declare class HoneyPot {
48
+ private auditLog;
49
+ private stats;
50
+ private maxLogSize;
51
+ /**
52
+ * Creates a new HoneyPot instance.
53
+ *
54
+ * @param maxLogSize Maximum audit log entries to retain (default: 10000).
55
+ */
56
+ constructor(maxLogSize?: number);
57
+ /**
58
+ * Attaches honeypot listeners to all provided event emitters.
59
+ *
60
+ * @param shell VirtualShell instance.
61
+ * @param vfs VirtualFileSystem instance.
62
+ * @param users VirtualUserManager instance.
63
+ * @param ssh SshMimic instance (optional).
64
+ * @param sftp SftpMimic instance (optional).
65
+ */
66
+ attach(shell: VirtualShell, vfs: VirtualFileSystem, users: VirtualUserManager, ssh?: SshMimic, sftp?: SftpMimic): void;
67
+ /**
68
+ * Attaches to VirtualShell events.
69
+ */
70
+ private attachVirtualShell;
71
+ /**
72
+ * Attaches to VirtualFileSystem events.
73
+ */
74
+ private attachVirtualFileSystem;
75
+ /**
76
+ * Attaches to VirtualUserManager events.
77
+ */
78
+ private attachVirtualUserManager;
79
+ /**
80
+ * Attaches to SshMimic events.
81
+ */
82
+ private attachSshMimic;
83
+ /**
84
+ * Attaches to SftpMimic events.
85
+ */
86
+ private attachSftpMimic;
87
+ /**
88
+ * Records an audit log entry.
89
+ *
90
+ * @param source Event source (e.g., "SshMimic", "VirtualFileSystem").
91
+ * @param type Event type.
92
+ * @param details Event-specific data.
93
+ */
94
+ private log;
95
+ /**
96
+ * Returns audit log entries matching optional filters.
97
+ *
98
+ * @param type Optional event type filter.
99
+ * @param source Optional source filter.
100
+ * @returns Filtered audit log entries.
101
+ */
102
+ getAuditLog(type?: string, source?: string): AuditLogEntry[];
103
+ /**
104
+ * Returns current activity statistics.
105
+ *
106
+ * @returns Snapshot of honeypot stats.
107
+ */
108
+ getStats(): Readonly<HoneyPotStats>;
109
+ /**
110
+ * Clears audit log and resets statistics.
111
+ */
112
+ reset(): void;
113
+ /**
114
+ * Returns recent log entries in reverse chronological order.
115
+ *
116
+ * @param limit Number of recent entries to return (default: 100).
117
+ * @returns Recent audit log entries.
118
+ */
119
+ getRecent(limit?: number): AuditLogEntry[];
120
+ /**
121
+ * Detects potential security issues based on activity patterns.
122
+ *
123
+ * @returns Array of anomalies detected.
124
+ */
125
+ detectAnomalies(): Array<{
126
+ type: string;
127
+ severity: "low" | "medium" | "high";
128
+ message: string;
129
+ }>;
130
+ }
131
+ export default HoneyPot;
132
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Honeypot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,qBAAa,QAAQ;IACpB,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,KAAK,CAaX;IAEF,OAAO,CAAC,UAAU,CAAS;IAE3B;;;;OAIG;gBACS,UAAU,GAAE,MAAc;IAItC;;;;;;;;OAQG;IACI,MAAM,CACZ,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,kBAAkB,EACzB,GAAG,CAAC,EAAE,QAAQ,EACd,IAAI,CAAC,EAAE,SAAS,GACd,IAAI;IAYP;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAmB1B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAkChC;;OAEG;IACH,OAAO,CAAC,cAAc;IAyCtB;;OAEG;IACH,OAAO,CAAC,eAAe;IAyCvB;;;;;;OAMG;IACH,OAAO,CAAC,GAAG;IAuBX;;;;;;OAMG;IACI,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,EAAE;IAOnE;;;;OAIG;IACI,QAAQ,IAAI,QAAQ,CAAC,aAAa,CAAC;IAI1C;;OAEG;IACI,KAAK,IAAI,IAAI;IAkBpB;;;;;OAKG;IACI,SAAS,CAAC,KAAK,GAAE,MAAY,GAAG,aAAa,EAAE;IAItD;;;;OAIG;IACI,eAAe,IAAI,KAAK,CAAC;QAC/B,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;QACpC,OAAO,EAAE,MAAM,CAAC;KAChB,CAAC;CAkDF;AAED,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Honeypot tracking and auditing module for virtual shell events.
3
+ *
4
+ * Attaches listeners to VirtualShell, VirtualFileSystem, VirtualUserManager,
5
+ * SshMimic, and SftpMimic instances to log all activity for security auditing,
6
+ * anomaly detection, and forensic analysis.
7
+ *
8
+ * @module honeypot
9
+ */
10
+ /**
11
+ * HoneyPot audit and event tracking utility.
12
+ *
13
+ * Singleton-like helper that attaches listeners to virtual shell components
14
+ * and maintains an audit log of all activity.
15
+ */
16
+ export class HoneyPot {
17
+ auditLog = [];
18
+ stats = {
19
+ authAttempts: 0,
20
+ authSuccesses: 0,
21
+ authFailures: 0,
22
+ commands: 0,
23
+ fileWrites: 0,
24
+ fileReads: 0,
25
+ sessionStarts: 0,
26
+ sessionEnds: 0,
27
+ userCreated: 0,
28
+ userDeleted: 0,
29
+ clientConnects: 0,
30
+ clientDisconnects: 0,
31
+ };
32
+ maxLogSize;
33
+ /**
34
+ * Creates a new HoneyPot instance.
35
+ *
36
+ * @param maxLogSize Maximum audit log entries to retain (default: 10000).
37
+ */
38
+ constructor(maxLogSize = 10000) {
39
+ this.maxLogSize = maxLogSize;
40
+ }
41
+ /**
42
+ * Attaches honeypot listeners to all provided event emitters.
43
+ *
44
+ * @param shell VirtualShell instance.
45
+ * @param vfs VirtualFileSystem instance.
46
+ * @param users VirtualUserManager instance.
47
+ * @param ssh SshMimic instance (optional).
48
+ * @param sftp SftpMimic instance (optional).
49
+ */
50
+ attach(shell, vfs, users, ssh, sftp) {
51
+ this.attachVirtualShell(shell);
52
+ this.attachVirtualFileSystem(vfs);
53
+ this.attachVirtualUserManager(users);
54
+ if (ssh) {
55
+ this.attachSshMimic(ssh);
56
+ }
57
+ if (sftp) {
58
+ this.attachSftpMimic(sftp);
59
+ }
60
+ }
61
+ /**
62
+ * Attaches to VirtualShell events.
63
+ */
64
+ attachVirtualShell(shell) {
65
+ shell.on("initialized", () => {
66
+ this.log("VirtualShell", "initialized", {});
67
+ });
68
+ shell.on("command", (data) => {
69
+ this.stats.commands++;
70
+ this.log("VirtualShell", "command", data);
71
+ });
72
+ shell.on("session:start", (data) => {
73
+ this.stats.sessionStarts++;
74
+ this.log("VirtualShell", "session:start", data);
75
+ });
76
+ }
77
+ /**
78
+ * Attaches to VirtualFileSystem events.
79
+ */
80
+ attachVirtualFileSystem(vfs) {
81
+ vfs.on("file:read", (data) => {
82
+ this.stats.fileReads++;
83
+ this.log("VirtualFileSystem", "file:read", data);
84
+ });
85
+ vfs.on("file:write", (data) => {
86
+ this.stats.fileWrites++;
87
+ this.log("VirtualFileSystem", "file:write", data);
88
+ });
89
+ vfs.on("dir:create", (data) => {
90
+ this.log("VirtualFileSystem", "dir:create", data);
91
+ });
92
+ vfs.on("mirror:flush", () => {
93
+ this.log("VirtualFileSystem", "mirror:flush", {});
94
+ });
95
+ }
96
+ /**
97
+ * Attaches to VirtualUserManager events.
98
+ */
99
+ attachVirtualUserManager(users) {
100
+ users.on("initialized", () => {
101
+ this.log("VirtualUserManager", "initialized", {});
102
+ });
103
+ users.on("user:add", (data) => {
104
+ this.stats.userCreated++;
105
+ this.log("VirtualUserManager", "user:add", data);
106
+ });
107
+ users.on("user:delete", (data) => {
108
+ this.stats.userDeleted++;
109
+ this.log("VirtualUserManager", "user:delete", data);
110
+ });
111
+ users.on("session:register", (data) => {
112
+ this.log("VirtualUserManager", "session:register", data);
113
+ });
114
+ users.on("session:unregister", (data) => {
115
+ this.stats.sessionEnds++;
116
+ this.log("VirtualUserManager", "session:unregister", data);
117
+ });
118
+ }
119
+ /**
120
+ * Attaches to SshMimic events.
121
+ */
122
+ attachSshMimic(ssh) {
123
+ ssh.on("start", (data) => {
124
+ this.log("SshMimic", "start", data);
125
+ });
126
+ ssh.on("stop", () => {
127
+ this.log("SshMimic", "stop", {});
128
+ });
129
+ ssh.on("auth:success", (data) => {
130
+ this.stats.authAttempts++;
131
+ this.stats.authSuccesses++;
132
+ this.log("SshMimic", "auth:success", data);
133
+ });
134
+ ssh.on("auth:failure", (data) => {
135
+ this.stats.authAttempts++;
136
+ this.stats.authFailures++;
137
+ this.log("SshMimic", "auth:failure", data);
138
+ });
139
+ ssh.on("client:connect", () => {
140
+ this.stats.clientConnects++;
141
+ this.log("SshMimic", "client:connect", {});
142
+ });
143
+ ssh.on("client:disconnect", (data) => {
144
+ this.stats.clientDisconnects++;
145
+ this.log("SshMimic", "client:disconnect", data);
146
+ });
147
+ }
148
+ /**
149
+ * Attaches to SftpMimic events.
150
+ */
151
+ attachSftpMimic(sftp) {
152
+ sftp.on("start", (data) => {
153
+ this.log("SftpMimic", "start", data);
154
+ });
155
+ sftp.on("stop", () => {
156
+ this.log("SftpMimic", "stop", {});
157
+ });
158
+ sftp.on("auth:success", (data) => {
159
+ this.stats.authAttempts++;
160
+ this.stats.authSuccesses++;
161
+ this.log("SftpMimic", "auth:success", data);
162
+ });
163
+ sftp.on("auth:failure", (data) => {
164
+ this.stats.authAttempts++;
165
+ this.stats.authFailures++;
166
+ this.log("SftpMimic", "auth:failure", data);
167
+ });
168
+ sftp.on("client:connect", () => {
169
+ this.stats.clientConnects++;
170
+ this.log("SftpMimic", "client:connect", {});
171
+ });
172
+ sftp.on("client:disconnect", (data) => {
173
+ this.stats.clientDisconnects++;
174
+ this.log("SftpMimic", "client:disconnect", data);
175
+ });
176
+ }
177
+ /**
178
+ * Records an audit log entry.
179
+ *
180
+ * @param source Event source (e.g., "SshMimic", "VirtualFileSystem").
181
+ * @param type Event type.
182
+ * @param details Event-specific data.
183
+ */
184
+ log(source, type, details) {
185
+ const entry = {
186
+ timestamp: new Date().toISOString(),
187
+ type,
188
+ source,
189
+ details,
190
+ };
191
+ this.auditLog.push(entry);
192
+ // Trim log if exceeds max size
193
+ if (this.auditLog.length > this.maxLogSize) {
194
+ this.auditLog = this.auditLog.slice(-this.maxLogSize);
195
+ }
196
+ // Console output for real-time monitoring
197
+ console.log(`[AUDIT] ${entry.timestamp} | ${source} | ${type}`, details);
198
+ }
199
+ /**
200
+ * Returns audit log entries matching optional filters.
201
+ *
202
+ * @param type Optional event type filter.
203
+ * @param source Optional source filter.
204
+ * @returns Filtered audit log entries.
205
+ */
206
+ getAuditLog(type, source) {
207
+ return this.auditLog.filter((entry) => (!type || entry.type === type) && (!source || entry.source === source));
208
+ }
209
+ /**
210
+ * Returns current activity statistics.
211
+ *
212
+ * @returns Snapshot of honeypot stats.
213
+ */
214
+ getStats() {
215
+ return Object.freeze({ ...this.stats });
216
+ }
217
+ /**
218
+ * Clears audit log and resets statistics.
219
+ */
220
+ reset() {
221
+ this.auditLog = [];
222
+ this.stats = {
223
+ authAttempts: 0,
224
+ authSuccesses: 0,
225
+ authFailures: 0,
226
+ commands: 0,
227
+ fileWrites: 0,
228
+ fileReads: 0,
229
+ sessionStarts: 0,
230
+ sessionEnds: 0,
231
+ userCreated: 0,
232
+ userDeleted: 0,
233
+ clientConnects: 0,
234
+ clientDisconnects: 0,
235
+ };
236
+ }
237
+ /**
238
+ * Returns recent log entries in reverse chronological order.
239
+ *
240
+ * @param limit Number of recent entries to return (default: 100).
241
+ * @returns Recent audit log entries.
242
+ */
243
+ getRecent(limit = 100) {
244
+ return this.auditLog.slice(Math.max(0, this.auditLog.length - limit));
245
+ }
246
+ /**
247
+ * Detects potential security issues based on activity patterns.
248
+ *
249
+ * @returns Array of anomalies detected.
250
+ */
251
+ detectAnomalies() {
252
+ const anomalies = [];
253
+ // High auth failure rate
254
+ if (this.stats.authAttempts > 0 &&
255
+ this.stats.authFailures / this.stats.authAttempts > 0.5) {
256
+ anomalies.push({
257
+ type: "high_auth_failure_rate",
258
+ severity: "medium",
259
+ message: `Auth failure rate: ${((this.stats.authFailures / this.stats.authAttempts) * 100).toFixed(1)}%`,
260
+ });
261
+ }
262
+ // Excessive auth failures in short time
263
+ if (this.stats.authFailures > 10) {
264
+ anomalies.push({
265
+ type: "excessive_auth_failures",
266
+ severity: "high",
267
+ message: `${this.stats.authFailures} authentication failures detected`,
268
+ });
269
+ }
270
+ // Unusual command execution volume
271
+ if (this.stats.commands > 1000) {
272
+ anomalies.push({
273
+ type: "high_command_volume",
274
+ severity: "low",
275
+ message: `${this.stats.commands} commands executed`,
276
+ });
277
+ }
278
+ // Unusual file write volume
279
+ if (this.stats.fileWrites > 500) {
280
+ anomalies.push({
281
+ type: "high_write_volume",
282
+ severity: "medium",
283
+ message: `${this.stats.fileWrites} file write operations`,
284
+ });
285
+ }
286
+ return anomalies;
287
+ }
288
+ }
289
+ export default HoneyPot;
@@ -1,3 +1,4 @@
1
+ import { EventEmitter } from "node:events";
1
2
  import { Server as SshServer } from "ssh2";
2
3
  import { VirtualShell } from "../VirtualShell";
3
4
  /**
@@ -7,7 +8,7 @@ import { VirtualShell } from "../VirtualShell";
7
8
  * Create an instance, call {@link SshMimic.start}, and stop it with
8
9
  * {@link SshMimic.stop} when your process exits.
9
10
  */
10
- declare class SshMimic {
11
+ declare class SshMimic extends EventEmitter {
11
12
  port: number;
12
13
  server: SshServer | null;
13
14
  private shell;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAI/C;;;;;;GAMG;AACH,cAAM,QAAQ;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,aAAa,CAAS;IAE9B;;;;;;OAMG;gBACS,EACX,IAAI,EACJ,QAA0B,EAC1B,KAAkC,GAClC,EAAE;QACF,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,YAAY,CAAC;KACrB;IAOD;;;;OAIG;IACU,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IA+GrC;;OAEG;IACI,IAAI,IAAI,IAAI;CAOnB;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAI/C;;;;;;GAMG;AACH,cAAM,QAAS,SAAQ,YAAY;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,aAAa,CAAS;IAE9B;;;;;;OAMG;gBACS,EACX,IAAI,EACJ,QAA0B,EAC1B,KAAkC,GAClC,EAAE;QACF,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,YAAY,CAAC;KACrB;IAQD;;;;OAIG;IACU,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAwHrC;;OAEG;IACI,IAAI,IAAI,IAAI;CAQnB;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { EventEmitter } from "node:events";
1
2
  import { Server as SshServer } from "ssh2";
2
3
  import { VirtualShell } from "../VirtualShell";
3
4
  import { runExec } from "./exec";
@@ -9,7 +10,7 @@ import { loadOrCreateHostKey } from "./hostKey";
9
10
  * Create an instance, call {@link SshMimic.start}, and stop it with
10
11
  * {@link SshMimic.stop} when your process exits.
11
12
  */
12
- class SshMimic {
13
+ class SshMimic extends EventEmitter {
13
14
  port;
14
15
  server;
15
16
  shell;
@@ -22,6 +23,7 @@ class SshMimic {
22
23
  * @param shell Optional preconfigured virtual shell instance to reuse.
23
24
  */
24
25
  constructor({ port, hostname = "typescript-vm", shell = new VirtualShell(hostname), }) {
26
+ super();
25
27
  this.port = port;
26
28
  this.shellHostname = hostname;
27
29
  this.server = null;
@@ -44,17 +46,23 @@ class SshMimic {
44
46
  let authUser = "root";
45
47
  let remoteAddress = "unknown";
46
48
  let sessionId = null;
49
+ this.emit("client:connect");
47
50
  client.on("authentication", (ctx) => {
48
51
  shell;
49
52
  if (ctx.method === "password") {
50
53
  const candidateUser = ctx.username || "root";
51
54
  remoteAddress = ctx.ip ?? remoteAddress;
52
55
  if (!shell.users.verifyPassword(candidateUser, ctx.password ?? "")) {
56
+ this.emit("auth:failure", {
57
+ username: candidateUser,
58
+ remoteAddress,
59
+ });
53
60
  ctx.reject();
54
61
  return;
55
62
  }
56
63
  authUser = candidateUser;
57
64
  sessionId = shell.users.registerSession(authUser, remoteAddress).id;
65
+ this.emit("auth:success", { username: authUser, remoteAddress });
58
66
  const homePath = `/home/${authUser}`;
59
67
  if (!shell.vfs.exists(homePath)) {
60
68
  shell.vfs.mkdir(homePath, 0o755);
@@ -68,6 +76,7 @@ class SshMimic {
68
76
  });
69
77
  client.on("close", () => {
70
78
  shell.users.unregisterSession(sessionId);
79
+ this.emit("client:disconnect", { user: authUser });
71
80
  sessionId = null;
72
81
  });
73
82
  client.on("ready", () => {
@@ -100,6 +109,7 @@ class SshMimic {
100
109
  this.server?.once("error", (err) => reject(err));
101
110
  this.server?.listen(this.port, "0.0.0.0", () => {
102
111
  console.log(`SSH Mimic listening on port ${this.port}`);
112
+ this.emit("start", { port: this.port });
103
113
  resolve(this.port);
104
114
  });
105
115
  });
@@ -111,6 +121,7 @@ class SshMimic {
111
121
  if (this.server) {
112
122
  this.server.close(() => {
113
123
  console.log("SSH Mimic stopped");
124
+ this.emit("stop");
114
125
  });
115
126
  }
116
127
  }
@@ -1,3 +1,5 @@
1
+ /** biome-ignore-all lint/style/useNamingConvention: const as enum */
2
+ import { EventEmitter } from "node:events";
1
3
  import { Server as SshServer } from "ssh2";
2
4
  import type VirtualFileSystem from "../VirtualFileSystem";
3
5
  import { VirtualShell } from "../VirtualShell";
@@ -9,7 +11,7 @@ export interface SftpMimicOptions {
9
11
  vfs?: VirtualFileSystem;
10
12
  users?: VirtualUserManager;
11
13
  }
12
- export declare class SftpMimic {
14
+ export declare class SftpMimic extends EventEmitter {
13
15
  port: number;
14
16
  server: SshServer | null;
15
17
  private readonly hostname;
@@ -1 +1 @@
1
- {"version":3,"file":"sftp.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/sftp.ts"],"names":[],"mappings":"AAGA,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,SAAS;IACrB,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;IAsBnB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAIH,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAuI9B,IAAI,IAAI,IAAI;IAQnB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAiB1B;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,kBAAkB;CA6a1B"}
1
+ {"version":3,"file":"sftp.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/sftp.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;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"}
@@ -1,4 +1,5 @@
1
1
  /** biome-ignore-all lint/style/useNamingConvention: const as enum */
2
+ import { EventEmitter } from "node:events";
2
3
  import * as path from "node:path";
3
4
  import { Server as SshServer } from "ssh2";
4
5
  import { VirtualShell } from "../VirtualShell";
@@ -22,7 +23,7 @@ const OPEN_MODE = {
22
23
  TRUNC: 0x00000010,
23
24
  EXCL: 0x00000020,
24
25
  };
25
- export class SftpMimic {
26
+ export class SftpMimic extends EventEmitter {
26
27
  port;
27
28
  server;
28
29
  hostname;
@@ -32,6 +33,7 @@ export class SftpMimic {
32
33
  nextHandleId = 0;
33
34
  handles = new Map();
34
35
  constructor({ port, hostname = "typescript-vm", shell, vfs, users, }) {
36
+ super();
35
37
  this.port = port;
36
38
  this.server = null;
37
39
  this.hostname = hostname;
@@ -80,6 +82,7 @@ export class SftpMimic {
80
82
  let authUser = "root";
81
83
  let sessionId = null;
82
84
  let remoteAddress = "unknown";
85
+ this.emit("client:connect");
83
86
  // Add error handling for the client
84
87
  client.on("error", (error) => {
85
88
  console.error(`[SFTP] Client error:`, error);
@@ -104,10 +107,15 @@ export class SftpMimic {
104
107
  console.log(`[SFTP] Auth attempt: user=${candidateUser}, method=${ctx.method}, ip=${remoteAddress}`);
105
108
  if (ctx.method === "password") {
106
109
  if (!this.getUsers().verifyPassword(candidateUser, ctx.password ?? "")) {
110
+ this.emit("auth:failure", {
111
+ username: candidateUser,
112
+ remoteAddress,
113
+ });
107
114
  ctx.reject(allowedAuthMethods);
108
115
  return;
109
116
  }
110
117
  acceptSession(candidateUser);
118
+ this.emit("auth:success", { username: authUser, remoteAddress });
111
119
  ctx.accept();
112
120
  return;
113
121
  }
@@ -116,10 +124,18 @@ export class SftpMimic {
116
124
  keyboardCtx.prompt([{ prompt: "Password: ", echo: false }], (answers) => {
117
125
  const password = answers[0] ?? "";
118
126
  if (!this.getUsers().verifyPassword(candidateUser, password)) {
127
+ this.emit("auth:failure", {
128
+ username: candidateUser,
129
+ remoteAddress,
130
+ });
119
131
  keyboardCtx.reject(allowedAuthMethods);
120
132
  return;
121
133
  }
122
134
  acceptSession(candidateUser);
135
+ this.emit("auth:success", {
136
+ username: authUser,
137
+ remoteAddress,
138
+ });
123
139
  keyboardCtx.accept();
124
140
  });
125
141
  return;
@@ -128,6 +144,7 @@ export class SftpMimic {
128
144
  });
129
145
  client.on("close", () => {
130
146
  this.getUsers().unregisterSession(sessionId);
147
+ this.emit("client:disconnect", { user: authUser });
131
148
  sessionId = null;
132
149
  });
133
150
  client.on("ready", () => {
@@ -152,6 +169,7 @@ export class SftpMimic {
152
169
  ? address.port
153
170
  : this.port;
154
171
  console.log(`SFTP Mimic listening on port ${actualPort}`);
172
+ this.emit("start", { port: actualPort });
155
173
  resolve(actualPort);
156
174
  });
157
175
  });
@@ -160,6 +178,7 @@ export class SftpMimic {
160
178
  if (this.server) {
161
179
  this.server.close(() => {
162
180
  console.log("SFTP Mimic stopped");
181
+ this.emit("stop");
163
182
  });
164
183
  }
165
184
  }
@@ -1,3 +1,4 @@
1
+ import { EventEmitter } from "node:events";
1
2
  import type { RemoveOptions, VfsNodeStats, WriteFileOptions } from "../types/vfs";
2
3
  /**
3
4
  * In-memory virtual filesystem with tar.gz mirror persistence.
@@ -6,7 +7,7 @@ import type { RemoveOptions, VfsNodeStats, WriteFileOptions } from "../types/vfs
6
7
  * {@link VirtualFileSystem.restoreMirror} on startup and
7
8
  * {@link VirtualFileSystem.flushMirror} to persist pending changes.
8
9
  */
9
- declare class VirtualFileSystem {
10
+ declare class VirtualFileSystem extends EventEmitter {
10
11
  private readonly mirrorRoot;
11
12
  private ensureMirrorRoot;
12
13
  private resolveFsPath;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualFileSystem/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,MAAM,cAAc,CAAC;AAGtB;;;;;;GAMG;AACH,cAAM,iBAAiB;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,eAAe;IA4BvB;;;;OAIG;gBACS,OAAO,GAAE,MAAsB;IAI3C;;;;OAIG;IACU,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3C;;;;OAIG;IACU,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzC;;;;;OAKG;IACI,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,GAAE,MAAc,GAAG,IAAI;IAW5D;;;;;;;;OAQG;IACI,SAAS,CACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,OAAO,GAAE,gBAAqB,GAC5B,IAAI;IAuBP;;;;;;;OAOG;IACI,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAY3C;;;;;OAKG;IACI,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAS1C;;;;;OAKG;IACI,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAQpD;;;;;OAKG;IACI,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY;IAqC7C;;;;;OAKG;IACI,IAAI,CAAC,OAAO,GAAE,MAAY,GAAG,MAAM,EAAE;IAS5C;;;;;OAKG;IACI,IAAI,CAAC,OAAO,GAAE,MAAY,GAAG,MAAM;IAW1C;;;;;;;;OAQG;IACI,aAAa,CAAC,UAAU,GAAE,MAAY,GAAG,MAAM;IAQtD;;;;OAIG;IACI,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAY7C;;;;OAIG;IACI,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAY/C;;;;;OAKG;IACI,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,IAAI;IA4BpE;;;;;OAKG;IACI,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAsBnD;AAED,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualFileSystem/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,OAAO,KAAK,EACX,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,MAAM,cAAc,CAAC;AAGtB;;;;;;GAMG;AACH,cAAM,iBAAkB,SAAQ,YAAY;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,eAAe;IA4BvB;;;;OAIG;gBACS,OAAO,GAAE,MAAsB;IAK3C;;;;OAIG;IACU,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3C;;;;OAIG;IACU,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAKzC;;;;;OAKG;IACI,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,GAAE,MAAc,GAAG,IAAI;IAY5D;;;;;;;;OAQG;IACI,SAAS,CACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,OAAO,GAAE,gBAAqB,GAC5B,IAAI;IAwBP;;;;;;;OAOG;IACI,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAc3C;;;;;OAKG;IACI,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAS1C;;;;;OAKG;IACI,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAQpD;;;;;OAKG;IACI,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY;IAqC7C;;;;;OAKG;IACI,IAAI,CAAC,OAAO,GAAE,MAAY,GAAG,MAAM,EAAE;IAS5C;;;;;OAKG;IACI,IAAI,CAAC,OAAO,GAAE,MAAY,GAAG,MAAM;IAW1C;;;;;;;;OAQG;IACI,aAAa,CAAC,UAAU,GAAE,MAAY,GAAG,MAAM;IAQtD;;;;OAIG;IACI,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAY7C;;;;OAIG;IACI,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAY/C;;;;;OAKG;IACI,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,IAAI;IA4BpE;;;;;OAKG;IACI,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAsBnD;AAED,eAAe,iBAAiB,CAAC"}