typescript-virtual-container 1.1.4 → 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.
- package/CHANGELOG.md +42 -0
- package/HONEYPOT.md +358 -0
- package/README.md +471 -16
- 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
package/dist/SSHMimic/index.js
CHANGED
|
@@ -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
|
}
|
package/dist/SSHMimic/sftp.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/SSHMimic/sftp.js
CHANGED
|
@@ -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":"
|
|
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"}
|
|
@@ -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";
|
|
@@ -9,7 +10,7 @@ import { normalizePath } from "./path";
|
|
|
9
10
|
* {@link VirtualFileSystem.restoreMirror} on startup and
|
|
10
11
|
* {@link VirtualFileSystem.flushMirror} to persist pending changes.
|
|
11
12
|
*/
|
|
12
|
-
class VirtualFileSystem {
|
|
13
|
+
class VirtualFileSystem extends EventEmitter {
|
|
13
14
|
mirrorRoot;
|
|
14
15
|
ensureMirrorRoot() {
|
|
15
16
|
fs.mkdirSync(this.mirrorRoot, { recursive: true, mode: 0o755 });
|
|
@@ -75,6 +76,7 @@ class VirtualFileSystem {
|
|
|
75
76
|
* @param baseDir Base directory used to resolve mirror archive location.
|
|
76
77
|
*/
|
|
77
78
|
constructor(baseDir = process.cwd()) {
|
|
79
|
+
super();
|
|
78
80
|
this.mirrorRoot = path.resolve(baseDir, ".vfs", "mirror");
|
|
79
81
|
}
|
|
80
82
|
/**
|
|
@@ -92,6 +94,7 @@ class VirtualFileSystem {
|
|
|
92
94
|
*/
|
|
93
95
|
async flushMirror() {
|
|
94
96
|
this.ensureMirrorRoot();
|
|
97
|
+
this.emit("mirror:flush");
|
|
95
98
|
}
|
|
96
99
|
/**
|
|
97
100
|
* Creates directory and any missing parent directories.
|
|
@@ -106,6 +109,7 @@ class VirtualFileSystem {
|
|
|
106
109
|
throw new Error(`Cannot create directory '${normalizePath(targetPath)}': path is a file.`);
|
|
107
110
|
}
|
|
108
111
|
fs.mkdirSync(fsPath, { recursive: true, mode });
|
|
112
|
+
this.emit("dir:create", { path: normalizePath(targetPath), mode });
|
|
109
113
|
}
|
|
110
114
|
/**
|
|
111
115
|
* Writes UTF-8 text or binary content into file.
|
|
@@ -132,6 +136,7 @@ class VirtualFileSystem {
|
|
|
132
136
|
}
|
|
133
137
|
fs.writeFileSync(fsPath, storedContent);
|
|
134
138
|
fs.chmodSync(fsPath, options.mode ?? 0o644);
|
|
139
|
+
this.emit("file:write", { path: normalized, size: storedContent.length });
|
|
135
140
|
}
|
|
136
141
|
/**
|
|
137
142
|
* Reads file content as UTF-8 text.
|
|
@@ -149,6 +154,8 @@ class VirtualFileSystem {
|
|
|
149
154
|
}
|
|
150
155
|
const stored = fs.readFileSync(fsPath);
|
|
151
156
|
const raw = this.detectGzipFile(fsPath) ? gunzipSync(stored) : stored;
|
|
157
|
+
const normalized = normalizePath(targetPath);
|
|
158
|
+
this.emit("file:read", { path: normalized, size: raw.length });
|
|
152
159
|
return raw.toString("utf8");
|
|
153
160
|
}
|
|
154
161
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
1
2
|
import type { CommandContext, CommandResult } from "../types/commands";
|
|
2
3
|
import type { ShellStream } from "../types/streams";
|
|
3
4
|
import VirtualFileSystem from "../VirtualFileSystem";
|
|
@@ -13,7 +14,7 @@ export interface ShellProperties {
|
|
|
13
14
|
* Instances are used both by the SSH server facade and by the programmatic
|
|
14
15
|
* client API.
|
|
15
16
|
*/
|
|
16
|
-
declare class VirtualShell {
|
|
17
|
+
declare class VirtualShell extends EventEmitter {
|
|
17
18
|
basePath: string;
|
|
18
19
|
vfs: VirtualFileSystem;
|
|
19
20
|
users: VirtualUserManager;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,iBAAiB,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACb;AA8BD;;;;;GAKG;AACH,cAAM,YAAa,SAAQ,YAAY;IACtC,QAAQ,EAAE,MAAM,CAAO;IACvB,GAAG,EAAE,iBAAiB,CAAC;IACvB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,CAAC,WAAW,CAAgB;IAEnC;;;;;;OAMG;gBAEF,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,eAAe,EAC5B,QAAQ,CAAC,EAAE,MAAM;IAyBlB;;;OAGG;IACU,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/C;;;;;;OAMG;IACH,UAAU,CACT,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAAE,EAChB,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,GACvE,IAAI;IASP;;;;;;OAMG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAKrE;;;;;;;OAOG;IAEH,uBAAuB,CACtB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAC1C,IAAI;IAeP;;;;OAIG;IACI,MAAM,IAAI,iBAAiB,GAAG,IAAI;IAIzC;;;;OAIG;IACI,QAAQ,IAAI,kBAAkB,GAAG,IAAI;IAI5C;;;;OAIG;IACI,WAAW,IAAI,MAAM;IAI5B;;;;;;OAMG;IACI,eAAe,CACrB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,GACtB,IAAI;CAIP;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -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 VirtualFileSystem from "../VirtualFileSystem";
|
|
4
5
|
import { VirtualUserManager } from "../VirtualUserManager";
|
|
@@ -30,7 +31,7 @@ function resolveAutoSudoForNewUsers() {
|
|
|
30
31
|
* Instances are used both by the SSH server facade and by the programmatic
|
|
31
32
|
* client API.
|
|
32
33
|
*/
|
|
33
|
-
class VirtualShell {
|
|
34
|
+
class VirtualShell extends EventEmitter {
|
|
34
35
|
basePath = ".";
|
|
35
36
|
vfs;
|
|
36
37
|
users;
|
|
@@ -45,6 +46,7 @@ class VirtualShell {
|
|
|
45
46
|
* @param basePath Optional base path for the virtual filesystem (defaults to process.cwd()).
|
|
46
47
|
*/
|
|
47
48
|
constructor(hostname, properties, basePath) {
|
|
49
|
+
super();
|
|
48
50
|
this.hostname = hostname;
|
|
49
51
|
this.properties = properties || defaultShellProperties;
|
|
50
52
|
this.basePath = basePath || ".";
|
|
@@ -57,6 +59,7 @@ class VirtualShell {
|
|
|
57
59
|
this.initialized = (async () => {
|
|
58
60
|
await vfs.restoreMirror();
|
|
59
61
|
await users.initialize();
|
|
62
|
+
this.emit("initialized");
|
|
60
63
|
})();
|
|
61
64
|
}
|
|
62
65
|
/**
|
|
@@ -89,6 +92,7 @@ class VirtualShell {
|
|
|
89
92
|
*/
|
|
90
93
|
executeCommand(rawInput, authUser, cwd) {
|
|
91
94
|
runCommand(rawInput, authUser, this.hostname, "shell", cwd, this);
|
|
95
|
+
this.emit("command", { command: rawInput, user: authUser, cwd });
|
|
92
96
|
}
|
|
93
97
|
/**
|
|
94
98
|
* Starts an interactive session with the shell.
|
|
@@ -100,6 +104,7 @@ class VirtualShell {
|
|
|
100
104
|
*/
|
|
101
105
|
startInteractiveSession(stream, authUser, sessionId, remoteAddress, terminalSize) {
|
|
102
106
|
// Interactive shell logic
|
|
107
|
+
this.emit("session:start", { user: authUser, sessionId, remoteAddress });
|
|
103
108
|
startShell(this.properties, stream, authUser, this.hostname, sessionId, remoteAddress, terminalSize, this);
|
|
104
109
|
}
|
|
105
110
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
1
2
|
import type VirtualFileSystem from "../VirtualFileSystem";
|
|
2
3
|
/** Persisted virtual user credential record. */
|
|
3
4
|
export interface VirtualUserRecord {
|
|
@@ -26,7 +27,7 @@ export interface VirtualActiveSession {
|
|
|
26
27
|
*
|
|
27
28
|
* Passwords are hashed with scrypt and stored in the backing virtual filesystem.
|
|
28
29
|
*/
|
|
29
|
-
export declare class VirtualUserManager {
|
|
30
|
+
export declare class VirtualUserManager extends EventEmitter {
|
|
30
31
|
private readonly vfs;
|
|
31
32
|
private readonly defaultRootPassword;
|
|
32
33
|
private readonly autoSudoForNewUsers;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualUserManager/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualUserManager/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,12 @@
|
|
|
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
|
/**
|
|
4
5
|
* Persistent user, sudoers, and active-session manager for the shell runtime.
|
|
5
6
|
*
|
|
6
7
|
* Passwords are hashed with scrypt and stored in the backing virtual filesystem.
|
|
7
8
|
*/
|
|
8
|
-
export class VirtualUserManager {
|
|
9
|
+
export class VirtualUserManager extends EventEmitter {
|
|
9
10
|
vfs;
|
|
10
11
|
defaultRootPassword;
|
|
11
12
|
autoSudoForNewUsers;
|
|
@@ -26,6 +27,7 @@ export class VirtualUserManager {
|
|
|
26
27
|
* @param autoSudoForNewUsers Whether newly created users are added to sudoers.
|
|
27
28
|
*/
|
|
28
29
|
constructor(vfs, defaultRootPassword = "root", autoSudoForNewUsers = true) {
|
|
30
|
+
super();
|
|
29
31
|
this.vfs = vfs;
|
|
30
32
|
this.defaultRootPassword = defaultRootPassword;
|
|
31
33
|
this.autoSudoForNewUsers = autoSudoForNewUsers;
|
|
@@ -55,6 +57,7 @@ export class VirtualUserManager {
|
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
await this.persist();
|
|
60
|
+
this.emit("initialized");
|
|
58
61
|
}
|
|
59
62
|
/**
|
|
60
63
|
* Sets max allowed bytes under /home/<username>.
|
|
@@ -177,6 +180,7 @@ export class VirtualUserManager {
|
|
|
177
180
|
this.vfs.writeFile(`${homePath}/README.txt`, `Welcome to the virtual environment, ${username}`);
|
|
178
181
|
}
|
|
179
182
|
await this.persist();
|
|
183
|
+
this.emit("user:add", { username });
|
|
180
184
|
}
|
|
181
185
|
/**
|
|
182
186
|
* Updates password for an existing user account.
|
|
@@ -207,6 +211,7 @@ export class VirtualUserManager {
|
|
|
207
211
|
throw new Error(`deluser: user '${username}' does not exist`);
|
|
208
212
|
}
|
|
209
213
|
this.sudoers.delete(username);
|
|
214
|
+
this.emit("user:delete", { username });
|
|
210
215
|
await this.persist();
|
|
211
216
|
}
|
|
212
217
|
/**
|
|
@@ -260,6 +265,11 @@ export class VirtualUserManager {
|
|
|
260
265
|
startedAt: new Date().toISOString(),
|
|
261
266
|
};
|
|
262
267
|
this.activeSessions.set(session.id, session);
|
|
268
|
+
this.emit("session:register", {
|
|
269
|
+
sessionId: session.id,
|
|
270
|
+
username,
|
|
271
|
+
remoteAddress,
|
|
272
|
+
});
|
|
263
273
|
return session;
|
|
264
274
|
}
|
|
265
275
|
/**
|
|
@@ -271,6 +281,14 @@ export class VirtualUserManager {
|
|
|
271
281
|
if (!sessionId) {
|
|
272
282
|
return;
|
|
273
283
|
}
|
|
284
|
+
const session = this.activeSessions.get(sessionId);
|
|
285
|
+
this.activeSessions.delete(sessionId);
|
|
286
|
+
if (session) {
|
|
287
|
+
this.emit("session:unregister", {
|
|
288
|
+
sessionId,
|
|
289
|
+
username: session.username,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
274
292
|
this.activeSessions.delete(sessionId);
|
|
275
293
|
}
|
|
276
294
|
/**
|
|
@@ -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 { VirtualShell } from "./VirtualShell";
|
|
11
|
+
import type VirtualFileSystem from "./VirtualFileSystem";
|
|
12
|
+
import type { VirtualUserManager } from "./VirtualUserManager";
|
|
13
|
+
import type { SshMimic } from "./SSHMimic";
|
|
14
|
+
import type { SftpMimic } from "./SSHMimic/sftp";
|
|
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=honeypot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"honeypot.d.ts","sourceRoot":"","sources":["../src/honeypot.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,iBAAiB,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;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"}
|