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.
- package/CHANGELOG.md +42 -0
- package/HONEYPOT.md +358 -0
- package/README.md +471 -16
- package/dist/Honeypot/index.d.ts +132 -0
- package/dist/Honeypot/index.d.ts.map +1 -0
- package/dist/Honeypot/index.js +289 -0
- package/dist/SSHMimic/index.d.ts +2 -1
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +12 -1
- package/dist/SSHMimic/sftp.d.ts +3 -1
- package/dist/SSHMimic/sftp.d.ts.map +1 -1
- package/dist/SSHMimic/sftp.js +20 -1
- package/dist/VirtualFileSystem/index.d.ts +2 -1
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +8 -1
- package/dist/VirtualShell/index.d.ts +2 -1
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +6 -1
- package/dist/VirtualUserManager/index.d.ts +2 -1
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.js +19 -1
- package/dist/honeypot.d.ts +132 -0
- package/dist/honeypot.d.ts.map +1 -0
- package/dist/honeypot.js +289 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/examples/README.md +210 -0
- package/examples/honeypot-audit.ts +180 -0
- package/examples/honeypot-export.ts +253 -0
- package/examples/honeypot-quickstart.ts +110 -0
- package/package.json +1 -1
- package/src/Honeypot/index.ts +422 -0
- package/src/SSHMimic/index.ts +13 -1
- package/src/SSHMimic/sftp.ts +21 -1
- package/src/VirtualFileSystem/index.ts +8 -1
- package/src/VirtualShell/index.ts +6 -1
- package/src/VirtualUserManager/index.ts +21 -3
- 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,
|