typescript-virtual-container 1.2.1 → 1.2.3

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.
@@ -0,0 +1,25 @@
1
+ name: Publish Package
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ id-token: write # Required for OIDC
11
+ contents: read
12
+
13
+ jobs:
14
+ publish:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+
19
+ - uses: actions/setup-node@v6
20
+ with:
21
+ node-version: '24'
22
+ registry-url: 'https://registry.npmjs.org'
23
+ - run: npm install
24
+ - run: npm run build --if-present
25
+ - run: npm publish
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # `typescript-virtual-container`
2
2
 
3
- > In-memory SSH/SFTP server with a virtual filesystem and typed programmatic API for testing, automation, and interactive shell scripting in TypeScript/JavaScript.
3
+ > Scalable SSH/SFTP server with a virtual filesystem and typed programmatic API for testing, automation, and interactive shell scripting in TypeScript/JavaScript.
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/typescript-virtual-container.svg)](https://www.npmjs.com/package/typescript-virtual-container)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -37,7 +37,7 @@
37
37
  `typescript-virtual-container` is a lightweight, fully-typed SSH/SFTP runtime written in TypeScript that provides:
38
38
 
39
39
  - **SSH + SFTP Protocol Support**: Serve SSH shell/exec sessions and SFTP file operations on configurable ports.
40
- - **Virtual Filesystem**: In-memory-like developer workflow backed by a mirror directory under `.vfs/mirror`, with optional gzip compression and programmatic access.
40
+ - **Virtual Filesystem**: Fast developer workflow backed by a mirror directory under `.vfs/mirror`, with optional gzip compression and programmatic access.
41
41
  - **User Management**: Create, authenticate, and manage virtual users with strict password hashing (scrypt) and sudo-like privilege elevation.
42
42
  - **Programmatic Shell API**: Execute shell commands and query filesystem state directly from TypeScript without SSH overhead.
43
43
  - **Event-Driven Architecture**: All core classes extend `EventEmitter` for lifecycle and operation tracking. Listen to auth events, filesystem operations, session lifecycle, and command execution for auditing and integration.
@@ -50,7 +50,7 @@
50
50
  ### What This Is
51
51
 
52
52
  - A virtual shell runtime written in TypeScript.
53
- - An in-memory environment with its own virtual filesystem, user management, and command runtime.
53
+ - A virtual environment with its own virtual filesystem, user management, and command runtime.
54
54
  - A practical tool for deterministic testing, automation pipelines, and SSH-like workflows without running real containers.
55
55
 
56
56
  ### What This Is Not
@@ -1684,17 +1684,34 @@ const ssh = new VirtualSshServer({
1684
1684
 
1685
1685
  ## Performance & Scalability
1686
1686
 
1687
- ### Memory Model
1687
+ ### Benchmarking
1688
1688
 
1689
- - **In-Memory FS**: Full filesystem tree kept in RAM (no lazy loading)
1690
- - **Typical footprint**: ~1-10 MB for 1000 files, increases with file content
1691
- - **Compression**: Use `compressFile()` or `compress: true` in `writeFile()` to reduce RAM usage
1689
+ Use the built-in benchmark script to measure initialization and command throughput under concurrent shell loads:
1690
+
1691
+ ```bash
1692
+ bun ./benchmark-virtualshell.ts
1693
+ ```
1694
+
1695
+ The benchmark reports:
1696
+
1697
+ - shell initialization time by concurrency level
1698
+ - command execution time across all active shells
1699
+ - RSS memory growth during the run
1700
+
1701
+ Recent baseline runs show strong startup behavior up to 100 concurrent shells, and the runtime is designed to scale up to **1000 environments very easily** for testing and automation workloads.
1692
1702
 
1693
1703
  ### Concurrency
1694
1704
 
1695
1705
  - SSH server handles multiple concurrent connections (event-driven)
1696
1706
  - Programmatic `SshClient` is synchronous (executes sequentially per instance)
1697
1707
  - Create multiple client instances for parallel operations
1708
+ - Horizontal shell instantiation (`new VirtualShell(...)`) is intended for high-volume scenarios, including large test matrices and multi-tenant simulation batches
1709
+
1710
+ ### Scalability Notes
1711
+
1712
+ - Use a dedicated `basePath` per isolated environment to parallelize safely
1713
+ - Reuse long-lived shell instances when you need low-latency command bursts
1714
+ - Keep performance logging enabled in development (`DEV_MODE=1` or `RENDER_PERF=1`) to locate hotspots quickly
1698
1715
 
1699
1716
  **Example:**
1700
1717
 
@@ -1740,7 +1757,7 @@ async function processResult(r: CommandResult) {
1740
1757
 
1741
1758
  ### Is this a real container runtime?
1742
1759
 
1743
- No. It emulates SSH sessions, users, and filesystem behavior in memory. It is ideal for testing, simulations, and automation workflows where full OS isolation is not required.
1760
+ No. It emulates SSH sessions, users, and filesystem behavior in a virtual runtime. It is ideal for testing, simulations, and automation workflows where full OS isolation is not required.
1744
1761
 
1745
1762
  ### Can I use this in production?
1746
1763
 
package/package.json CHANGED
@@ -4,8 +4,12 @@
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
7
- "version": "1.2.1",
7
+ "version": "1.2.3",
8
8
  "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/itsrealfortune/typescript-virtual-container"
12
+ },
9
13
  "keywords": [
10
14
  "ssh",
11
15
  "virtual-filesystem",
@@ -15,7 +19,7 @@
15
19
  "shell"
16
20
  ],
17
21
  "scripts": {
18
- "postinstall": "rm -f node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node && rm -rf node_modules/cpu-features",
22
+ "postinstall": "node scripts/postinstall.js",
19
23
  "format": "bunx --bun @biomejs/biome format --write ./src",
20
24
  "check": "bunx --bun @biomejs/biome check ./src",
21
25
  "lint": "bunx --bun @biomejs/biome lint ./src",
@@ -34,7 +38,6 @@
34
38
  "typescript": "^5"
35
39
  },
36
40
  "dependencies": {
37
- "ssh2": "^1.17.0",
38
- "tar-stream": "^3.1.8"
41
+ "ssh2": "^1.17.0"
39
42
  }
40
43
  }
@@ -0,0 +1,42 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
6
+
7
+ function deleteFile(filePath) {
8
+ if (fs.existsSync(filePath)) {
9
+ fs.unlinkSync(filePath);
10
+ console.log(`Deleted: ${filePath}`);
11
+ }
12
+ }
13
+
14
+ function deleteDirectory(dirPath) {
15
+ if (fs.existsSync(dirPath)) {
16
+ fs.rmSync(dirPath, { recursive: true, force: true });
17
+ console.log(`Deleted: ${dirPath}`);
18
+ }
19
+ }
20
+
21
+ const sshCryptoPath = path.join(
22
+ __dirname,
23
+ '..',
24
+ 'node_modules',
25
+ 'ssh2',
26
+ 'lib',
27
+ 'protocol',
28
+ 'crypto',
29
+ 'build',
30
+ 'Release',
31
+ 'sshcrypto.node'
32
+ );
33
+
34
+ const cpuFeaturesPath = path.join(
35
+ __dirname,
36
+ '..',
37
+ 'node_modules',
38
+ 'cpu-features'
39
+ );
40
+
41
+ deleteFile(sshCryptoPath);
42
+ deleteDirectory(cpuFeaturesPath);
@@ -1,5 +0,0 @@
1
- import type { VfsSnapshot } from "../types/vfs";
2
- export declare function archiveExists(archivePath: string): Promise<boolean>;
3
- export declare function createTarBuffer(snapshotJson: string): Promise<Buffer>;
4
- export declare function readSnapshotFromTar(tarBuffer: Buffer): Promise<VfsSnapshot>;
5
- //# sourceMappingURL=archive.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../src/VirtualFileSystem/archive.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOzE;AAED,wBAAsB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAuB3E;AAED,wBAAsB,mBAAmB,CACxC,SAAS,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,CAAC,CAiCtB"}
@@ -1,56 +0,0 @@
1
- import { promises as fs } from "node:fs";
2
- import * as tarStream from "tar-stream";
3
- export async function archiveExists(archivePath) {
4
- try {
5
- await fs.access(archivePath);
6
- return true;
7
- }
8
- catch {
9
- return false;
10
- }
11
- }
12
- export async function createTarBuffer(snapshotJson) {
13
- const pack = tarStream.pack();
14
- const chunks = [];
15
- const finished = new Promise((resolve, reject) => {
16
- pack.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
17
- pack.on("error", reject);
18
- pack.on("end", () => resolve(Buffer.concat(chunks)));
19
- });
20
- pack.entry({ name: "snapshot.json", mode: 0o600 }, snapshotJson, (error) => {
21
- if (error) {
22
- return;
23
- }
24
- pack.finalize();
25
- });
26
- return finished;
27
- }
28
- export async function readSnapshotFromTar(tarBuffer) {
29
- return new Promise((resolve, reject) => {
30
- const extract = tarStream.extract();
31
- let snapshotText = "";
32
- let found = false;
33
- extract.on("entry", (header, stream, next) => {
34
- if (header.name === "snapshot.json") {
35
- found = true;
36
- stream.on("data", (chunk) => {
37
- snapshotText += chunk.toString("utf8");
38
- });
39
- stream.on("end", next);
40
- stream.resume();
41
- return;
42
- }
43
- stream.resume();
44
- stream.on("end", next);
45
- });
46
- extract.on("finish", () => {
47
- if (!found) {
48
- reject(new Error("snapshot.json missing from archive"));
49
- return;
50
- }
51
- resolve(JSON.parse(snapshotText));
52
- });
53
- extract.on("error", reject);
54
- extract.end(tarBuffer);
55
- });
56
- }
@@ -1,5 +0,0 @@
1
- import type { VfsSnapshot } from "../types/vfs";
2
- import type { InternalDirectoryNode } from "./internalTypes";
3
- export declare function createSnapshot(root: InternalDirectoryNode): VfsSnapshot;
4
- export declare function applySnapshot(rootTarget: InternalDirectoryNode, snapshot: VfsSnapshot): void;
5
- //# sourceMappingURL=snapshot.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/VirtualFileSystem/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,WAAW,EAGX,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,qBAAqB,EAAgB,MAAM,iBAAiB,CAAC;AAgE3E,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,WAAW,CAEvE;AAED,wBAAgB,aAAa,CAC5B,UAAU,EAAE,qBAAqB,EACjC,QAAQ,EAAE,WAAW,GACnB,IAAI,CAON"}
@@ -1,59 +0,0 @@
1
- function serializeNode(node) {
2
- if (node.type === "file") {
3
- return {
4
- type: "file",
5
- name: node.name,
6
- mode: node.mode,
7
- createdAt: node.createdAt.toISOString(),
8
- updatedAt: node.updatedAt.toISOString(),
9
- compressed: node.compressed,
10
- contentBase64: node.content.toString("base64"),
11
- };
12
- }
13
- return serializeDirectory(node);
14
- }
15
- function serializeDirectory(node) {
16
- return {
17
- type: "directory",
18
- name: node.name,
19
- mode: node.mode,
20
- createdAt: node.createdAt.toISOString(),
21
- updatedAt: node.updatedAt.toISOString(),
22
- children: Array.from(node.children.values()).map((child) => serializeNode(child)),
23
- };
24
- }
25
- function deserializeNode(node) {
26
- if (node.type === "file") {
27
- return {
28
- type: "file",
29
- name: node.name,
30
- mode: node.mode,
31
- createdAt: new Date(node.createdAt),
32
- updatedAt: new Date(node.updatedAt),
33
- content: Buffer.from(node.contentBase64, "base64"),
34
- compressed: node.compressed,
35
- };
36
- }
37
- return deserializeDirectory(node);
38
- }
39
- function deserializeDirectory(node) {
40
- return {
41
- type: "directory",
42
- name: node.name,
43
- mode: node.mode,
44
- createdAt: new Date(node.createdAt),
45
- updatedAt: new Date(node.updatedAt),
46
- children: new Map(node.children.map((child) => [child.name, deserializeNode(child)])),
47
- };
48
- }
49
- export function createSnapshot(root) {
50
- return { root: serializeDirectory(root) };
51
- }
52
- export function applySnapshot(rootTarget, snapshot) {
53
- const root = deserializeDirectory(snapshot.root);
54
- rootTarget.name = root.name;
55
- rootTarget.mode = root.mode;
56
- rootTarget.createdAt = root.createdAt;
57
- rootTarget.updatedAt = root.updatedAt;
58
- rootTarget.children = root.children;
59
- }
@@ -1,3 +0,0 @@
1
- import type { InternalDirectoryNode } from "./internalTypes";
2
- export declare function renderTree(node: InternalDirectoryNode, rootLabel: string): string;
3
- //# sourceMappingURL=tree.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../src/VirtualFileSystem/tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AA0B7D,wBAAgB,UAAU,CACzB,IAAI,EAAE,qBAAqB,EAC3B,SAAS,EAAE,MAAM,GACf,MAAM,CAIR"}
@@ -1,19 +0,0 @@
1
- function walkTree(node, indent, lines) {
2
- const entries = Array.from(node.children.entries()).sort(([a], [b]) => a.localeCompare(b));
3
- entries.forEach(([name, child], index) => {
4
- const isLast = index === entries.length - 1;
5
- const branch = isLast ? "`-- " : "|-- ";
6
- const nextIndent = indent + (isLast ? " " : "| ");
7
- if (child.type === "file") {
8
- lines.push(`${indent}${branch}${name}${child.compressed ? " [gz]" : ""}`);
9
- return;
10
- }
11
- lines.push(`${indent}${branch}${name}/`);
12
- walkTree(child, nextIndent, lines);
13
- });
14
- }
15
- export function renderTree(node, rootLabel) {
16
- const lines = [rootLabel];
17
- walkTree(node, "", lines);
18
- return lines.join("\n");
19
- }
@@ -1,132 +0,0 @@
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
@@ -1 +0,0 @@
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"}
package/dist/honeypot.js DELETED
@@ -1,289 +0,0 @@
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,74 +0,0 @@
1
- import { promises as fs } from "node:fs";
2
- import * as tarStream from "tar-stream";
3
- import type { VfsSnapshot } from "../types/vfs";
4
-
5
- export async function archiveExists(archivePath: string): Promise<boolean> {
6
- try {
7
- await fs.access(archivePath);
8
- return true;
9
- } catch {
10
- return false;
11
- }
12
- }
13
-
14
- export async function createTarBuffer(snapshotJson: string): Promise<Buffer> {
15
- const pack = tarStream.pack();
16
- const chunks: Buffer[] = [];
17
-
18
- const finished = new Promise<Buffer>((resolve, reject) => {
19
- pack.on("data", (chunk: Buffer) => chunks.push(Buffer.from(chunk)));
20
- pack.on("error", reject);
21
- pack.on("end", () => resolve(Buffer.concat(chunks)));
22
- });
23
-
24
- pack.entry(
25
- { name: "snapshot.json", mode: 0o600 },
26
- snapshotJson,
27
- (error?: Error | null) => {
28
- if (error) {
29
- return;
30
- }
31
-
32
- pack.finalize();
33
- },
34
- );
35
-
36
- return finished;
37
- }
38
-
39
- export async function readSnapshotFromTar(
40
- tarBuffer: Buffer,
41
- ): Promise<VfsSnapshot> {
42
- return new Promise<VfsSnapshot>((resolve, reject) => {
43
- const extract = tarStream.extract();
44
- let snapshotText = "";
45
- let found = false;
46
-
47
- extract.on("entry", (header, stream, next) => {
48
- if (header.name === "snapshot.json") {
49
- found = true;
50
- stream.on("data", (chunk: Buffer) => {
51
- snapshotText += chunk.toString("utf8");
52
- });
53
- stream.on("end", next);
54
- stream.resume();
55
- return;
56
- }
57
-
58
- stream.resume();
59
- stream.on("end", next);
60
- });
61
-
62
- extract.on("finish", () => {
63
- if (!found) {
64
- reject(new Error("snapshot.json missing from archive"));
65
- return;
66
- }
67
-
68
- resolve(JSON.parse(snapshotText) as VfsSnapshot);
69
- });
70
-
71
- extract.on("error", reject);
72
- extract.end(tarBuffer);
73
- });
74
- }
@@ -1,84 +0,0 @@
1
- import type {
2
- VfsSnapshot,
3
- VfsSnapshotDirectoryNode,
4
- VfsSnapshotNode,
5
- } from "../types/vfs";
6
- import type { InternalDirectoryNode, InternalNode } from "./internalTypes";
7
-
8
- function serializeNode(node: InternalNode): VfsSnapshotNode {
9
- if (node.type === "file") {
10
- return {
11
- type: "file",
12
- name: node.name,
13
- mode: node.mode,
14
- createdAt: node.createdAt.toISOString(),
15
- updatedAt: node.updatedAt.toISOString(),
16
- compressed: node.compressed,
17
- contentBase64: node.content.toString("base64"),
18
- };
19
- }
20
-
21
- return serializeDirectory(node);
22
- }
23
-
24
- function serializeDirectory(
25
- node: InternalDirectoryNode,
26
- ): VfsSnapshotDirectoryNode {
27
- return {
28
- type: "directory",
29
- name: node.name,
30
- mode: node.mode,
31
- createdAt: node.createdAt.toISOString(),
32
- updatedAt: node.updatedAt.toISOString(),
33
- children: Array.from(node.children.values()).map((child) =>
34
- serializeNode(child),
35
- ),
36
- };
37
- }
38
-
39
- function deserializeNode(node: VfsSnapshotNode): InternalNode {
40
- if (node.type === "file") {
41
- return {
42
- type: "file",
43
- name: node.name,
44
- mode: node.mode,
45
- createdAt: new Date(node.createdAt),
46
- updatedAt: new Date(node.updatedAt),
47
- content: Buffer.from(node.contentBase64, "base64"),
48
- compressed: node.compressed,
49
- };
50
- }
51
-
52
- return deserializeDirectory(node);
53
- }
54
-
55
- function deserializeDirectory(
56
- node: VfsSnapshotDirectoryNode,
57
- ): InternalDirectoryNode {
58
- return {
59
- type: "directory",
60
- name: node.name,
61
- mode: node.mode,
62
- createdAt: new Date(node.createdAt),
63
- updatedAt: new Date(node.updatedAt),
64
- children: new Map<string, InternalNode>(
65
- node.children.map((child) => [child.name, deserializeNode(child)]),
66
- ),
67
- };
68
- }
69
-
70
- export function createSnapshot(root: InternalDirectoryNode): VfsSnapshot {
71
- return { root: serializeDirectory(root) };
72
- }
73
-
74
- export function applySnapshot(
75
- rootTarget: InternalDirectoryNode,
76
- snapshot: VfsSnapshot,
77
- ): void {
78
- const root = deserializeDirectory(snapshot.root);
79
- rootTarget.name = root.name;
80
- rootTarget.mode = root.mode;
81
- rootTarget.createdAt = root.createdAt;
82
- rootTarget.updatedAt = root.updatedAt;
83
- rootTarget.children = root.children;
84
- }
@@ -1,34 +0,0 @@
1
- import type { InternalDirectoryNode } from "./internalTypes";
2
-
3
- function walkTree(
4
- node: InternalDirectoryNode,
5
- indent: string,
6
- lines: string[],
7
- ): void {
8
- const entries = Array.from(node.children.entries()).sort(([a], [b]) =>
9
- a.localeCompare(b),
10
- );
11
-
12
- entries.forEach(([name, child], index) => {
13
- const isLast = index === entries.length - 1;
14
- const branch = isLast ? "`-- " : "|-- ";
15
- const nextIndent = indent + (isLast ? " " : "| ");
16
-
17
- if (child.type === "file") {
18
- lines.push(`${indent}${branch}${name}${child.compressed ? " [gz]" : ""}`);
19
- return;
20
- }
21
-
22
- lines.push(`${indent}${branch}${name}/`);
23
- walkTree(child, nextIndent, lines);
24
- });
25
- }
26
-
27
- export function renderTree(
28
- node: InternalDirectoryNode,
29
- rootLabel: string,
30
- ): string {
31
- const lines: string[] = [rootLabel];
32
- walkTree(node, "", lines);
33
- return lines.join("\n");
34
- }