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
@@ -1,3 +1,4 @@
1
+ import { EventEmitter } from "node:events";
1
2
  import * as fs from "node:fs";
2
3
  import * as path from "node:path";
3
4
  import { gunzipSync, gzipSync } from "node:zlib";
@@ -15,7 +16,7 @@ import { normalizePath } from "./path";
15
16
  * {@link VirtualFileSystem.restoreMirror} on startup and
16
17
  * {@link VirtualFileSystem.flushMirror} to persist pending changes.
17
18
  */
18
- class VirtualFileSystem {
19
+ class VirtualFileSystem extends EventEmitter {
19
20
  private readonly mirrorRoot: string;
20
21
 
21
22
  private ensureMirrorRoot(): void {
@@ -93,6 +94,7 @@ class VirtualFileSystem {
93
94
  * @param baseDir Base directory used to resolve mirror archive location.
94
95
  */
95
96
  constructor(baseDir: string = process.cwd()) {
97
+ super();
96
98
  this.mirrorRoot = path.resolve(baseDir, ".vfs", "mirror");
97
99
  }
98
100
 
@@ -112,6 +114,7 @@ class VirtualFileSystem {
112
114
  */
113
115
  public async flushMirror(): Promise<void> {
114
116
  this.ensureMirrorRoot();
117
+ this.emit("mirror:flush");
115
118
  }
116
119
 
117
120
  /**
@@ -129,6 +132,7 @@ class VirtualFileSystem {
129
132
  );
130
133
  }
131
134
  fs.mkdirSync(fsPath, { recursive: true, mode });
135
+ this.emit("dir:create", { path: normalizePath(targetPath), mode });
132
136
  }
133
137
 
134
138
  /**
@@ -165,6 +169,7 @@ class VirtualFileSystem {
165
169
 
166
170
  fs.writeFileSync(fsPath, storedContent);
167
171
  fs.chmodSync(fsPath, options.mode ?? 0o644);
172
+ this.emit("file:write", { path: normalized, size: storedContent.length });
168
173
  }
169
174
 
170
175
  /**
@@ -184,6 +189,8 @@ class VirtualFileSystem {
184
189
 
185
190
  const stored = fs.readFileSync(fsPath);
186
191
  const raw = this.detectGzipFile(fsPath) ? gunzipSync(stored) : stored;
192
+ const normalized = normalizePath(targetPath);
193
+ this.emit("file:read", { path: normalized, size: raw.length });
187
194
  return raw.toString("utf8");
188
195
  }
189
196
 
@@ -1,4 +1,5 @@
1
1
  import { randomBytes } from "node:crypto";
2
+ import { EventEmitter } from "node:events";
2
3
  import { createCustomCommand, registerCommand, runCommand } from "../commands";
3
4
  import type { CommandContext, CommandResult } from "../types/commands";
4
5
  import type { ShellStream } from "../types/streams";
@@ -46,7 +47,7 @@ function resolveAutoSudoForNewUsers(): boolean {
46
47
  * Instances are used both by the SSH server facade and by the programmatic
47
48
  * client API.
48
49
  */
49
- class VirtualShell {
50
+ class VirtualShell extends EventEmitter {
50
51
  basePath: string = ".";
51
52
  vfs: VirtualFileSystem;
52
53
  users: VirtualUserManager;
@@ -66,6 +67,7 @@ class VirtualShell {
66
67
  properties?: ShellProperties,
67
68
  basePath?: string,
68
69
  ) {
70
+ super();
69
71
  this.hostname = hostname;
70
72
  this.properties = properties || defaultShellProperties;
71
73
  this.basePath = basePath || ".";
@@ -84,6 +86,7 @@ class VirtualShell {
84
86
  this.initialized = (async () => {
85
87
  await vfs.restoreMirror();
86
88
  await users.initialize();
89
+ this.emit("initialized");
87
90
  })();
88
91
  }
89
92
 
@@ -124,6 +127,7 @@ class VirtualShell {
124
127
  */
125
128
  executeCommand(rawInput: string, authUser: string, cwd: string): void {
126
129
  runCommand(rawInput, authUser, this.hostname, "shell", cwd, this);
130
+ this.emit("command", { command: rawInput, user: authUser, cwd });
127
131
  }
128
132
 
129
133
  /**
@@ -143,6 +147,7 @@ class VirtualShell {
143
147
  terminalSize: { cols: number; rows: number },
144
148
  ): void {
145
149
  // Interactive shell logic
150
+ this.emit("session:start", { user: authUser, sessionId, remoteAddress });
146
151
  startShell(
147
152
  this.properties,
148
153
  stream,
@@ -1,4 +1,5 @@
1
1
  import { randomBytes, randomUUID, scryptSync } from "node:crypto";
2
+ import { EventEmitter } from "node:events";
2
3
  import * as path from "node:path";
3
4
  import type VirtualFileSystem from "../VirtualFileSystem";
4
5
 
@@ -31,7 +32,7 @@ export interface VirtualActiveSession {
31
32
  *
32
33
  * Passwords are hashed with scrypt and stored in the backing virtual filesystem.
33
34
  */
34
- export class VirtualUserManager {
35
+ export class VirtualUserManager extends EventEmitter {
35
36
  private readonly usersPath = "/virtual-env-js/.auth/htpasswd";
36
37
  private readonly sudoersPath = "/virtual-env-js/.auth/sudoers";
37
38
  private readonly quotasPath = "/virtual-env-js/.auth/quotas";
@@ -53,7 +54,9 @@ export class VirtualUserManager {
53
54
  private readonly vfs: VirtualFileSystem,
54
55
  private readonly defaultRootPassword: string = "root",
55
56
  private readonly autoSudoForNewUsers: boolean = true,
56
- ) {}
57
+ ) {
58
+ super();
59
+ }
57
60
 
58
61
  /**
59
62
  * Loads users/sudoers from disk and ensures root account exists.
@@ -88,6 +91,7 @@ export class VirtualUserManager {
88
91
  }
89
92
 
90
93
  await this.persist();
94
+ this.emit("initialized");
91
95
  }
92
96
 
93
97
  /**
@@ -240,6 +244,7 @@ export class VirtualUserManager {
240
244
  );
241
245
  }
242
246
  await this.persist();
247
+ this.emit("user:add", { username });
243
248
  }
244
249
 
245
250
  /**
@@ -278,6 +283,7 @@ export class VirtualUserManager {
278
283
 
279
284
  this.sudoers.delete(username);
280
285
 
286
+ this.emit("user:delete", { username });
281
287
  await this.persist();
282
288
  }
283
289
 
@@ -339,8 +345,12 @@ export class VirtualUserManager {
339
345
  remoteAddress,
340
346
  startedAt: new Date().toISOString(),
341
347
  };
342
-
343
348
  this.activeSessions.set(session.id, session);
349
+ this.emit("session:register", {
350
+ sessionId: session.id,
351
+ username,
352
+ remoteAddress,
353
+ });
344
354
  return session;
345
355
  }
346
356
 
@@ -354,6 +364,14 @@ export class VirtualUserManager {
354
364
  return;
355
365
  }
356
366
 
367
+ const session = this.activeSessions.get(sessionId);
368
+ this.activeSessions.delete(sessionId);
369
+ if (session) {
370
+ this.emit("session:unregister", {
371
+ sessionId,
372
+ username: session.username,
373
+ });
374
+ }
357
375
  this.activeSessions.delete(sessionId);
358
376
  }
359
377
 
package/src/index.ts CHANGED
@@ -1,9 +1,14 @@
1
+ import { HoneyPot } from "./Honeypot";
1
2
  import { SshClient } from "./SSHClient";
2
3
  import { SftpMimic, SshMimic } from "./SSHMimic/index";
3
4
  import VirtualFileSystem from "./VirtualFileSystem";
4
5
  import { VirtualShell } from "./VirtualShell";
5
6
  import { VirtualUserManager } from "./VirtualUserManager";
6
7
 
8
+ export type {
9
+ AuditLogEntry,
10
+ HoneyPotStats,
11
+ } from "./Honeypot";
7
12
  export type {
8
13
  CommandContext,
9
14
  CommandMode,
@@ -30,6 +35,7 @@ export type {
30
35
  } from "./types/vfs";
31
36
 
32
37
  export {
38
+ HoneyPot,
33
39
  SshClient,
34
40
  VirtualFileSystem,
35
41
  SftpMimic as VirtualSftpServer,