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,10 +1,8 @@
1
- import { promises as fs } from "node:fs";
1
+ import { EventEmitter } from "node:events";
2
+ import * as fs from "node:fs";
2
3
  import * as path from "node:path";
3
4
  import { gunzipSync, gzipSync } from "node:zlib";
4
- import { archiveExists, createTarBuffer, readSnapshotFromTar } from "./archive";
5
- import { getNode, getParentDirectory, normalizePath, splitPath } from "./path";
6
- import { applySnapshot, createSnapshot } from "./snapshot";
7
- import { renderTree } from "./tree";
5
+ import { normalizePath } from "./path";
8
6
  /**
9
7
  * In-memory virtual filesystem with tar.gz mirror persistence.
10
8
  *
@@ -12,36 +10,74 @@ import { renderTree } from "./tree";
12
10
  * {@link VirtualFileSystem.restoreMirror} on startup and
13
11
  * {@link VirtualFileSystem.flushMirror} to persist pending changes.
14
12
  */
15
- class VirtualFileSystem {
16
- root;
17
- archivePath;
18
- dirty = false;
19
- computeNodeUsageBytes(node) {
20
- if (node.type === "file") {
21
- return node.content.length;
13
+ class VirtualFileSystem extends EventEmitter {
14
+ mirrorRoot;
15
+ ensureMirrorRoot() {
16
+ fs.mkdirSync(this.mirrorRoot, { recursive: true, mode: 0o755 });
17
+ }
18
+ resolveFsPath(targetPath) {
19
+ const normalized = normalizePath(targetPath);
20
+ const relativePath = normalized.slice(1);
21
+ const resolved = path.resolve(this.mirrorRoot, relativePath || ".");
22
+ const relative = path.relative(this.mirrorRoot, resolved);
23
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
24
+ throw new Error(`Invalid path '${targetPath}'.`);
25
+ }
26
+ return resolved;
27
+ }
28
+ detectGzipFile(targetPath) {
29
+ const fd = fs.openSync(targetPath, "r");
30
+ try {
31
+ const header = Buffer.alloc(2);
32
+ const bytesRead = fs.readSync(fd, header, 0, 2, 0);
33
+ return bytesRead === 2 && header[0] === 0x1f && header[1] === 0x8b;
34
+ }
35
+ finally {
36
+ fs.closeSync(fd);
37
+ }
38
+ }
39
+ computeDiskUsageBytes(targetPath) {
40
+ const stats = fs.statSync(targetPath);
41
+ if (stats.isFile()) {
42
+ return stats.size;
22
43
  }
23
44
  let total = 0;
24
- for (const child of node.children.values()) {
25
- total += this.computeNodeUsageBytes(child);
45
+ for (const entry of fs.readdirSync(targetPath)) {
46
+ total += this.computeDiskUsageBytes(path.join(targetPath, entry));
26
47
  }
27
48
  return total;
28
49
  }
50
+ renderTreeLines(targetPath, label) {
51
+ const lines = [label];
52
+ const walk = (currentPath, prefix) => {
53
+ const entries = fs
54
+ .readdirSync(currentPath, { withFileTypes: true })
55
+ .map((entry) => entry.name)
56
+ .sort((left, right) => left.localeCompare(right));
57
+ for (let i = 0; i < entries.length; i += 1) {
58
+ const name = entries[i];
59
+ const isLast = i === entries.length - 1;
60
+ const connector = isLast ? "└── " : "├── ";
61
+ const nextPrefix = `${prefix}${isLast ? " " : "│ "}`;
62
+ const entryPath = path.join(currentPath, name);
63
+ const isDirectory = fs.statSync(entryPath).isDirectory();
64
+ lines.push(`${prefix}${connector}${name}`);
65
+ if (isDirectory) {
66
+ walk(entryPath, nextPrefix);
67
+ }
68
+ }
69
+ };
70
+ walk(targetPath, "");
71
+ return lines.join("\n");
72
+ }
29
73
  /**
30
74
  * Creates a virtual filesystem instance.
31
75
  *
32
76
  * @param baseDir Base directory used to resolve mirror archive location.
33
77
  */
34
78
  constructor(baseDir = process.cwd()) {
35
- const now = new Date();
36
- this.archivePath = path.resolve(baseDir, ".vfs", "mirror.tar.gz");
37
- this.root = {
38
- type: "directory",
39
- name: "",
40
- mode: 0o755,
41
- createdAt: now,
42
- updatedAt: now,
43
- children: new Map(),
44
- };
79
+ super();
80
+ this.mirrorRoot = path.resolve(baseDir, ".vfs", "mirror");
45
81
  }
46
82
  /**
47
83
  * Restores filesystem state from mirror archive.
@@ -49,19 +85,7 @@ class VirtualFileSystem {
49
85
  * If archive does not exist or cannot be read, creates fresh mirror file.
50
86
  */
51
87
  async restoreMirror() {
52
- await fs.mkdir(path.dirname(this.archivePath), { recursive: true });
53
- try {
54
- const compressed = await fs.readFile(this.archivePath);
55
- const tarBuffer = gunzipSync(compressed);
56
- const snapshot = await readSnapshotFromTar(tarBuffer);
57
- applySnapshot(this.root, snapshot);
58
- this.dirty = false;
59
- return;
60
- }
61
- catch {
62
- console.warn(`No valid mirror archive found at '${this.archivePath}'. Starting with empty filesystem.`);
63
- await this.flushMirror();
64
- }
88
+ this.ensureMirrorRoot();
65
89
  }
66
90
  /**
67
91
  * Persists current filesystem state to mirror archive.
@@ -69,15 +93,8 @@ class VirtualFileSystem {
69
93
  * No-op when nothing changed and archive already exists.
70
94
  */
71
95
  async flushMirror() {
72
- if (!this.dirty && (await archiveExists(this.archivePath))) {
73
- return;
74
- }
75
- await fs.mkdir(path.dirname(this.archivePath), { recursive: true });
76
- const snapshotJson = JSON.stringify(createSnapshot(this.root), null, 2);
77
- const tarBuffer = await createTarBuffer(snapshotJson);
78
- const compressed = gzipSync(tarBuffer);
79
- await fs.writeFile(this.archivePath, compressed);
80
- this.dirty = false;
96
+ this.ensureMirrorRoot();
97
+ this.emit("mirror:flush");
81
98
  }
82
99
  /**
83
100
  * Creates directory and any missing parent directories.
@@ -86,32 +103,13 @@ class VirtualFileSystem {
86
103
  * @param mode POSIX-like mode bits for new directories.
87
104
  */
88
105
  mkdir(targetPath, mode = 0o755) {
89
- const normalized = normalizePath(targetPath);
90
- const parts = splitPath(normalized);
91
- let current = this.root;
92
- for (const part of parts) {
93
- const existing = current.children.get(part);
94
- if (!existing) {
95
- const now = new Date();
96
- const nextDir = {
97
- type: "directory",
98
- name: part,
99
- mode,
100
- createdAt: now,
101
- updatedAt: now,
102
- children: new Map(),
103
- };
104
- current.children.set(part, nextDir);
105
- current.updatedAt = now;
106
- this.dirty = true;
107
- current = nextDir;
108
- continue;
109
- }
110
- if (existing.type !== "directory") {
111
- throw new Error(`Cannot create directory '${normalized}': '${part}' is a file.`);
112
- }
113
- current = existing;
106
+ this.ensureMirrorRoot();
107
+ const fsPath = this.resolveFsPath(targetPath);
108
+ if (fs.existsSync(fsPath) && !fs.statSync(fsPath).isDirectory()) {
109
+ throw new Error(`Cannot create directory '${normalizePath(targetPath)}': path is a file.`);
114
110
  }
111
+ fs.mkdirSync(fsPath, { recursive: true, mode });
112
+ this.emit("dir:create", { path: normalizePath(targetPath), mode });
115
113
  }
116
114
  /**
117
115
  * Writes UTF-8 text or binary content into file.
@@ -123,31 +121,22 @@ class VirtualFileSystem {
123
121
  * @param options Optional write behavior (mode, compression).
124
122
  */
125
123
  writeFile(targetPath, content, options = {}) {
124
+ this.ensureMirrorRoot();
126
125
  const normalized = normalizePath(targetPath);
127
- const { parent, name } = getParentDirectory(this.root, normalized, true, (pathToCreate) => this.mkdir(pathToCreate));
128
- const now = new Date();
126
+ const fsPath = this.resolveFsPath(normalized);
127
+ const parentPath = path.dirname(fsPath);
128
+ fs.mkdirSync(parentPath, { recursive: true, mode: 0o755 });
129
129
  const rawContent = Buffer.isBuffer(content)
130
130
  ? content
131
131
  : Buffer.from(content, "utf8");
132
132
  const shouldCompress = options.compress ?? false;
133
133
  const storedContent = shouldCompress ? gzipSync(rawContent) : rawContent;
134
- const existing = parent.children.get(name);
135
- if (existing && existing.type === "directory") {
134
+ if (fs.existsSync(fsPath) && fs.statSync(fsPath).isDirectory()) {
136
135
  throw new Error(`Cannot write file '${normalized}': path is a directory.`);
137
136
  }
138
- const createdAt = existing?.type === "file" ? existing.createdAt : now;
139
- const mode = options.mode ?? (existing?.type === "file" ? existing.mode : 0o644);
140
- parent.children.set(name, {
141
- type: "file",
142
- name,
143
- mode,
144
- createdAt,
145
- updatedAt: now,
146
- content: storedContent,
147
- compressed: shouldCompress,
148
- });
149
- parent.updatedAt = now;
150
- this.dirty = true;
137
+ fs.writeFileSync(fsPath, storedContent);
138
+ fs.chmodSync(fsPath, options.mode ?? 0o644);
139
+ this.emit("file:write", { path: normalized, size: storedContent.length });
151
140
  }
152
141
  /**
153
142
  * Reads file content as UTF-8 text.
@@ -158,11 +147,15 @@ class VirtualFileSystem {
158
147
  * @returns UTF-8 string content.
159
148
  */
160
149
  readFile(targetPath) {
161
- const node = getNode(this.root, targetPath);
162
- if (node.type !== "file") {
150
+ this.ensureMirrorRoot();
151
+ const fsPath = this.resolveFsPath(targetPath);
152
+ if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isFile()) {
163
153
  throw new Error(`Cannot read '${targetPath}': not a file.`);
164
154
  }
165
- const raw = node.compressed ? gunzipSync(node.content) : node.content;
155
+ const stored = fs.readFileSync(fsPath);
156
+ const raw = this.detectGzipFile(fsPath) ? gunzipSync(stored) : stored;
157
+ const normalized = normalizePath(targetPath);
158
+ this.emit("file:read", { path: normalized, size: raw.length });
166
159
  return raw.toString("utf8");
167
160
  }
168
161
  /**
@@ -173,8 +166,8 @@ class VirtualFileSystem {
173
166
  */
174
167
  exists(targetPath) {
175
168
  try {
176
- getNode(this.root, targetPath);
177
- return true;
169
+ const fsPath = this.resolveFsPath(targetPath);
170
+ return fs.existsSync(fsPath);
178
171
  }
179
172
  catch {
180
173
  return false;
@@ -187,10 +180,11 @@ class VirtualFileSystem {
187
180
  * @param mode New POSIX-like mode.
188
181
  */
189
182
  chmod(targetPath, mode) {
190
- const node = getNode(this.root, targetPath);
191
- node.mode = mode;
192
- node.updatedAt = new Date();
193
- this.dirty = true;
183
+ const fsPath = this.resolveFsPath(targetPath);
184
+ if (!fs.existsSync(fsPath)) {
185
+ throw new Error(`Path '${normalizePath(targetPath)}' does not exist.`);
186
+ }
187
+ fs.chmodSync(fsPath, mode);
194
188
  }
195
189
  /**
196
190
  * Returns metadata for file or directory.
@@ -199,28 +193,35 @@ class VirtualFileSystem {
199
193
  * @returns Typed stat object based on node type.
200
194
  */
201
195
  stat(targetPath) {
196
+ this.ensureMirrorRoot();
202
197
  const normalized = normalizePath(targetPath);
203
- const node = getNode(this.root, normalized);
204
- if (node.type === "file") {
198
+ const fsPath = this.resolveFsPath(normalized);
199
+ if (!fs.existsSync(fsPath)) {
200
+ throw new Error(`Path '${normalized}' does not exist.`);
201
+ }
202
+ const stats = fs.statSync(fsPath);
203
+ const mode = stats.mode & 0o777;
204
+ const name = normalized === "/" ? "" : path.posix.basename(normalized);
205
+ if (stats.isFile()) {
205
206
  return {
206
207
  type: "file",
207
- name: node.name,
208
+ name,
208
209
  path: normalized,
209
- mode: node.mode,
210
- createdAt: node.createdAt,
211
- updatedAt: node.updatedAt,
212
- compressed: node.compressed,
213
- size: node.content.length,
210
+ mode,
211
+ createdAt: stats.birthtime,
212
+ updatedAt: stats.mtime,
213
+ compressed: this.detectGzipFile(fsPath),
214
+ size: stats.size,
214
215
  };
215
216
  }
216
217
  return {
217
218
  type: "directory",
218
- name: node.name,
219
+ name,
219
220
  path: normalized,
220
- mode: node.mode,
221
- createdAt: node.createdAt,
222
- updatedAt: node.updatedAt,
223
- childrenCount: node.children.size,
221
+ mode,
222
+ createdAt: stats.birthtime,
223
+ updatedAt: stats.mtime,
224
+ childrenCount: fs.readdirSync(fsPath).length,
224
225
  };
225
226
  }
226
227
  /**
@@ -230,11 +231,11 @@ class VirtualFileSystem {
230
231
  * @returns Sorted child names.
231
232
  */
232
233
  list(dirPath = "/") {
233
- const node = getNode(this.root, dirPath);
234
- if (node.type !== "directory") {
234
+ const fsPath = this.resolveFsPath(dirPath);
235
+ if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isDirectory()) {
235
236
  throw new Error(`Cannot list '${dirPath}': not a directory.`);
236
237
  }
237
- return Array.from(node.children.keys()).sort();
238
+ return fs.readdirSync(fsPath).sort();
238
239
  }
239
240
  /**
240
241
  * Renders ASCII tree view of directory hierarchy.
@@ -243,12 +244,12 @@ class VirtualFileSystem {
243
244
  * @returns Multi-line tree string.
244
245
  */
245
246
  tree(dirPath = "/") {
246
- const node = getNode(this.root, dirPath);
247
- if (node.type !== "directory") {
247
+ const fsPath = this.resolveFsPath(dirPath);
248
+ if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isDirectory()) {
248
249
  throw new Error(`Cannot render tree for '${dirPath}': not a directory.`);
249
250
  }
250
251
  const rootLabel = dirPath === "/" ? "/" : path.posix.basename(normalizePath(dirPath));
251
- return renderTree(node, rootLabel);
252
+ return this.renderTreeLines(fsPath, rootLabel);
252
253
  }
253
254
  /**
254
255
  * Computes total stored file bytes under a path.
@@ -260,8 +261,11 @@ class VirtualFileSystem {
260
261
  * @returns Total byte usage for file content under target path.
261
262
  */
262
263
  getUsageBytes(targetPath = "/") {
263
- const node = getNode(this.root, targetPath);
264
- return this.computeNodeUsageBytes(node);
264
+ const fsPath = this.resolveFsPath(targetPath);
265
+ if (!fs.existsSync(fsPath)) {
266
+ throw new Error(`Path '${normalizePath(targetPath)}' does not exist.`);
267
+ }
268
+ return this.computeDiskUsageBytes(fsPath);
265
269
  }
266
270
  /**
267
271
  * Compresses file content with gzip and flags node as compressed.
@@ -269,15 +273,13 @@ class VirtualFileSystem {
269
273
  * @param targetPath Path to file.
270
274
  */
271
275
  compressFile(targetPath) {
272
- const node = getNode(this.root, targetPath);
273
- if (node.type !== "file") {
276
+ const fsPath = this.resolveFsPath(targetPath);
277
+ if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isFile()) {
274
278
  throw new Error(`Cannot compress '${targetPath}': not a file.`);
275
279
  }
276
- if (!node.compressed) {
277
- node.content = gzipSync(node.content);
278
- node.compressed = true;
279
- node.updatedAt = new Date();
280
- this.dirty = true;
280
+ if (!this.detectGzipFile(fsPath)) {
281
+ const content = fs.readFileSync(fsPath);
282
+ fs.writeFileSync(fsPath, gzipSync(content));
281
283
  }
282
284
  }
283
285
  /**
@@ -286,15 +288,13 @@ class VirtualFileSystem {
286
288
  * @param targetPath Path to file.
287
289
  */
288
290
  decompressFile(targetPath) {
289
- const node = getNode(this.root, targetPath);
290
- if (node.type !== "file") {
291
+ const fsPath = this.resolveFsPath(targetPath);
292
+ if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isFile()) {
291
293
  throw new Error(`Cannot decompress '${targetPath}': not a file.`);
292
294
  }
293
- if (node.compressed) {
294
- node.content = gunzipSync(node.content);
295
- node.compressed = false;
296
- node.updatedAt = new Date();
297
- this.dirty = true;
295
+ if (this.detectGzipFile(fsPath)) {
296
+ const content = fs.readFileSync(fsPath);
297
+ fs.writeFileSync(fsPath, gunzipSync(content));
298
298
  }
299
299
  }
300
300
  /**
@@ -308,19 +308,23 @@ class VirtualFileSystem {
308
308
  if (normalized === "/") {
309
309
  throw new Error("Cannot remove root directory.");
310
310
  }
311
- const { parent, name } = getParentDirectory(this.root, normalized, false, () => undefined);
312
- const node = parent.children.get(name);
313
- if (!node) {
311
+ const fsPath = this.resolveFsPath(normalized);
312
+ if (!fs.existsSync(fsPath)) {
314
313
  throw new Error(`Path '${normalized}' does not exist.`);
315
314
  }
316
- if (node.type === "directory" &&
317
- node.children.size > 0 &&
318
- !options.recursive) {
319
- throw new Error(`Directory '${normalized}' is not empty. Use recursive option.`);
315
+ const stats = fs.statSync(fsPath);
316
+ if (stats.isDirectory() && !options.recursive) {
317
+ const entries = fs.readdirSync(fsPath);
318
+ if (entries.length > 0) {
319
+ throw new Error(`Directory '${normalized}' is not empty. Use recursive option.`);
320
+ }
321
+ }
322
+ if (stats.isDirectory()) {
323
+ fs.rmSync(fsPath, { recursive: options.recursive ?? false });
324
+ }
325
+ else {
326
+ fs.rmSync(fsPath);
320
327
  }
321
- parent.children.delete(name);
322
- parent.updatedAt = new Date();
323
- this.dirty = true;
324
328
  }
325
329
  /**
326
330
  * Moves or renames node to destination path.
@@ -334,22 +338,16 @@ class VirtualFileSystem {
334
338
  if (fromNormalized === "/" || toNormalized === "/") {
335
339
  throw new Error("Cannot move root directory.");
336
340
  }
337
- const { parent: fromParent, name: fromName } = getParentDirectory(this.root, fromNormalized, false, () => undefined);
338
- const node = fromParent.children.get(fromName);
339
- if (!node) {
341
+ const fromFsPath = this.resolveFsPath(fromNormalized);
342
+ const toFsPath = this.resolveFsPath(toNormalized);
343
+ if (!fs.existsSync(fromFsPath)) {
340
344
  throw new Error(`Path '${fromNormalized}' does not exist.`);
341
345
  }
342
- const { parent: toParent, name: toName } = getParentDirectory(this.root, toNormalized, true, (pathToCreate) => this.mkdir(pathToCreate));
343
- if (toParent.children.has(toName)) {
346
+ if (fs.existsSync(toFsPath)) {
344
347
  throw new Error(`Destination '${toNormalized}' already exists.`);
345
348
  }
346
- fromParent.children.delete(fromName);
347
- node.name = toName;
348
- node.updatedAt = new Date();
349
- toParent.children.set(toName, node);
350
- fromParent.updatedAt = new Date();
351
- toParent.updatedAt = new Date();
352
- this.dirty = true;
349
+ fs.mkdirSync(path.dirname(toFsPath), { recursive: true, mode: 0o755 });
350
+ fs.renameSync(fromFsPath, toFsPath);
353
351
  }
354
352
  }
355
353
  export default VirtualFileSystem;
@@ -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,12 +14,13 @@ 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;
20
21
  hostname: string;
21
22
  properties: ShellProperties;
23
+ private initialized;
22
24
  /**
23
25
  * Creates a new virtual shell instance.
24
26
  *
@@ -27,6 +29,11 @@ declare class VirtualShell {
27
29
  * @param basePath Optional base path for the virtual filesystem (defaults to process.cwd()).
28
30
  */
29
31
  constructor(hostname: string, properties?: ShellProperties, basePath?: string);
32
+ /**
33
+ * Ensures initialization is complete before allowing operations.
34
+ * Call this before any authentication or command execution.
35
+ */
36
+ ensureInitialized(): Promise<void>;
30
37
  /**
31
38
  * Registers a new command in the shell runtime.
32
39
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/index.ts"],"names":[],"mappings":"AAEA,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,YAAY;IACjB,QAAQ,EAAE,MAAM,CAAO;IACvB,GAAG,EAAE,iBAAiB,CAAC;IACvB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,eAAe,CAAC;IAE5B;;;;;;OAMG;gBAEF,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,eAAe,EAC5B,QAAQ,CAAC,EAAE,MAAM;IAqBlB;;;;;;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;IAIrE;;;;;;;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;IAcP;;;;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
+ {"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,12 +31,13 @@ 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;
37
38
  hostname;
38
39
  properties;
40
+ initialized;
39
41
  /**
40
42
  * Creates a new virtual shell instance.
41
43
  *
@@ -44,15 +46,28 @@ class VirtualShell {
44
46
  * @param basePath Optional base path for the virtual filesystem (defaults to process.cwd()).
45
47
  */
46
48
  constructor(hostname, properties, basePath) {
49
+ super();
47
50
  this.hostname = hostname;
48
51
  this.properties = properties || defaultShellProperties;
49
52
  this.basePath = basePath || ".";
50
53
  this.vfs = new VirtualFileSystem(this.basePath);
51
54
  this.users = new VirtualUserManager(this.vfs, resolveRootPassword(), resolveAutoSudoForNewUsers());
52
- this.vfs.restoreMirror().then(() => {
53
- this.users = new VirtualUserManager(this.vfs, resolveRootPassword(), resolveAutoSudoForNewUsers());
54
- this.users.initialize();
55
- });
55
+ // Store references to avoid TypeScript "used before assigned" errors
56
+ const vfs = this.vfs;
57
+ const users = this.users;
58
+ // Initialize both VFS mirror and users, ensuring all is ready before auth
59
+ this.initialized = (async () => {
60
+ await vfs.restoreMirror();
61
+ await users.initialize();
62
+ this.emit("initialized");
63
+ })();
64
+ }
65
+ /**
66
+ * Ensures initialization is complete before allowing operations.
67
+ * Call this before any authentication or command execution.
68
+ */
69
+ async ensureInitialized() {
70
+ await this.initialized;
56
71
  }
57
72
  /**
58
73
  * Registers a new command in the shell runtime.
@@ -77,6 +92,7 @@ class VirtualShell {
77
92
  */
78
93
  executeCommand(rawInput, authUser, cwd) {
79
94
  runCommand(rawInput, authUser, this.hostname, "shell", cwd, this);
95
+ this.emit("command", { command: rawInput, user: authUser, cwd });
80
96
  }
81
97
  /**
82
98
  * Starts an interactive session with the shell.
@@ -88,6 +104,7 @@ class VirtualShell {
88
104
  */
89
105
  startInteractiveSession(stream, authUser, sessionId, remoteAddress, terminalSize) {
90
106
  // Interactive shell logic
107
+ this.emit("session:start", { user: authUser, sessionId, remoteAddress });
91
108
  startShell(this.properties, stream, authUser, this.hostname, sessionId, remoteAddress, terminalSize, this);
92
109
  }
93
110
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/shell.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AAMvD,OAAO,EAGN,KAAK,YAAY,EAEjB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAmBpD,wBAAgB,UAAU,CACzB,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,oBAAY,EACzB,YAAY,EAAE,YAAY,YAAyB,EACnD,KAAK,EAAE,YAAY,GACjB,IAAI,CAulBN"}
1
+ {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/shell.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AAMvD,OAAO,EAGN,KAAK,YAAY,EAEjB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAmBpD,wBAAgB,UAAU,CACzB,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,oBAAY,EACzB,YAAY,EAAE,YAAY,YAAyB,EACnD,KAAK,EAAE,YAAY,GACjB,IAAI,CA8lBN"}
@@ -312,6 +312,13 @@ export function startShell(properties, stream, authUser, hostname, sessionId, re
312
312
  for (let i = 0; i < input.length; i += 1) {
313
313
  const ch = input[i];
314
314
  if (ch === "\u0004") {
315
+ lineBuffer = "";
316
+ cursorPos = 0;
317
+ historyIndex = null;
318
+ historyDraft = "";
319
+ stream.write("bye\r\n");
320
+ pushHistory("bye");
321
+ await shell.vfs.flushMirror();
315
322
  stream.write("logout\r\n");
316
323
  stream.exit(0);
317
324
  stream.end();
@@ -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;
@@ -49,6 +50,7 @@ export declare class VirtualUserManager {
49
50
  constructor(vfs: VirtualFileSystem, defaultRootPassword?: string, autoSudoForNewUsers?: boolean);
50
51
  /**
51
52
  * Loads users/sudoers from disk and ensures root account exists.
53
+ * Also creates the current system user if not already present.
52
54
  */
53
55
  initialize(): Promise<void>;
54
56
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualUserManager/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualUserManager/index.ts"],"names":[],"mappings":"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"}