typescript-virtual-container 1.4.9 → 1.5.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 +1 -1
- package/README.md +141 -89
- package/builds/fortune-nyx-v1.5.1-directbash-k6.1.0.mjs +1768 -0
- package/builds/fortune-nyx-v1.5.1-ssh-nosftp.js +1768 -0
- package/builds/fortune-nyx-v1.5.1-ssh.cjs +1769 -0
- package/bun.lock +3 -3
- package/dist/SSHMimic/exec.js +2 -2
- package/dist/SSHMimic/exec.js.map +1 -1
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +2 -1
- package/dist/SSHMimic/index.js.map +1 -1
- package/dist/SSHMimic/sftp.d.ts.map +1 -1
- package/dist/SSHMimic/sftp.js +4 -3
- package/dist/SSHMimic/sftp.js.map +1 -1
- package/dist/VirtualFileSystem/index.d.ts +14 -0
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +51 -3
- package/dist/VirtualFileSystem/index.js.map +1 -1
- package/dist/VirtualFileSystem/journal.d.ts.map +1 -1
- package/dist/VirtualFileSystem/journal.js +13 -5
- package/dist/VirtualFileSystem/journal.js.map +1 -1
- package/dist/VirtualShell/shell.js +12 -12
- package/dist/VirtualShell/shell.js.map +1 -1
- package/dist/VirtualUserManager/index.js +8 -11
- package/dist/VirtualUserManager/index.js.map +1 -1
- package/dist/commands/apt.js +3 -3
- package/dist/commands/apt.js.map +1 -1
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +2 -1
- package/dist/commands/cd.js.map +1 -1
- package/dist/commands/helpers.d.ts +1 -1
- package/dist/commands/helpers.d.ts.map +1 -1
- package/dist/commands/helpers.js +3 -2
- package/dist/commands/helpers.js.map +1 -1
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -1
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/lsb-release.js +1 -1
- package/dist/commands/lsb-release.js.map +1 -1
- package/dist/commands/runtime.d.ts +2 -0
- package/dist/commands/runtime.d.ts.map +1 -1
- package/dist/commands/runtime.js +5 -1
- package/dist/commands/runtime.js.map +1 -1
- package/dist/modules/linuxRootfs.d.ts +9 -5
- package/dist/modules/linuxRootfs.d.ts.map +1 -1
- package/dist/modules/linuxRootfs.js +1079 -148
- package/dist/modules/linuxRootfs.js.map +1 -1
- package/dist/self-standalone.js +22 -12
- package/dist/self-standalone.js.map +1 -1
- package/docs/assets/hierarchy.js +1 -1
- package/docs/classes/HoneyPot.html +9 -9
- package/docs/classes/IdleManager.html +8 -8
- package/docs/classes/SshClient.html +18 -18
- package/docs/classes/VirtualFileSystem.html +34 -34
- package/docs/classes/VirtualPackageManager.html +13 -13
- package/docs/classes/VirtualSftpServer.html +3 -3
- package/docs/classes/VirtualShell.html +28 -28
- package/docs/classes/VirtualSshServer.html +5 -5
- package/docs/classes/VirtualUserManager.html +27 -27
- package/docs/functions/assertDiff.html +2 -2
- package/docs/functions/diffSnapshots.html +2 -2
- package/docs/functions/formatDiff.html +2 -2
- package/docs/functions/getArg.html +2 -2
- package/docs/functions/getFlag.html +2 -2
- package/docs/functions/ifFlag.html +2 -2
- package/docs/hierarchy.html +1 -1
- package/docs/index.html +37 -31
- package/docs/interfaces/AuditLogEntry.html +3 -3
- package/docs/interfaces/CommandContext.html +12 -12
- package/docs/interfaces/CommandResult.html +13 -13
- package/docs/interfaces/ExecStream.html +6 -6
- package/docs/interfaces/HoneyPotStats.html +3 -3
- package/docs/interfaces/IdleManagerOptions.html +3 -3
- package/docs/interfaces/InstalledPackage.html +11 -11
- package/docs/interfaces/NanoEditorSession.html +5 -5
- package/docs/interfaces/PackageDefinition.html +14 -14
- package/docs/interfaces/PackageFile.html +5 -5
- package/docs/interfaces/PasswordChallenge.html +9 -9
- package/docs/interfaces/RemoveOptions.html +3 -3
- package/docs/interfaces/ShellEnv.html +4 -4
- package/docs/interfaces/ShellModule.html +8 -8
- package/docs/interfaces/ShellProperties.html +5 -5
- package/docs/interfaces/ShellStream.html +7 -7
- package/docs/interfaces/SudoChallenge.html +9 -9
- package/docs/interfaces/VfsBaseNode.html +7 -7
- package/docs/interfaces/VfsDiff.html +6 -6
- package/docs/interfaces/VfsDiffEntry.html +4 -4
- package/docs/interfaces/VfsDiffModified.html +6 -6
- package/docs/interfaces/VfsDirectoryNode.html +8 -8
- package/docs/interfaces/VfsFileNode.html +9 -9
- package/docs/interfaces/VfsOptions.html +6 -6
- package/docs/interfaces/VfsSnapshot.html +3 -3
- package/docs/interfaces/VfsSnapshotBaseNode.html +4 -4
- package/docs/interfaces/VfsSnapshotDirectoryNode.html +5 -5
- package/docs/interfaces/VfsSnapshotFileNode.html +6 -6
- package/docs/interfaces/VirtualActiveSession.html +7 -7
- package/docs/interfaces/VirtualSftpServerOptions.html +3 -3
- package/docs/interfaces/VirtualShellVfsLike.html +3 -3
- package/docs/interfaces/VirtualShellVfsOptions.html +3 -3
- package/docs/interfaces/WriteFileOptions.html +4 -4
- package/docs/modules.html +1 -1
- package/docs/types/ArgParseOptions.html +3 -3
- package/docs/types/CommandMode.html +2 -2
- package/docs/types/CommandOutcome.html +2 -2
- package/docs/types/IdleState.html +1 -1
- package/docs/types/VfsNodeStats.html +2 -2
- package/docs/types/VfsNodeType.html +2 -2
- package/docs/types/VfsPersistenceMode.html +2 -2
- package/docs/types/VfsSnapshotNode.html +2 -2
- package/package.json +6 -5
- package/scripts/build-all.mjs +198 -0
- package/scripts/build-names.mjs +44 -0
- package/src/SSHMimic/exec.ts +2 -2
- package/src/SSHMimic/index.ts +2 -1
- package/src/SSHMimic/sftp.ts +4 -3
- package/src/VirtualFileSystem/index.ts +46 -3
- package/src/VirtualFileSystem/journal.ts +12 -5
- package/src/VirtualShell/shell.ts +12 -12
- package/src/VirtualUserManager/index.ts +11 -11
- package/src/commands/apt.ts +3 -3
- package/src/commands/cd.ts +2 -1
- package/src/commands/helpers.ts +3 -2
- package/src/commands/index.ts +1 -1
- package/src/commands/lsb-release.ts +1 -1
- package/src/commands/runtime.ts +6 -1
- package/src/modules/linuxRootfs.ts +1293 -207
- package/src/self-standalone.ts +26 -12
- package/tests/new-features.test.ts +2 -2
- package/tests/sftp.test.ts +13 -13
- package/builds/self-standalone.js +0 -1299
- package/builds/standalone-wo-sftp.js +0 -1300
- package/builds/standalone.cjs +0 -1301
- /package/builds/{web-full-api.min.js → fortune-nyx-v1.5.1-web-full.min.js} +0 -0
- /package/builds/{web.min.js → fortune-nyx-v1.5.1-web.min.js} +0 -0
package/src/self-standalone.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { stdin, stdout } from "node:process";
|
|
|
5
5
|
import { createInterface, type Interface } from "node:readline";
|
|
6
6
|
|
|
7
7
|
import { getCommandNames } from "./commands/registry";
|
|
8
|
-
import { makeDefaultEnv, runCommand } from "./commands/runtime";
|
|
8
|
+
import { makeDefaultEnv, runCommand, userHome } from "./commands/runtime";
|
|
9
9
|
import { spawnNanoEditorProcess } from "./modules/shellInteractive";
|
|
10
10
|
import { resolvePath } from "./modules/shellRuntime";
|
|
11
11
|
import { buildLoginBanner, type LoginBannerState } from "./SSHMimic/loginBanner";
|
|
@@ -66,7 +66,7 @@ async function flushVfs(): Promise<void> {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function loadHistory(authUser: string): string[] {
|
|
69
|
-
const historyPath =
|
|
69
|
+
const historyPath = `${userHome(authUser)}/.bash_history`;
|
|
70
70
|
if (!virtualShell.vfs.exists(historyPath)) {
|
|
71
71
|
virtualShell.vfs.writeFile(historyPath, "");
|
|
72
72
|
return [];
|
|
@@ -80,7 +80,7 @@ function loadHistory(authUser: string): string[] {
|
|
|
80
80
|
|
|
81
81
|
function saveHistory(history: string[], authUser: string): void {
|
|
82
82
|
const data = history.length > 0 ? `${history.join("\n")}\n` : "";
|
|
83
|
-
virtualShell.vfs.writeFile(
|
|
83
|
+
virtualShell.vfs.writeFile(`${userHome(authUser)}/.bash_history`, data);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
// ── Tab completion ────────────────────────────────────────────────────────────
|
|
@@ -173,10 +173,10 @@ function applySessionState(
|
|
|
173
173
|
let cwd = cwdState;
|
|
174
174
|
if (result.switchUser) {
|
|
175
175
|
authUser = result.switchUser;
|
|
176
|
-
cwd = result.nextCwd ??
|
|
176
|
+
cwd = result.nextCwd ?? userHome(authUser);
|
|
177
177
|
shellEnvState.vars.USER = authUser;
|
|
178
178
|
shellEnvState.vars.LOGNAME = authUser;
|
|
179
|
-
shellEnvState.vars.HOME =
|
|
179
|
+
shellEnvState.vars.HOME = userHome(authUser);
|
|
180
180
|
shellEnvState.vars.PWD = cwd;
|
|
181
181
|
} else if (result.nextCwd) {
|
|
182
182
|
cwd = result.nextCwd;
|
|
@@ -203,9 +203,23 @@ async function runReadlineShell(): Promise<void> {
|
|
|
203
203
|
process.exit(1);
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
// Ensure home dir and README.txt exist (mirrors SSHMimic/VirtualUserManager behaviour)
|
|
207
|
+
const homePath = selectedUser === "root" ? "/root" : userHome(selectedUser);
|
|
208
|
+
if (!virtualShell.vfs.exists(homePath)) {
|
|
209
|
+
virtualShell.vfs.mkdir(homePath, selectedUser === "root" ? 0o700 : 0o755);
|
|
210
|
+
}
|
|
211
|
+
const readmePath = `${homePath}/README.txt`;
|
|
212
|
+
if (!virtualShell.vfs.exists(readmePath)) {
|
|
213
|
+
virtualShell.vfs.writeFile(
|
|
214
|
+
readmePath,
|
|
215
|
+
`Welcome to ${hostname}\n`,
|
|
216
|
+
);
|
|
217
|
+
await virtualShell.vfs.stopAutoFlush();
|
|
218
|
+
}
|
|
219
|
+
|
|
206
220
|
const shellEnv = makeDefaultEnv(selectedUser, hostname);
|
|
207
221
|
let authUser = selectedUser;
|
|
208
|
-
let cwd =
|
|
222
|
+
let cwd = userHome(authUser);
|
|
209
223
|
shellEnv.vars.PWD = cwd;
|
|
210
224
|
const remoteAddress = "localhost";
|
|
211
225
|
const terminalSize = { cols: stdout.columns ?? 80, rows: stdout.rows ?? 24 };
|
|
@@ -309,15 +323,15 @@ async function runReadlineShell(): Promise<void> {
|
|
|
309
323
|
|
|
310
324
|
if (!challenge.commandLine) {
|
|
311
325
|
authUser = challenge.targetUser;
|
|
312
|
-
cwd =
|
|
326
|
+
cwd = userHome(authUser);
|
|
313
327
|
shellEnv.vars.USER = authUser;
|
|
314
328
|
shellEnv.vars.LOGNAME = authUser;
|
|
315
|
-
shellEnv.vars.HOME =
|
|
329
|
+
shellEnv.vars.HOME = userHome(authUser);
|
|
316
330
|
shellEnv.vars.PWD = cwd;
|
|
317
331
|
return;
|
|
318
332
|
}
|
|
319
333
|
|
|
320
|
-
const runCwd = challenge.loginShell ?
|
|
334
|
+
const runCwd = challenge.loginShell ? userHome(challenge.targetUser) : cwd;
|
|
321
335
|
const nestedResult = await runCommand(
|
|
322
336
|
challenge.commandLine,
|
|
323
337
|
challenge.targetUser,
|
|
@@ -360,10 +374,10 @@ async function runReadlineShell(): Promise<void> {
|
|
|
360
374
|
break;
|
|
361
375
|
case "su":
|
|
362
376
|
authUser = challenge.targetUsername;
|
|
363
|
-
cwd =
|
|
377
|
+
cwd = userHome(authUser);
|
|
364
378
|
shellEnv.vars.USER = authUser;
|
|
365
379
|
shellEnv.vars.LOGNAME = authUser;
|
|
366
|
-
shellEnv.vars.HOME =
|
|
380
|
+
shellEnv.vars.HOME = userHome(authUser);
|
|
367
381
|
shellEnv.vars.PWD = cwd;
|
|
368
382
|
break;
|
|
369
383
|
}
|
|
@@ -417,7 +431,7 @@ async function runReadlineShell(): Promise<void> {
|
|
|
417
431
|
// ── Prompt helper ──────────────────────────────────────────────────────────
|
|
418
432
|
|
|
419
433
|
const renderPrompt = (): string => {
|
|
420
|
-
const cwdLabel = cwd ===
|
|
434
|
+
const cwdLabel = cwd === userHome(authUser) ? "~" : basename(cwd) || "/";
|
|
421
435
|
return buildPrompt(authUser, hostname, cwdLabel);
|
|
422
436
|
};
|
|
423
437
|
|
|
@@ -55,7 +55,7 @@ describe("Linux rootfs", () => {
|
|
|
55
55
|
expect(r.stdout).toContain("1.0.0+itsrealfortune");
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
test("/
|
|
58
|
+
test("/ exists", async () => {
|
|
59
59
|
const r = await client.cat("/sys/devices/virtual/dmi/id/sys_vendor");
|
|
60
60
|
expect(r.exitCode).toBe(0);
|
|
61
61
|
expect(r.stdout?.trim()).toBe("Fortune Systems");
|
|
@@ -381,7 +381,7 @@ describe("Extra commands", () => {
|
|
|
381
381
|
test("lsb_release -a returns distro info", async () => {
|
|
382
382
|
const r = await client.exec("lsb_release -a");
|
|
383
383
|
expect(r.stdout).toContain("Fortune");
|
|
384
|
-
expect(r.stdout).toContain("
|
|
384
|
+
expect(r.stdout).toContain("nyx");
|
|
385
385
|
});
|
|
386
386
|
|
|
387
387
|
test("lsb_release -d returns description", async () => {
|
package/tests/sftp.test.ts
CHANGED
|
@@ -69,7 +69,7 @@ describe("SftpMimic", () => {
|
|
|
69
69
|
|
|
70
70
|
await users.initialize();
|
|
71
71
|
|
|
72
|
-
const rootPath = "/
|
|
72
|
+
const rootPath = "/root";
|
|
73
73
|
if (!vfs.exists(rootPath)) {
|
|
74
74
|
vfs.mkdir(rootPath, 0o755);
|
|
75
75
|
}
|
|
@@ -89,7 +89,7 @@ describe("SftpMimic", () => {
|
|
|
89
89
|
const list = await new Promise<FileEntryWithStats[]>(
|
|
90
90
|
(resolve, reject) => {
|
|
91
91
|
sftp.readdir(
|
|
92
|
-
"/
|
|
92
|
+
"/root",
|
|
93
93
|
(err?: Error | null, list?: FileEntryWithStats[]) => {
|
|
94
94
|
if (err) {
|
|
95
95
|
reject(err);
|
|
@@ -105,7 +105,7 @@ describe("SftpMimic", () => {
|
|
|
105
105
|
|
|
106
106
|
const content = await new Promise<string>((resolve, reject) => {
|
|
107
107
|
sftp.readFile(
|
|
108
|
-
"/
|
|
108
|
+
"/root/TEST.txt",
|
|
109
109
|
"utf8",
|
|
110
110
|
(err?: Error | null, data?: Buffer) => {
|
|
111
111
|
if (err) {
|
|
@@ -130,7 +130,7 @@ describe("SftpMimic", () => {
|
|
|
130
130
|
|
|
131
131
|
await users.initialize();
|
|
132
132
|
|
|
133
|
-
const rootPath = "/
|
|
133
|
+
const rootPath = "/root";
|
|
134
134
|
if (!vfs.exists(rootPath)) {
|
|
135
135
|
vfs.mkdir(rootPath, 0o755);
|
|
136
136
|
}
|
|
@@ -146,7 +146,7 @@ describe("SftpMimic", () => {
|
|
|
146
146
|
try {
|
|
147
147
|
const { client, sftp } = await connectSftp(port);
|
|
148
148
|
|
|
149
|
-
// /etc/passwd is outside /
|
|
149
|
+
// /etc/passwd is outside /root — should be rejected
|
|
150
150
|
const traversalAttempt = await new Promise<Error | null>((resolve) => {
|
|
151
151
|
sftp.stat("/etc/passwd", (err?: Error | null) => {
|
|
152
152
|
resolve(err ?? null);
|
|
@@ -156,11 +156,11 @@ describe("SftpMimic", () => {
|
|
|
156
156
|
expect(traversalAttempt).not.toBeNull();
|
|
157
157
|
expect(traversalAttempt?.message).toContain("Permission denied");
|
|
158
158
|
|
|
159
|
-
// /
|
|
159
|
+
// /root itself should work
|
|
160
160
|
const homeAccess = await new Promise<FileEntryWithStats[]>(
|
|
161
161
|
(resolve, reject) => {
|
|
162
162
|
sftp.readdir(
|
|
163
|
-
"/
|
|
163
|
+
"/root",
|
|
164
164
|
(err?: Error | null, list?: FileEntryWithStats[]) => {
|
|
165
165
|
if (err) {
|
|
166
166
|
reject(err);
|
|
@@ -175,7 +175,7 @@ describe("SftpMimic", () => {
|
|
|
175
175
|
|
|
176
176
|
// Path traversal via ../.. should also be rejected
|
|
177
177
|
const upTraversalAttempt = await new Promise<Error | null>((resolve) => {
|
|
178
|
-
sftp.readdir("/
|
|
178
|
+
sftp.readdir("/root/../../etc", (err?: Error | null) => {
|
|
179
179
|
resolve(err ?? null);
|
|
180
180
|
});
|
|
181
181
|
});
|
|
@@ -267,8 +267,8 @@ describe("SftpMimic", () => {
|
|
|
267
267
|
|
|
268
268
|
await users.initialize();
|
|
269
269
|
|
|
270
|
-
if (!vfs.exists("/
|
|
271
|
-
vfs.mkdir("/
|
|
270
|
+
if (!vfs.exists("/root")) {
|
|
271
|
+
vfs.mkdir("/root", 0o755);
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
const server = new SftpMimic({
|
|
@@ -284,7 +284,7 @@ describe("SftpMimic", () => {
|
|
|
284
284
|
|
|
285
285
|
await new Promise<void>((resolve, reject) => {
|
|
286
286
|
sftp.writeFile(
|
|
287
|
-
"/
|
|
287
|
+
"/root/written.txt",
|
|
288
288
|
Buffer.from("written via sftp"),
|
|
289
289
|
(err?: Error | null) => {
|
|
290
290
|
if (err) {
|
|
@@ -298,7 +298,7 @@ describe("SftpMimic", () => {
|
|
|
298
298
|
|
|
299
299
|
const content = await new Promise<string>((resolve, reject) => {
|
|
300
300
|
sftp.readFile(
|
|
301
|
-
"/
|
|
301
|
+
"/root/written.txt",
|
|
302
302
|
"utf8",
|
|
303
303
|
(err?: Error | null, data?: Buffer) => {
|
|
304
304
|
if (err) {
|
|
@@ -313,7 +313,7 @@ describe("SftpMimic", () => {
|
|
|
313
313
|
expect(content).toBe("written via sftp");
|
|
314
314
|
|
|
315
315
|
// Also verify it landed in the in-memory VFS
|
|
316
|
-
expect(vfs.readFile("/
|
|
316
|
+
expect(vfs.readFile("/root/written.txt")).toBe("written via sftp");
|
|
317
317
|
|
|
318
318
|
client.end();
|
|
319
319
|
} finally {
|