typescript-virtual-container 1.2.9 → 1.3.1
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/.vscode/settings.json +0 -1
- package/README.md +141 -50
- package/biome.json +7 -0
- package/dist/SSHMimic/exec.d.ts.map +1 -1
- package/dist/SSHMimic/executor.d.ts.map +1 -1
- package/dist/SSHMimic/executor.js +32 -16
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +20 -6
- package/dist/VirtualFileSystem/binaryPack.d.ts.map +1 -1
- package/dist/VirtualFileSystem/binaryPack.js +29 -6
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +36 -13
- package/dist/VirtualPackageManager/index.d.ts.map +1 -1
- package/dist/VirtualPackageManager/index.js +192 -43
- package/dist/VirtualShell/index.d.ts +10 -4
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +18 -7
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +3 -1
- package/dist/VirtualShell/shellParser.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/commands/adduser.d.ts +6 -0
- package/dist/commands/adduser.d.ts.map +1 -1
- package/dist/commands/adduser.js +6 -0
- package/dist/commands/alias.d.ts +5 -0
- package/dist/commands/alias.d.ts.map +1 -1
- package/dist/commands/alias.js +5 -0
- package/dist/commands/apt.d.ts +5 -0
- package/dist/commands/apt.d.ts.map +1 -1
- package/dist/commands/apt.js +32 -9
- package/dist/commands/awk.d.ts +11 -0
- package/dist/commands/awk.d.ts.map +1 -1
- package/dist/commands/awk.js +15 -2
- package/dist/commands/base64.d.ts +5 -0
- package/dist/commands/base64.d.ts.map +1 -1
- package/dist/commands/base64.js +9 -1
- package/dist/commands/cat.d.ts +5 -0
- package/dist/commands/cat.d.ts.map +1 -1
- package/dist/commands/cat.js +10 -2
- package/dist/commands/cd.d.ts +5 -0
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +5 -0
- package/dist/commands/chmod.d.ts +5 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +5 -0
- package/dist/commands/cp.d.ts +5 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +5 -0
- package/dist/commands/curl.d.ts +5 -0
- package/dist/commands/curl.d.ts.map +1 -1
- package/dist/commands/curl.js +34 -6
- package/dist/commands/cut.d.ts +5 -0
- package/dist/commands/cut.d.ts.map +1 -1
- package/dist/commands/cut.js +8 -1
- package/dist/commands/date.d.ts +5 -0
- package/dist/commands/date.d.ts.map +1 -1
- package/dist/commands/date.js +7 -1
- package/dist/commands/declare.d.ts +3 -0
- package/dist/commands/declare.d.ts.map +1 -0
- package/dist/commands/declare.js +39 -0
- package/dist/commands/diff.d.ts +5 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +5 -0
- package/dist/commands/dpkg.d.ts +5 -0
- package/dist/commands/dpkg.d.ts.map +1 -1
- package/dist/commands/dpkg.js +24 -7
- package/dist/commands/du.d.ts.map +1 -1
- package/dist/commands/du.js +8 -2
- package/dist/commands/echo.d.ts +5 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +13 -4
- package/dist/commands/env.d.ts +5 -0
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +11 -1
- package/dist/commands/exit.d.ts +5 -0
- package/dist/commands/exit.d.ts.map +1 -1
- package/dist/commands/exit.js +12 -2
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +3 -1
- package/dist/commands/find.d.ts +5 -0
- package/dist/commands/find.d.ts.map +1 -1
- package/dist/commands/find.js +5 -0
- package/dist/commands/free.d.ts +5 -0
- package/dist/commands/free.d.ts.map +1 -1
- package/dist/commands/free.js +5 -0
- package/dist/commands/grep.d.ts +5 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +12 -2
- package/dist/commands/gzip.d.ts +5 -0
- package/dist/commands/gzip.d.ts.map +1 -1
- package/dist/commands/gzip.js +18 -2
- package/dist/commands/head.d.ts +5 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +5 -0
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +98 -45
- package/dist/commands/history.d.ts +5 -0
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +5 -0
- package/dist/commands/hostname.d.ts +5 -0
- package/dist/commands/hostname.d.ts.map +1 -1
- package/dist/commands/hostname.js +5 -0
- package/dist/commands/id.d.ts.map +1 -1
- package/dist/commands/id.js +4 -1
- package/dist/commands/index.d.ts +2 -17
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -340
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +3 -1
- package/dist/commands/lsb-release.d.ts.map +1 -1
- package/dist/commands/lsb-release.js +8 -2
- package/dist/commands/nano.js +1 -1
- package/dist/commands/neofetch.js +1 -1
- package/dist/commands/node.d.ts +9 -0
- package/dist/commands/node.d.ts.map +1 -0
- package/dist/commands/node.js +316 -0
- package/dist/commands/npm.d.ts +19 -0
- package/dist/commands/npm.d.ts.map +1 -0
- package/dist/commands/npm.js +109 -0
- package/dist/commands/ping.d.ts.map +1 -1
- package/dist/commands/ping.js +3 -1
- package/dist/commands/printf.d.ts +3 -0
- package/dist/commands/printf.d.ts.map +1 -0
- package/dist/commands/printf.js +113 -0
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +4 -1
- package/dist/commands/python.d.ts +30 -0
- package/dist/commands/python.d.ts.map +1 -0
- package/dist/commands/python.js +2058 -0
- package/dist/commands/read.d.ts +3 -0
- package/dist/commands/read.d.ts.map +1 -0
- package/dist/commands/read.js +34 -0
- package/dist/commands/registry.d.ts +8 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +229 -0
- package/dist/commands/runtime.d.ts +6 -0
- package/dist/commands/runtime.d.ts.map +1 -0
- package/dist/commands/runtime.js +280 -0
- package/dist/commands/sed.d.ts.map +1 -1
- package/dist/commands/sed.js +11 -3
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +9 -3
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +57 -36
- package/dist/commands/shift.d.ts +5 -0
- package/dist/commands/shift.d.ts.map +1 -0
- package/dist/commands/shift.js +52 -0
- package/dist/commands/sleep.d.ts.map +1 -1
- package/dist/commands/sort.d.ts.map +1 -1
- package/dist/commands/sort.js +4 -2
- package/dist/commands/source.d.ts.map +1 -1
- package/dist/commands/source.js +5 -2
- package/dist/commands/sudo.js +1 -1
- package/dist/commands/tar.d.ts.map +1 -1
- package/dist/commands/tar.js +11 -3
- package/dist/commands/tee.d.ts.map +1 -1
- package/dist/commands/tee.js +8 -6
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +46 -24
- package/dist/commands/tr.d.ts.map +1 -1
- package/dist/commands/tr.js +3 -1
- package/dist/commands/true.d.ts +4 -0
- package/dist/commands/true.d.ts.map +1 -0
- package/dist/commands/true.js +14 -0
- package/dist/commands/type.d.ts.map +1 -1
- package/dist/commands/type.js +1 -1
- package/dist/commands/uname.d.ts.map +1 -1
- package/dist/commands/uname.js +4 -1
- package/dist/commands/uniq.d.ts.map +1 -1
- package/dist/commands/uptime.d.ts.map +1 -1
- package/dist/commands/uptime.js +4 -1
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +32 -7
- package/dist/commands/which.d.ts.map +1 -1
- package/dist/commands/xargs.d.ts.map +1 -1
- package/dist/commands/xargs.js +1 -1
- package/dist/index.d.ts +15 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -9
- package/dist/modules/linuxRootfs.d.ts +18 -1
- package/dist/modules/linuxRootfs.d.ts.map +1 -1
- package/dist/modules/linuxRootfs.js +160 -17
- package/dist/standalone-wo-sftp.d.ts +2 -0
- package/dist/standalone-wo-sftp.d.ts.map +1 -0
- package/dist/standalone-wo-sftp.js +30 -0
- package/dist/utils/expand.d.ts +50 -0
- package/dist/utils/expand.d.ts.map +1 -0
- package/dist/utils/expand.js +183 -0
- package/dist/utils/vfsDiff.d.ts +90 -0
- package/dist/utils/vfsDiff.d.ts.map +1 -0
- package/dist/utils/vfsDiff.js +177 -0
- package/package.json +2 -1
- package/src/SSHMimic/exec.ts +10 -1
- package/src/SSHMimic/executor.ts +104 -18
- package/src/SSHMimic/index.ts +49 -15
- package/src/VirtualFileSystem/binaryPack.ts +35 -8
- package/src/VirtualFileSystem/index.ts +78 -28
- package/src/VirtualPackageManager/index.ts +208 -49
- package/src/VirtualShell/index.ts +35 -7
- package/src/VirtualShell/shell.ts +23 -3
- package/src/VirtualShell/shellParser.ts +134 -36
- package/src/VirtualUserManager/index.ts +7 -2
- package/src/commands/adduser.ts +6 -0
- package/src/commands/alias.ts +5 -1
- package/src/commands/apt.ts +47 -17
- package/src/commands/awk.ts +20 -6
- package/src/commands/base64.ts +13 -2
- package/src/commands/cat.ts +13 -5
- package/src/commands/cd.ts +5 -0
- package/src/commands/chmod.ts +5 -0
- package/src/commands/cp.ts +5 -0
- package/src/commands/curl.ts +56 -12
- package/src/commands/cut.ts +8 -1
- package/src/commands/date.ts +7 -1
- package/src/commands/declare.ts +44 -0
- package/src/commands/diff.ts +17 -3
- package/src/commands/dpkg.ts +33 -11
- package/src/commands/du.ts +17 -5
- package/src/commands/echo.ts +22 -9
- package/src/commands/env.ts +11 -1
- package/src/commands/exit.ts +12 -2
- package/src/commands/export.ts +3 -1
- package/src/commands/find.ts +5 -0
- package/src/commands/free.ts +9 -2
- package/src/commands/grep.ts +12 -2
- package/src/commands/gzip.ts +28 -4
- package/src/commands/head.ts +5 -0
- package/src/commands/help.ts +121 -47
- package/src/commands/history.ts +7 -2
- package/src/commands/hostname.ts +5 -0
- package/src/commands/id.ts +4 -1
- package/src/commands/index.ts +9 -360
- package/src/commands/ls.ts +5 -3
- package/src/commands/lsb-release.ts +8 -2
- package/src/commands/nano.ts +1 -1
- package/src/commands/neofetch.ts +1 -1
- package/src/commands/node.ts +341 -0
- package/src/commands/npm.ts +132 -0
- package/src/commands/ping.ts +6 -2
- package/src/commands/printf.ts +112 -0
- package/src/commands/ps.ts +21 -9
- package/src/commands/python.ts +2229 -0
- package/src/commands/read.ts +41 -0
- package/src/commands/registry.ts +244 -0
- package/src/commands/runtime.ts +353 -0
- package/src/commands/sed.ts +27 -9
- package/src/commands/set.ts +9 -3
- package/src/commands/sh.ts +159 -55
- package/src/commands/shift.ts +53 -0
- package/src/commands/sleep.ts +2 -1
- package/src/commands/sort.ts +10 -6
- package/src/commands/source.ts +15 -3
- package/src/commands/sudo.ts +1 -1
- package/src/commands/tar.ts +28 -7
- package/src/commands/tee.ts +7 -1
- package/src/commands/test.ts +61 -26
- package/src/commands/tr.ts +3 -1
- package/src/commands/true.ts +17 -0
- package/src/commands/type.ts +6 -3
- package/src/commands/uname.ts +5 -1
- package/src/commands/uniq.ts +8 -2
- package/src/commands/uptime.ts +4 -1
- package/src/commands/wget.ts +51 -12
- package/src/commands/which.ts +5 -2
- package/src/commands/xargs.ts +11 -2
- package/src/index.ts +23 -24
- package/src/modules/linuxRootfs.ts +233 -30
- package/src/standalone-wo-sftp.ts +38 -0
- package/src/utils/expand.ts +238 -0
- package/src/utils/vfsDiff.ts +275 -0
- package/standalone-wo-sftp.js +507 -0
- package/standalone-wo-sftp.js.map +7 -0
- package/standalone.js +253 -191
- package/standalone.js.map +4 -4
- package/tests/bun-test-shim.ts +9 -1
- package/tests/command-helpers.test.ts +1 -5
- package/tests/new-features.test.ts +415 -5
- package/tests/parser-executor.test.ts +27 -27
- package/tests/sftp.test.ts +122 -42
- package/tests/users.test.ts +23 -5
- package/CHANGELOG.md +0 -150
package/tests/sftp.test.ts
CHANGED
|
@@ -8,12 +8,18 @@ import { VirtualUserManager } from "../src/VirtualUserManager";
|
|
|
8
8
|
|
|
9
9
|
// VirtualFileSystem is now pure in-memory — no temp dir or cleanup needed.
|
|
10
10
|
|
|
11
|
-
function connectSftp(
|
|
11
|
+
function connectSftp(
|
|
12
|
+
port: number,
|
|
13
|
+
): Promise<{ client: Client; sftp: SFTPWrapper }> {
|
|
12
14
|
return new Promise((resolve, reject) => {
|
|
13
15
|
const client = new Client();
|
|
14
16
|
client.on("ready", () => {
|
|
15
17
|
client.sftp((err, sftp) => {
|
|
16
|
-
if (err) {
|
|
18
|
+
if (err) {
|
|
19
|
+
client.end();
|
|
20
|
+
reject(err);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
17
23
|
resolve({ client, sftp });
|
|
18
24
|
});
|
|
19
25
|
});
|
|
@@ -22,7 +28,7 @@ function connectSftp(port: number): Promise<{ client: Client; sftp: SFTPWrapper
|
|
|
22
28
|
host: "127.0.0.1",
|
|
23
29
|
port,
|
|
24
30
|
username: "root",
|
|
25
|
-
password: "",
|
|
31
|
+
password: "", // root has no password — any value or empty is accepted
|
|
26
32
|
hostVerifier: () => true,
|
|
27
33
|
});
|
|
28
34
|
});
|
|
@@ -37,7 +43,11 @@ function connectSftpWithUser(
|
|
|
37
43
|
const client = new Client();
|
|
38
44
|
client.on("ready", () => {
|
|
39
45
|
client.sftp((err, sftp) => {
|
|
40
|
-
if (err) {
|
|
46
|
+
if (err) {
|
|
47
|
+
client.end();
|
|
48
|
+
reject(err);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
41
51
|
resolve({ client, sftp });
|
|
42
52
|
});
|
|
43
53
|
});
|
|
@@ -54,7 +64,7 @@ function connectSftpWithUser(
|
|
|
54
64
|
|
|
55
65
|
describe("SftpMimic", () => {
|
|
56
66
|
test("authenticates with VirtualUserManager and serves files from the VirtualFileSystem", async () => {
|
|
57
|
-
const vfs
|
|
67
|
+
const vfs = new VirtualFileSystem();
|
|
58
68
|
const users = new VirtualUserManager(vfs);
|
|
59
69
|
|
|
60
70
|
await users.initialize();
|
|
@@ -65,26 +75,46 @@ describe("SftpMimic", () => {
|
|
|
65
75
|
}
|
|
66
76
|
vfs.writeFile(`${rootPath}/TEST.txt`, "hello world");
|
|
67
77
|
|
|
68
|
-
const server = new SftpMimic({
|
|
78
|
+
const server = new SftpMimic({
|
|
79
|
+
port: 0,
|
|
80
|
+
hostname: "test-sftp",
|
|
81
|
+
vfs,
|
|
82
|
+
users,
|
|
83
|
+
});
|
|
69
84
|
const port = await server.start();
|
|
70
85
|
|
|
71
86
|
try {
|
|
72
87
|
const { client, sftp } = await connectSftp(port);
|
|
73
88
|
|
|
74
|
-
const list = await new Promise<FileEntryWithStats[]>(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
const list = await new Promise<FileEntryWithStats[]>(
|
|
90
|
+
(resolve, reject) => {
|
|
91
|
+
sftp.readdir(
|
|
92
|
+
"/home/root",
|
|
93
|
+
(err?: Error | null, list?: FileEntryWithStats[]) => {
|
|
94
|
+
if (err) {
|
|
95
|
+
reject(err);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
resolve(list || []);
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
},
|
|
102
|
+
);
|
|
80
103
|
|
|
81
104
|
expect(list.map((entry) => entry.filename)).toContain("TEST.txt");
|
|
82
105
|
|
|
83
106
|
const content = await new Promise<string>((resolve, reject) => {
|
|
84
|
-
sftp.readFile(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
107
|
+
sftp.readFile(
|
|
108
|
+
"/home/root/TEST.txt",
|
|
109
|
+
"utf8",
|
|
110
|
+
(err?: Error | null, data?: Buffer) => {
|
|
111
|
+
if (err) {
|
|
112
|
+
reject(err);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
resolve((data || Buffer.alloc(0)).toString("utf8"));
|
|
116
|
+
},
|
|
117
|
+
);
|
|
88
118
|
});
|
|
89
119
|
|
|
90
120
|
expect(content).toBe("hello world");
|
|
@@ -95,7 +125,7 @@ describe("SftpMimic", () => {
|
|
|
95
125
|
});
|
|
96
126
|
|
|
97
127
|
test("blocks path traversal attempts outside home directory", async () => {
|
|
98
|
-
const vfs
|
|
128
|
+
const vfs = new VirtualFileSystem();
|
|
99
129
|
const users = new VirtualUserManager(vfs);
|
|
100
130
|
|
|
101
131
|
await users.initialize();
|
|
@@ -105,7 +135,12 @@ describe("SftpMimic", () => {
|
|
|
105
135
|
vfs.mkdir(rootPath, 0o755);
|
|
106
136
|
}
|
|
107
137
|
|
|
108
|
-
const server = new SftpMimic({
|
|
138
|
+
const server = new SftpMimic({
|
|
139
|
+
port: 0,
|
|
140
|
+
hostname: "test-sftp",
|
|
141
|
+
vfs,
|
|
142
|
+
users,
|
|
143
|
+
});
|
|
109
144
|
const port = await server.start();
|
|
110
145
|
|
|
111
146
|
try {
|
|
@@ -113,24 +148,36 @@ describe("SftpMimic", () => {
|
|
|
113
148
|
|
|
114
149
|
// /etc/passwd is outside /home/root — should be rejected
|
|
115
150
|
const traversalAttempt = await new Promise<Error | null>((resolve) => {
|
|
116
|
-
sftp.stat("/etc/passwd", (err?: Error | null) => {
|
|
151
|
+
sftp.stat("/etc/passwd", (err?: Error | null) => {
|
|
152
|
+
resolve(err ?? null);
|
|
153
|
+
});
|
|
117
154
|
});
|
|
118
155
|
|
|
119
156
|
expect(traversalAttempt).not.toBeNull();
|
|
120
157
|
expect(traversalAttempt?.message).toContain("Permission denied");
|
|
121
158
|
|
|
122
159
|
// /home/root itself should work
|
|
123
|
-
const homeAccess = await new Promise<FileEntryWithStats[]>(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
160
|
+
const homeAccess = await new Promise<FileEntryWithStats[]>(
|
|
161
|
+
(resolve, reject) => {
|
|
162
|
+
sftp.readdir(
|
|
163
|
+
"/home/root",
|
|
164
|
+
(err?: Error | null, list?: FileEntryWithStats[]) => {
|
|
165
|
+
if (err) {
|
|
166
|
+
reject(err);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
resolve(list || []);
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
},
|
|
173
|
+
);
|
|
129
174
|
expect(homeAccess).toBeDefined();
|
|
130
175
|
|
|
131
176
|
// Path traversal via ../.. should also be rejected
|
|
132
177
|
const upTraversalAttempt = await new Promise<Error | null>((resolve) => {
|
|
133
|
-
sftp.readdir("/home/root/../../etc", (err?: Error | null) => {
|
|
178
|
+
sftp.readdir("/home/root/../../etc", (err?: Error | null) => {
|
|
179
|
+
resolve(err ?? null);
|
|
180
|
+
});
|
|
134
181
|
});
|
|
135
182
|
expect(upTraversalAttempt).not.toBeNull();
|
|
136
183
|
|
|
@@ -141,7 +188,7 @@ describe("SftpMimic", () => {
|
|
|
141
188
|
});
|
|
142
189
|
|
|
143
190
|
test("allows a user with a password to authenticate", async () => {
|
|
144
|
-
const vfs
|
|
191
|
+
const vfs = new VirtualFileSystem();
|
|
145
192
|
const users = new VirtualUserManager(vfs);
|
|
146
193
|
|
|
147
194
|
await users.initialize();
|
|
@@ -153,18 +200,35 @@ describe("SftpMimic", () => {
|
|
|
153
200
|
}
|
|
154
201
|
vfs.writeFile("/home/alice/hello.txt", "hi alice");
|
|
155
202
|
|
|
156
|
-
const server = new SftpMimic({
|
|
203
|
+
const server = new SftpMimic({
|
|
204
|
+
port: 0,
|
|
205
|
+
hostname: "test-sftp",
|
|
206
|
+
vfs,
|
|
207
|
+
users,
|
|
208
|
+
});
|
|
157
209
|
const port = await server.start();
|
|
158
210
|
|
|
159
211
|
try {
|
|
160
|
-
const { client, sftp } = await connectSftpWithUser(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
212
|
+
const { client, sftp } = await connectSftpWithUser(
|
|
213
|
+
port,
|
|
214
|
+
"alice",
|
|
215
|
+
"alice-pass",
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const list = await new Promise<FileEntryWithStats[]>(
|
|
219
|
+
(resolve, reject) => {
|
|
220
|
+
sftp.readdir(
|
|
221
|
+
"/home/alice",
|
|
222
|
+
(err?: Error | null, list?: FileEntryWithStats[]) => {
|
|
223
|
+
if (err) {
|
|
224
|
+
reject(err);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
resolve(list || []);
|
|
228
|
+
},
|
|
229
|
+
);
|
|
230
|
+
},
|
|
231
|
+
);
|
|
168
232
|
|
|
169
233
|
expect(list.map((e) => e.filename)).toContain("hello.txt");
|
|
170
234
|
|
|
@@ -175,13 +239,18 @@ describe("SftpMimic", () => {
|
|
|
175
239
|
});
|
|
176
240
|
|
|
177
241
|
test("rejects a user with a wrong password", async () => {
|
|
178
|
-
const vfs
|
|
242
|
+
const vfs = new VirtualFileSystem();
|
|
179
243
|
const users = new VirtualUserManager(vfs);
|
|
180
244
|
|
|
181
245
|
await users.initialize();
|
|
182
246
|
await users.addUser("bob", "correct-pass");
|
|
183
247
|
|
|
184
|
-
const server = new SftpMimic({
|
|
248
|
+
const server = new SftpMimic({
|
|
249
|
+
port: 0,
|
|
250
|
+
hostname: "test-sftp",
|
|
251
|
+
vfs,
|
|
252
|
+
users,
|
|
253
|
+
});
|
|
185
254
|
const port = await server.start();
|
|
186
255
|
|
|
187
256
|
try {
|
|
@@ -193,7 +262,7 @@ describe("SftpMimic", () => {
|
|
|
193
262
|
});
|
|
194
263
|
|
|
195
264
|
test("allows writing and reading back a file over SFTP", async () => {
|
|
196
|
-
const vfs
|
|
265
|
+
const vfs = new VirtualFileSystem();
|
|
197
266
|
const users = new VirtualUserManager(vfs);
|
|
198
267
|
|
|
199
268
|
await users.initialize();
|
|
@@ -202,7 +271,12 @@ describe("SftpMimic", () => {
|
|
|
202
271
|
vfs.mkdir("/home/root", 0o755);
|
|
203
272
|
}
|
|
204
273
|
|
|
205
|
-
const server = new SftpMimic({
|
|
274
|
+
const server = new SftpMimic({
|
|
275
|
+
port: 0,
|
|
276
|
+
hostname: "test-sftp",
|
|
277
|
+
vfs,
|
|
278
|
+
users,
|
|
279
|
+
});
|
|
206
280
|
const port = await server.start();
|
|
207
281
|
|
|
208
282
|
try {
|
|
@@ -213,7 +287,10 @@ describe("SftpMimic", () => {
|
|
|
213
287
|
"/home/root/written.txt",
|
|
214
288
|
Buffer.from("written via sftp"),
|
|
215
289
|
(err?: Error | null) => {
|
|
216
|
-
if (err) {
|
|
290
|
+
if (err) {
|
|
291
|
+
reject(err);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
217
294
|
resolve();
|
|
218
295
|
},
|
|
219
296
|
);
|
|
@@ -224,7 +301,10 @@ describe("SftpMimic", () => {
|
|
|
224
301
|
"/home/root/written.txt",
|
|
225
302
|
"utf8",
|
|
226
303
|
(err?: Error | null, data?: Buffer) => {
|
|
227
|
-
if (err) {
|
|
304
|
+
if (err) {
|
|
305
|
+
reject(err);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
228
308
|
resolve((data || Buffer.alloc(0)).toString("utf8"));
|
|
229
309
|
},
|
|
230
310
|
);
|
package/tests/users.test.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import VirtualFileSystem from "../src/VirtualFileSystem";
|
|
3
3
|
import { VirtualUserManager } from "../src/VirtualUserManager";
|
|
4
4
|
|
|
5
|
-
function makeVfs() {
|
|
5
|
+
function makeVfs() {
|
|
6
|
+
return new VirtualFileSystem();
|
|
7
|
+
}
|
|
6
8
|
|
|
7
9
|
describe("VirtualUserManager auto sudo", () => {
|
|
8
10
|
test("adds new users to sudoers by default", async () => {
|
|
@@ -40,9 +42,17 @@ describe("VirtualUserManager quotas", () => {
|
|
|
40
42
|
await users.addUser("alice", "alice-pass");
|
|
41
43
|
const startingUsage = users.getUsageBytes("alice");
|
|
42
44
|
await users.setQuotaBytes("alice", startingUsage + 5);
|
|
43
|
-
expect(() => {
|
|
45
|
+
expect(() => {
|
|
46
|
+
users.assertWriteWithinQuota("alice", "/home/alice/note.txt", "hello");
|
|
47
|
+
}).not.toThrow();
|
|
44
48
|
vfs.writeFile("/home/alice/note.txt", "hello");
|
|
45
|
-
expect(() => {
|
|
49
|
+
expect(() => {
|
|
50
|
+
users.assertWriteWithinQuota(
|
|
51
|
+
"alice",
|
|
52
|
+
"/home/alice/note.txt",
|
|
53
|
+
"this exceeds the configured quota",
|
|
54
|
+
);
|
|
55
|
+
}).toThrow("quota exceeded for 'alice'");
|
|
46
56
|
});
|
|
47
57
|
|
|
48
58
|
test("does not enforce home quota outside user home", async () => {
|
|
@@ -51,7 +61,9 @@ describe("VirtualUserManager quotas", () => {
|
|
|
51
61
|
await users.initialize();
|
|
52
62
|
await users.addUser("bob", "bob-pass");
|
|
53
63
|
await users.setQuotaBytes("bob", 1);
|
|
54
|
-
expect(() => {
|
|
64
|
+
expect(() => {
|
|
65
|
+
users.assertWriteWithinQuota("bob", "/tmp/shared.txt", "large-content");
|
|
66
|
+
}).not.toThrow();
|
|
55
67
|
});
|
|
56
68
|
|
|
57
69
|
test("clearQuota removes enforced limit", async () => {
|
|
@@ -63,6 +75,12 @@ describe("VirtualUserManager quotas", () => {
|
|
|
63
75
|
expect(users.getQuotaBytes("charlie")).toBe(2);
|
|
64
76
|
await users.clearQuota("charlie");
|
|
65
77
|
expect(users.getQuotaBytes("charlie")).toBeNull();
|
|
66
|
-
expect(() => {
|
|
78
|
+
expect(() => {
|
|
79
|
+
users.assertWriteWithinQuota(
|
|
80
|
+
"charlie",
|
|
81
|
+
"/home/charlie/file.txt",
|
|
82
|
+
"long-content",
|
|
83
|
+
);
|
|
84
|
+
}).not.toThrow();
|
|
67
85
|
});
|
|
68
86
|
});
|
package/CHANGELOG.md
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project are documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on Keep a Changelog.
|
|
6
|
-
|
|
7
|
-
## [Unreleased]
|
|
8
|
-
|
|
9
|
-
## [1.1.5] - 2026-04-16
|
|
10
|
-
|
|
11
|
-
### Added
|
|
12
|
-
|
|
13
|
-
- `HoneyPot` auditing utility for tracking and auditing SSH/SFTP activity across virtual shell, filesystem, user manager, and server components.
|
|
14
|
-
- HoneyPot attaches event listeners to `VirtualShell`, `VirtualFileSystem`, `VirtualUserManager`, `SshMimic`, and `SftpMimic` for holistic activity capture.
|
|
15
|
-
- HoneyPot event tracking, statistics, anomaly detection, and audit log export support.
|
|
16
|
-
- Comprehensive HoneyPot documentation in `README.md` and dedicated example files.
|
|
17
|
-
- Added `examples/honeypot-quickstart.ts`, `examples/honeypot-audit.ts`, `examples/honeypot-export.ts`, and `examples/README.md`.
|
|
18
|
-
- Added `HONEYPOT.md` implementation guide for HoneyPot usage patterns and integrations.
|
|
19
|
-
|
|
20
|
-
## [1.1.4] - 2026-04-16
|
|
21
|
-
|
|
22
|
-
### Added
|
|
23
|
-
|
|
24
|
-
- New SFTP server implementation (`SftpMimic`), exported as `VirtualSftpServer`.
|
|
25
|
-
- SFTP handlers for file/directory operations: `OPEN`, `READ`, `WRITE`, `FSTAT`, `CLOSE`, `OPENDIR`, `READDIR`, `STAT`, `LSTAT`, `SETSTAT`, `FSETSTAT`, `REALPATH`, `MKDIR`, `RMDIR`, `REMOVE`, `RENAME`.
|
|
26
|
-
- Home-directory confinement for SFTP sessions (`/home/<user>`) with path traversal blocking.
|
|
27
|
-
- Keyboard-interactive authentication support in SFTP in addition to password auth.
|
|
28
|
-
- Standalone runtime now starts both SSH and SFTP servers with a shared `VirtualShell`.
|
|
29
|
-
- New SFTP test suite covering authentication, read/write flows, system-user login, and traversal protection.
|
|
30
|
-
- **Event-Driven Integration**: Core classes now extend `EventEmitter` for full lifecycle visibility:
|
|
31
|
-
- `VirtualShell`: `initialized`, `command`, `session:start` events
|
|
32
|
-
- `VirtualFileSystem`: `file:read`, `file:write`, `dir:create`, `mirror:flush` events
|
|
33
|
-
- `VirtualUserManager`: `initialized`, `user:add`, `user:delete`, `session:register`, `session:unregister` events
|
|
34
|
-
- `SshMimic` & `SftpMimic`: `start`, `stop`, `auth:success`, `auth:failure`, `client:connect`, `client:disconnect` events
|
|
35
|
-
- New `HoneyPot` utility for tracking and auditing server activity via event listeners.
|
|
36
|
-
|
|
37
|
-
### Changed
|
|
38
|
-
|
|
39
|
-
- `VirtualFileSystem` mirror strategy now uses `.vfs/mirror` directory storage as the persistence boundary.
|
|
40
|
-
- SSH and SFTP startup now explicitly wait for full shell/user initialization before accepting connections.
|
|
41
|
-
- SFTP `start()` returns the actual bound port (including when configured with port `0`).
|
|
42
|
-
- Relative SFTP request paths are resolved from the authenticated user home.
|
|
43
|
-
- `VirtualUserManager.initialize()` auto-creates the current system user (`$USER` / `$USERNAME`) for easier local auth flows.
|
|
44
|
-
|
|
45
|
-
### Fixed
|
|
46
|
-
|
|
47
|
-
- Added stronger error handling/logging for SFTP client/session/stream lifecycle events.
|
|
48
|
-
- Fixed flaky SFTP tests by isolating each test run with temporary VFS base paths and deterministic cleanup.
|
|
49
|
-
- Fixed `ssh-exec` test timeout by resolving the mocked exec stream completion correctly.
|
|
50
|
-
|
|
51
|
-
## [1.0.6...1.0.8] - 2026-04-15
|
|
52
|
-
|
|
53
|
-
Too much refactor to list.
|
|
54
|
-
|
|
55
|
-
## [1.0.5] - 2026-04-15
|
|
56
|
-
|
|
57
|
-
### Changed
|
|
58
|
-
|
|
59
|
-
- Refactored commands to use shared argument/flag parsing helpers.
|
|
60
|
-
- Improved maintainability and consistency of argument parsing across commands.
|
|
61
|
-
|
|
62
|
-
### Fixed
|
|
63
|
-
|
|
64
|
-
- Verified all refactored commands pass existing test cases without regressions.
|
|
65
|
-
|
|
66
|
-
## [1.0.4] - 2026-04-15
|
|
67
|
-
|
|
68
|
-
### Added
|
|
69
|
-
|
|
70
|
-
- Shell pipeline parser and executor with support for:
|
|
71
|
-
- Pipes (`|`)
|
|
72
|
-
- Input redirection (`<`)
|
|
73
|
-
- Output redirection (`>`)
|
|
74
|
-
- Append redirection (`>>`)
|
|
75
|
-
- New built-in commands:
|
|
76
|
-
- `echo`
|
|
77
|
-
- `grep`
|
|
78
|
-
- `set`
|
|
79
|
-
- `env`
|
|
80
|
-
- `export`
|
|
81
|
-
- `unset`
|
|
82
|
-
- `sh` (with `bash` alias)
|
|
83
|
-
- Command stdin support in runtime context so commands can consume piped input.
|
|
84
|
-
|
|
85
|
-
### Changed
|
|
86
|
-
|
|
87
|
-
- Argument parsing now respects quoted strings, including for commands like `sh -c "echo hi"`.
|
|
88
|
-
- `echo` now expands environment variables (`$VAR`) and can read from stdin when no explicit text argument is provided.
|
|
89
|
-
- `grep` now supports stdin input (e.g. `ls | grep ".txt"`) in addition to file operands.
|
|
90
|
-
|
|
91
|
-
### Fixed
|
|
92
|
-
|
|
93
|
-
- Relative file paths in redirections are now resolved from current working directory during pipeline execution.
|
|
94
|
-
- Example fixed behavior: `echo hi > cat.txt` writes to `./cat.txt` in current virtual directory.
|
|
95
|
-
- Pipeline chaining now correctly passes command stdout as stdin to next command.
|
|
96
|
-
|
|
97
|
-
## [1.0.2] - 2026-04-14
|
|
98
|
-
|
|
99
|
-
### Added
|
|
100
|
-
|
|
101
|
-
- Governance and community files:
|
|
102
|
-
- LICENSE
|
|
103
|
-
- CONTRIBUTING.md
|
|
104
|
-
- SECURITY.md
|
|
105
|
-
- CODE_OF_CONDUCT.md
|
|
106
|
-
- GitHub issue and PR templates
|
|
107
|
-
- Security hardening for virtual auth storage and shell access:
|
|
108
|
-
- Non-root commands now block access to `/virtual-env-js/.auth/**`.
|
|
109
|
-
- Auth files are persisted with restrictive modes (`0700` for `.auth`, `0600` for `htpasswd` and `sudoers`).
|
|
110
|
-
- Root password no longer falls back to a fixed default when `SSH_MIMIC_ROOT_PASSWORD` is unset; startup generates an ephemeral password instead.
|
|
111
|
-
- New environment toggle `SSH_MIMIC_AUTO_SUDO_NEW_USERS` to control whether newly created users are added to sudoers by default.
|
|
112
|
-
- README and security docs now describe the new auth hardening and configuration flags.
|
|
113
|
-
- Added tests covering `.auth` path protection and auto-sudo behavior.
|
|
114
|
-
|
|
115
|
-
## [1.0.1] - 2026-04-14
|
|
116
|
-
|
|
117
|
-
### Added
|
|
118
|
-
|
|
119
|
-
- `ls -l` / `ls --long` support with long listing format (permissions, size, updated time).
|
|
120
|
-
- Host-command mirroring for network tools:
|
|
121
|
-
- `curl` now runs through host `curl` via `child_process`.
|
|
122
|
-
- `wget` now runs through host `wget` via `child_process`.
|
|
123
|
-
- Temporary host download flow for `wget` using `/tmp` before import into VFS.
|
|
124
|
-
- Terminal line normalization utility for command help and diagnostics rendering.
|
|
125
|
-
|
|
126
|
-
### Changed
|
|
127
|
-
|
|
128
|
-
- `curl` behavior is now aligned with the host binary output and exit codes.
|
|
129
|
-
- `curl -o` writes host command output to the virtual filesystem target path.
|
|
130
|
-
- `wget` writes downloaded payloads to VFS after host-side transfer, preserving command semantics.
|
|
131
|
-
- URL fetch helper now accepts host-only inputs by normalizing missing protocol to `http://`.
|
|
132
|
-
- Auto pull-request GitHub workflow now targets any non-`main` branch instead of only `dev`.
|
|
133
|
-
- Auto PR metadata now uses the dynamic source branch name in PR head/title/body.
|
|
134
|
-
- Test workflow trigger scope was generalized by removing hardcoded branch filters.
|
|
135
|
-
|
|
136
|
-
### Fixed
|
|
137
|
-
|
|
138
|
-
- Resolved large horizontal spacing artifacts in SSH terminal output by normalizing TTY line endings (`\r\n`) in both interactive shell and exec paths.
|
|
139
|
-
- Reduced excessive whitespace in help output rendering (`curl --help`, `wget --help`) by normalizing tabs and over-padded spacing.
|
|
140
|
-
|
|
141
|
-
## [1.0.0] - 2026-04-14
|
|
142
|
-
|
|
143
|
-
### Added
|
|
144
|
-
|
|
145
|
-
- In-memory SSH server with password authentication.
|
|
146
|
-
- Virtual filesystem with optional compression and tar.gz persistence.
|
|
147
|
-
- Virtual user management with sudoers and session tracking.
|
|
148
|
-
- Programmatic SshClient API.
|
|
149
|
-
- 20+ built-in shell commands.
|
|
150
|
-
- TypeScript-first API with exported types and JSDoc.
|