typescript-virtual-container 1.5.3 → 1.5.4
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/README.md +43 -23
- package/dist/.tsbuildinfo +1 -0
- package/dist/SSHMimic/executor.js +23 -5
- package/dist/VirtualPackageManager/index.js +10 -0
- package/dist/commands/basename.d.ts +13 -0
- package/dist/commands/basename.js +45 -0
- package/dist/commands/file.d.ts +8 -0
- package/dist/commands/file.js +57 -0
- package/dist/commands/fun.d.ts +32 -0
- package/dist/commands/fun.js +172 -0
- package/dist/commands/ifconfig.d.ts +7 -0
- package/dist/commands/ifconfig.js +52 -0
- package/dist/commands/last.d.ts +13 -0
- package/dist/commands/last.js +68 -0
- package/dist/commands/manuals-bundle.js +598 -6
- package/dist/commands/registry.js +24 -2
- package/dist/commands/runtime.js +22 -2
- package/dist/commands/sh.js +5 -0
- package/dist/commands/tput.d.ts +13 -0
- package/dist/commands/tput.js +76 -0
- package/dist/commands/w.d.ts +7 -0
- package/dist/commands/w.js +38 -0
- package/dist/utils/expand.d.ts +12 -0
- package/dist/utils/expand.js +84 -0
- package/package.json +9 -3
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -50
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -31
- package/.github/dependabot.yml +0 -27
- package/.github/pull_request_template.md +0 -21
- package/.github/workflows/create-pull-request.yml +0 -85
- package/.github/workflows/publish.yml +0 -25
- package/.github/workflows/test-battery.yml +0 -102
- package/.vscode/settings.json +0 -20
- package/CODE_OF_CONDUCT.md +0 -39
- package/CONTRIBUTING.md +0 -59
- package/HONEYPOT.md +0 -358
- package/SECURITY.md +0 -33
- package/benchmark-results.txt +0 -40
- package/benchmark-virtualshell.ts +0 -88
- package/biome.json +0 -37
- package/build.js +0 -22
- package/builds/fortune-nyx-v1.5.3-directbash-k6.1.0.mjs +0 -1764
- package/builds/fortune-nyx-v1.5.3-ssh-nosftp.js +0 -1764
- package/builds/fortune-nyx-v1.5.3-ssh.cjs +0 -1765
- package/builds/fortune-nyx-v1.5.3-web.min.js +0 -17036
- package/bun.lock +0 -244
- package/docs/.nojekyll +0 -1
- package/docs/app.js +0 -1751
- package/docs/assets/hierarchy.js +0 -1
- package/docs/assets/highlight.css +0 -162
- package/docs/assets/icons.js +0 -18
- package/docs/assets/icons.svg +0 -1
- package/docs/assets/main.js +0 -60
- package/docs/assets/navigation.js +0 -1
- package/docs/assets/search.js +0 -1
- package/docs/assets/style.css +0 -1633
- package/docs/classes/HoneyPot.html +0 -31
- package/docs/classes/IdleManager.html +0 -162
- package/docs/classes/SshClient.html +0 -66
- package/docs/classes/VirtualFileSystem.html +0 -279
- package/docs/classes/VirtualPackageManager.html +0 -63
- package/docs/classes/VirtualSftpServer.html +0 -169
- package/docs/classes/VirtualShell.html +0 -285
- package/docs/classes/VirtualSshServer.html +0 -182
- package/docs/classes/VirtualUserManager.html +0 -276
- package/docs/demo.html +0 -82
- package/docs/functions/assertDiff.html +0 -6
- package/docs/functions/diffSnapshots.html +0 -7
- package/docs/functions/formatDiff.html +0 -6
- package/docs/functions/getArg.html +0 -13
- package/docs/functions/getFlag.html +0 -15
- package/docs/functions/ifFlag.html +0 -11
- package/docs/hierarchy.html +0 -1
- package/docs/index.html +0 -1869
- package/docs/interfaces/AuditLogEntry.html +0 -6
- package/docs/interfaces/CommandContext.html +0 -22
- package/docs/interfaces/CommandResult.html +0 -26
- package/docs/interfaces/ExecStream.html +0 -11
- package/docs/interfaces/HoneyPotStats.html +0 -16
- package/docs/interfaces/IdleManagerOptions.html +0 -7
- package/docs/interfaces/InstalledPackage.html +0 -20
- package/docs/interfaces/NanoEditorSession.html +0 -8
- package/docs/interfaces/PackageDefinition.html +0 -30
- package/docs/interfaces/PackageFile.html +0 -8
- package/docs/interfaces/PasswordChallenge.html +0 -16
- package/docs/interfaces/RemoveOptions.html +0 -4
- package/docs/interfaces/ShellEnv.html +0 -6
- package/docs/interfaces/ShellModule.html +0 -14
- package/docs/interfaces/ShellProperties.html +0 -14
- package/docs/interfaces/ShellStream.html +0 -11
- package/docs/interfaces/SudoChallenge.html +0 -24
- package/docs/interfaces/VfsBaseNode.html +0 -12
- package/docs/interfaces/VfsDiff.html +0 -10
- package/docs/interfaces/VfsDiffEntry.html +0 -6
- package/docs/interfaces/VfsDiffModified.html +0 -10
- package/docs/interfaces/VfsDirectoryNode.html +0 -15
- package/docs/interfaces/VfsFileNode.html +0 -17
- package/docs/interfaces/VfsOptions.html +0 -26
- package/docs/interfaces/VfsSnapshot.html +0 -3
- package/docs/interfaces/VfsSnapshotBaseNode.html +0 -8
- package/docs/interfaces/VfsSnapshotDirectoryNode.html +0 -10
- package/docs/interfaces/VfsSnapshotFileNode.html +0 -12
- package/docs/interfaces/VirtualActiveSession.html +0 -12
- package/docs/interfaces/VirtualSftpServerOptions.html +0 -7
- package/docs/interfaces/VirtualShellVfsLike.html +0 -15
- package/docs/interfaces/VirtualShellVfsOptions.html +0 -3
- package/docs/interfaces/WriteFileOptions.html +0 -6
- package/docs/media/LICENSE +0 -21
- package/docs/modules.html +0 -1
- package/docs/types/ArgParseOptions.html +0 -4
- package/docs/types/CommandMode.html +0 -2
- package/docs/types/CommandOutcome.html +0 -2
- package/docs/types/IdleState.html +0 -1
- package/docs/types/VfsNodeStats.html +0 -2
- package/docs/types/VfsNodeType.html +0 -2
- package/docs/types/VfsPersistenceMode.html +0 -5
- package/docs/types/VfsSnapshotNode.html +0 -2
- package/examples/README.md +0 -288
- package/examples/app.js +0 -1751
- package/examples/app.ts +0 -299
- package/examples/build.js +0 -27
- package/examples/demo.html +0 -33
- package/examples/honeypot-audit.ts +0 -180
- package/examples/honeypot-export.ts +0 -253
- package/examples/honeypot-quickstart.ts +0 -110
- package/examples/index.html +0 -82
- package/examples/server.js +0 -55
- package/polyfills/buffer.js +0 -117
- package/polyfills/node_child_process/index.js +0 -2
- package/polyfills/node_crypto/index.js +0 -167
- package/polyfills/node_events/index.js +0 -9
- package/polyfills/node_fs/index.js +0 -202
- package/polyfills/node_fs/promises.js +0 -4
- package/polyfills/node_os/index.js +0 -9
- package/polyfills/node_path/index.js +0 -28
- package/polyfills/node_vm/index.js +0 -7
- package/polyfills/node_zlib/index.js +0 -3
- package/polyfills/process.js +0 -14
- package/polyfills/ssh2/index.js +0 -75
- package/scripts/build-all.mjs +0 -226
- package/scripts/build-names.mjs +0 -43
- package/scripts/generate-manuals-bundle.mjs +0 -49
- package/scripts/postinstall.js +0 -42
- package/scripts/publish-package.sh +0 -70
- package/src/Honeypot/index.ts +0 -457
- package/src/SSHClient/index.ts +0 -270
- package/src/SSHMimic/exec.ts +0 -49
- package/src/SSHMimic/executor.ts +0 -251
- package/src/SSHMimic/hostKey.ts +0 -21
- package/src/SSHMimic/index.ts +0 -337
- package/src/SSHMimic/loginBanner.ts +0 -36
- package/src/SSHMimic/loginFormat.ts +0 -10
- package/src/SSHMimic/prompt.ts +0 -14
- package/src/SSHMimic/sftp.ts +0 -883
- package/src/VirtualFileSystem/binaryPack.ts +0 -258
- package/src/VirtualFileSystem/index.ts +0 -1193
- package/src/VirtualFileSystem/internalTypes.ts +0 -43
- package/src/VirtualFileSystem/journal.ts +0 -171
- package/src/VirtualFileSystem/path.ts +0 -74
- package/src/VirtualPackageManager/index.ts +0 -996
- package/src/VirtualShell/idleManager.ts +0 -137
- package/src/VirtualShell/index.ts +0 -475
- package/src/VirtualShell/shell.ts +0 -700
- package/src/VirtualShell/shellParser.ts +0 -285
- package/src/VirtualUserManager/index.ts +0 -758
- package/src/bun.d.ts +0 -1
- package/src/commands/adduser.ts +0 -103
- package/src/commands/alias.ts +0 -69
- package/src/commands/apt.ts +0 -233
- package/src/commands/awk.ts +0 -168
- package/src/commands/base64.ts +0 -29
- package/src/commands/cat.ts +0 -52
- package/src/commands/cd.ts +0 -25
- package/src/commands/chmod.ts +0 -85
- package/src/commands/clear.ts +0 -15
- package/src/commands/command-helpers.ts +0 -286
- package/src/commands/cp.ts +0 -83
- package/src/commands/curl.ts +0 -147
- package/src/commands/cut.ts +0 -36
- package/src/commands/date.ts +0 -30
- package/src/commands/declare.ts +0 -49
- package/src/commands/deluser.ts +0 -98
- package/src/commands/df.ts +0 -23
- package/src/commands/diff.ts +0 -43
- package/src/commands/dpkg.ts +0 -180
- package/src/commands/du.ts +0 -56
- package/src/commands/echo.ts +0 -58
- package/src/commands/env.ts +0 -23
- package/src/commands/exit.ts +0 -18
- package/src/commands/export.ts +0 -34
- package/src/commands/find.ts +0 -68
- package/src/commands/free.ts +0 -47
- package/src/commands/grep.ts +0 -116
- package/src/commands/groups.ts +0 -19
- package/src/commands/gzip.ts +0 -88
- package/src/commands/head.ts +0 -52
- package/src/commands/help.ts +0 -152
- package/src/commands/helpers.ts +0 -234
- package/src/commands/history.ts +0 -34
- package/src/commands/hostname.ts +0 -14
- package/src/commands/htop.ts +0 -20
- package/src/commands/id.ts +0 -19
- package/src/commands/index.ts +0 -9
- package/src/commands/kill.ts +0 -19
- package/src/commands/ln.ts +0 -71
- package/src/commands/ls.ts +0 -243
- package/src/commands/lsb-release.ts +0 -63
- package/src/commands/man.ts +0 -31
- package/src/commands/manuals/adduser.txt +0 -11
- package/src/commands/manuals/apt-cache.txt +0 -12
- package/src/commands/manuals/apt.txt +0 -20
- package/src/commands/manuals/awk.txt +0 -13
- package/src/commands/manuals/cat.txt +0 -14
- package/src/commands/manuals/cd.txt +0 -16
- package/src/commands/manuals/chmod.txt +0 -16
- package/src/commands/manuals/clear.txt +0 -10
- package/src/commands/manuals/cp.txt +0 -10
- package/src/commands/manuals/curl.txt +0 -20
- package/src/commands/manuals/date.txt +0 -14
- package/src/commands/manuals/declare.txt +0 -12
- package/src/commands/manuals/deluser.txt +0 -10
- package/src/commands/manuals/df.txt +0 -10
- package/src/commands/manuals/dpkg-query.txt +0 -11
- package/src/commands/manuals/dpkg.txt +0 -14
- package/src/commands/manuals/du.txt +0 -11
- package/src/commands/manuals/echo.txt +0 -11
- package/src/commands/manuals/false.txt +0 -10
- package/src/commands/manuals/find.txt +0 -11
- package/src/commands/manuals/free.txt +0 -12
- package/src/commands/manuals/grep.txt +0 -13
- package/src/commands/manuals/groups.txt +0 -10
- package/src/commands/manuals/gzip.txt +0 -11
- package/src/commands/manuals/head.txt +0 -10
- package/src/commands/manuals/help.txt +0 -11
- package/src/commands/manuals/history.txt +0 -11
- package/src/commands/manuals/hostname.txt +0 -10
- package/src/commands/manuals/id.txt +0 -10
- package/src/commands/manuals/kill.txt +0 -13
- package/src/commands/manuals/ls.txt +0 -20
- package/src/commands/manuals/lsb_release.txt +0 -14
- package/src/commands/manuals/mkdir.txt +0 -10
- package/src/commands/manuals/mv.txt +0 -10
- package/src/commands/manuals/nano.txt +0 -11
- package/src/commands/manuals/neofetch.txt +0 -10
- package/src/commands/manuals/node.txt +0 -13
- package/src/commands/manuals/npm.txt +0 -13
- package/src/commands/manuals/npx.txt +0 -13
- package/src/commands/manuals/passwd.txt +0 -11
- package/src/commands/manuals/ping.txt +0 -10
- package/src/commands/manuals/printf.txt +0 -11
- package/src/commands/manuals/ps.txt +0 -10
- package/src/commands/manuals/pwd.txt +0 -10
- package/src/commands/manuals/python3.txt +0 -13
- package/src/commands/manuals/readlink.txt +0 -10
- package/src/commands/manuals/return.txt +0 -10
- package/src/commands/manuals/rm.txt +0 -10
- package/src/commands/manuals/sed.txt +0 -11
- package/src/commands/manuals/set.txt +0 -11
- package/src/commands/manuals/shift.txt +0 -10
- package/src/commands/manuals/sleep.txt +0 -10
- package/src/commands/manuals/sort.txt +0 -12
- package/src/commands/manuals/source.txt +0 -11
- package/src/commands/manuals/ssh.txt +0 -11
- package/src/commands/manuals/stat.txt +0 -10
- package/src/commands/manuals/su.txt +0 -13
- package/src/commands/manuals/sudo.txt +0 -11
- package/src/commands/manuals/tail.txt +0 -10
- package/src/commands/manuals/tar.txt +0 -19
- package/src/commands/manuals/tee.txt +0 -10
- package/src/commands/manuals/test.txt +0 -11
- package/src/commands/manuals/touch.txt +0 -11
- package/src/commands/manuals/tr.txt +0 -10
- package/src/commands/manuals/trap.txt +0 -10
- package/src/commands/manuals/true.txt +0 -10
- package/src/commands/manuals/type.txt +0 -10
- package/src/commands/manuals/uname.txt +0 -12
- package/src/commands/manuals/uniq.txt +0 -12
- package/src/commands/manuals/unset.txt +0 -10
- package/src/commands/manuals/uptime.txt +0 -11
- package/src/commands/manuals/wc.txt +0 -12
- package/src/commands/manuals/wget.txt +0 -12
- package/src/commands/manuals/which.txt +0 -10
- package/src/commands/manuals/whoami.txt +0 -10
- package/src/commands/manuals/xargs.txt +0 -10
- package/src/commands/manuals-bundle.ts +0 -898
- package/src/commands/mkdir.ts +0 -31
- package/src/commands/mv.ts +0 -50
- package/src/commands/nano.ts +0 -38
- package/src/commands/neofetch.ts +0 -53
- package/src/commands/node.ts +0 -341
- package/src/commands/npm.ts +0 -132
- package/src/commands/passwd.ts +0 -50
- package/src/commands/ping.ts +0 -32
- package/src/commands/printf.ts +0 -129
- package/src/commands/ps.ts +0 -58
- package/src/commands/pwd.ts +0 -9
- package/src/commands/python.ts +0 -2229
- package/src/commands/read.ts +0 -46
- package/src/commands/registry.ts +0 -249
- package/src/commands/rm.ts +0 -42
- package/src/commands/runtime.ts +0 -421
- package/src/commands/sed.ts +0 -68
- package/src/commands/seq.ts +0 -43
- package/src/commands/set.ts +0 -29
- package/src/commands/sh.ts +0 -467
- package/src/commands/shift.ts +0 -63
- package/src/commands/sleep.ts +0 -20
- package/src/commands/sort.ts +0 -46
- package/src/commands/source.ts +0 -52
- package/src/commands/stat.ts +0 -61
- package/src/commands/su.ts +0 -72
- package/src/commands/sudo.ts +0 -76
- package/src/commands/tail.ts +0 -53
- package/src/commands/tar.ts +0 -102
- package/src/commands/tee.ts +0 -36
- package/src/commands/test.ts +0 -137
- package/src/commands/touch.ts +0 -28
- package/src/commands/tr.ts +0 -70
- package/src/commands/tree.ts +0 -20
- package/src/commands/true.ts +0 -27
- package/src/commands/type.ts +0 -48
- package/src/commands/uname.ts +0 -29
- package/src/commands/uniq.ts +0 -39
- package/src/commands/unset.ts +0 -17
- package/src/commands/uptime.ts +0 -54
- package/src/commands/wc.ts +0 -55
- package/src/commands/wget.ts +0 -148
- package/src/commands/which.ts +0 -37
- package/src/commands/who.ts +0 -25
- package/src/commands/whoami.ts +0 -14
- package/src/commands/xargs.ts +0 -31
- package/src/index.ts +0 -67
- package/src/modules/linuxRootfs.ts +0 -1961
- package/src/modules/neofetch.ts +0 -358
- package/src/modules/shellInteractive.ts +0 -57
- package/src/modules/shellRuntime.ts +0 -76
- package/src/self-standalone.ts +0 -542
- package/src/standalone-wo-sftp.ts +0 -38
- package/src/standalone.ts +0 -72
- package/src/types/commands.ts +0 -146
- package/src/types/pipeline.ts +0 -52
- package/src/types/streams.ts +0 -32
- package/src/types/tar-stream.d.ts +0 -38
- package/src/types/vfs.ts +0 -98
- package/src/utils/expand.ts +0 -491
- package/src/utils/perfLogger.ts +0 -72
- package/src/utils/tokenize.ts +0 -98
- package/src/utils/vfsDiff.ts +0 -275
- package/tests/command-helpers.test.ts +0 -116
- package/tests/commands-admin-net.test.ts +0 -441
- package/tests/commands-advanced.test.ts +0 -456
- package/tests/commands-core.test.ts +0 -562
- package/tests/commands-missing.test.ts +0 -570
- package/tests/commands-specific-units.test.ts +0 -327
- package/tests/commands-text-sys.test.ts +0 -445
- package/tests/expand.test.ts +0 -170
- package/tests/helpers.test.ts +0 -97
- package/tests/new-features.test.ts +0 -1036
- package/tests/parser-executor.test.ts +0 -37
- package/tests/sftp.test.ts +0 -323
- package/tests/ssh-exec.test.ts +0 -45
- package/tests/test-helper.ts +0 -79
- package/tests/users.test.ts +0 -86
- package/tsconfig.json +0 -49
- package/typedoc.json +0 -47
package/src/SSHMimic/exec.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { makeDefaultEnv, runCommand, userHome } from "../commands";
|
|
2
|
-
import type { ExecStream } from "../types/streams";
|
|
3
|
-
import type { VirtualShell } from "../VirtualShell";
|
|
4
|
-
|
|
5
|
-
function toTtyLines(text: string): string {
|
|
6
|
-
return text
|
|
7
|
-
.replace(/\r\n/g, "\n")
|
|
8
|
-
.replace(/\r/g, "\n")
|
|
9
|
-
.replace(/\n/g, "\r\n");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function runExec(
|
|
13
|
-
stream: ExecStream,
|
|
14
|
-
cmd: string,
|
|
15
|
-
authUser: string,
|
|
16
|
-
hostname: string,
|
|
17
|
-
shell: VirtualShell,
|
|
18
|
-
): void {
|
|
19
|
-
Promise.resolve(
|
|
20
|
-
runCommand(
|
|
21
|
-
cmd,
|
|
22
|
-
authUser,
|
|
23
|
-
hostname,
|
|
24
|
-
"exec",
|
|
25
|
-
userHome(authUser),
|
|
26
|
-
shell,
|
|
27
|
-
undefined,
|
|
28
|
-
makeDefaultEnv(authUser, hostname),
|
|
29
|
-
),
|
|
30
|
-
)
|
|
31
|
-
.then((result) => {
|
|
32
|
-
if (result.stdout) {
|
|
33
|
-
stream.write(`${toTtyLines(result.stdout)}\r\n`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (result.stderr) {
|
|
37
|
-
stream.stderr.write(`${toTtyLines(result.stderr)}\r\n`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
stream.exit(result.exitCode ?? 0);
|
|
41
|
-
stream.end();
|
|
42
|
-
})
|
|
43
|
-
.catch((error) => {
|
|
44
|
-
console.error("Exec error:", error);
|
|
45
|
-
stream.stderr.write(`Error: ${String(error)}\r\n`);
|
|
46
|
-
stream.exit(1);
|
|
47
|
-
stream.end();
|
|
48
|
-
});
|
|
49
|
-
}
|
package/src/SSHMimic/executor.ts
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import { runCommandDirect } from "../commands";
|
|
2
|
-
import { resolvePath } from "../commands/helpers";
|
|
3
|
-
import type { CommandMode, CommandResult, ShellEnv } from "../types/commands";
|
|
4
|
-
import type {
|
|
5
|
-
Pipeline,
|
|
6
|
-
PipelineCommand,
|
|
7
|
-
Statement,
|
|
8
|
-
} from "../types/pipeline";
|
|
9
|
-
import type { VirtualShell } from "../VirtualShell";
|
|
10
|
-
|
|
11
|
-
// ── Script executor (handles &&/||/;) ────────────────────────────────────────
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export async function executeStatements(
|
|
15
|
-
statements: Statement[],
|
|
16
|
-
authUser: string,
|
|
17
|
-
hostname: string,
|
|
18
|
-
mode: CommandMode,
|
|
19
|
-
cwd: string,
|
|
20
|
-
shell: VirtualShell,
|
|
21
|
-
env: ShellEnv,
|
|
22
|
-
): Promise<CommandResult> {
|
|
23
|
-
let last: CommandResult = { exitCode: 0 };
|
|
24
|
-
const accumulatedStdout: string[] = [];
|
|
25
|
-
let currentCwd = cwd; // track cwd changes from cd, su, etc.
|
|
26
|
-
let i = 0;
|
|
27
|
-
|
|
28
|
-
while (i < statements.length) {
|
|
29
|
-
const stmt = statements[i]!;
|
|
30
|
-
last = await executePipeline(
|
|
31
|
-
stmt.pipeline,
|
|
32
|
-
authUser,
|
|
33
|
-
hostname,
|
|
34
|
-
mode,
|
|
35
|
-
currentCwd,
|
|
36
|
-
shell,
|
|
37
|
-
env,
|
|
38
|
-
);
|
|
39
|
-
env.lastExitCode = last.exitCode ?? 0;
|
|
40
|
-
|
|
41
|
-
// Propagate cwd changes (cd, su -l, etc.)
|
|
42
|
-
if (last.nextCwd && (last.exitCode ?? 0) === 0) {
|
|
43
|
-
currentCwd = last.nextCwd;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Collect stdout from each statement (for echo a; echo b → "a\nb\n")
|
|
47
|
-
if (last.stdout) accumulatedStdout.push(last.stdout);
|
|
48
|
-
|
|
49
|
-
if (last.closeSession || last.switchUser) {
|
|
50
|
-
return {
|
|
51
|
-
...last,
|
|
52
|
-
stdout: accumulatedStdout.join("") || last.stdout,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const op = stmt.op;
|
|
57
|
-
if (!op || op === ";") {
|
|
58
|
-
// always run next
|
|
59
|
-
} else if (op === "&&") {
|
|
60
|
-
if ((last.exitCode ?? 0) !== 0) {
|
|
61
|
-
// skip until next ; or end
|
|
62
|
-
while (i < statements.length && statements[i]?.op === "&&") i++;
|
|
63
|
-
}
|
|
64
|
-
} else if (op === "||") {
|
|
65
|
-
if ((last.exitCode ?? 0) === 0) {
|
|
66
|
-
// skip until next ; or end
|
|
67
|
-
while (i < statements.length && statements[i]?.op === "||") i++;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
i++;
|
|
71
|
-
}
|
|
72
|
-
// Merge accumulated stdout (for "echo a; echo b" → "a\nb\n")
|
|
73
|
-
const merged = accumulatedStdout.join("");
|
|
74
|
-
// Preserve the deepest cwd change across the whole pipeline
|
|
75
|
-
return { ...last, stdout: merged || last.stdout, nextCwd: currentCwd !== cwd ? currentCwd : undefined };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ── Pipeline executor ─────────────────────────────────────────────────────────
|
|
79
|
-
|
|
80
|
-
export async function executePipeline(
|
|
81
|
-
pipeline: Pipeline,
|
|
82
|
-
authUser: string,
|
|
83
|
-
hostname: string,
|
|
84
|
-
mode: CommandMode,
|
|
85
|
-
cwd: string,
|
|
86
|
-
shell: VirtualShell,
|
|
87
|
-
env?: ShellEnv,
|
|
88
|
-
): Promise<CommandResult> {
|
|
89
|
-
if (!pipeline.isValid)
|
|
90
|
-
return { stderr: pipeline.error || "Syntax error", exitCode: 1 };
|
|
91
|
-
if (pipeline.commands.length === 0) return { exitCode: 0 };
|
|
92
|
-
|
|
93
|
-
const shellEnv: ShellEnv = env ?? { vars: {}, lastExitCode: 0 };
|
|
94
|
-
|
|
95
|
-
if (pipeline.commands.length === 1) {
|
|
96
|
-
return executeSingleCommandWithRedirections(
|
|
97
|
-
pipeline.commands[0] as PipelineCommand,
|
|
98
|
-
authUser,
|
|
99
|
-
hostname,
|
|
100
|
-
mode,
|
|
101
|
-
cwd,
|
|
102
|
-
shell,
|
|
103
|
-
shellEnv,
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return executePipelineChain(
|
|
108
|
-
pipeline.commands as PipelineCommand[],
|
|
109
|
-
authUser,
|
|
110
|
-
hostname,
|
|
111
|
-
mode,
|
|
112
|
-
cwd,
|
|
113
|
-
shell,
|
|
114
|
-
shellEnv,
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function executeSingleCommandWithRedirections(
|
|
119
|
-
cmd: PipelineCommand,
|
|
120
|
-
authUser: string,
|
|
121
|
-
hostname: string,
|
|
122
|
-
mode: CommandMode,
|
|
123
|
-
cwd: string,
|
|
124
|
-
shell: VirtualShell,
|
|
125
|
-
env: ShellEnv,
|
|
126
|
-
): Promise<CommandResult> {
|
|
127
|
-
let stdin: string | undefined;
|
|
128
|
-
if (cmd.inputFile) {
|
|
129
|
-
const inputPath = resolvePath(cwd, cmd.inputFile);
|
|
130
|
-
try {
|
|
131
|
-
stdin = shell.vfs.readFile(inputPath);
|
|
132
|
-
} catch {
|
|
133
|
-
return {
|
|
134
|
-
stderr: `${cmd.inputFile}: No such file or directory`,
|
|
135
|
-
exitCode: 1,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const result = await runCommandDirect(
|
|
141
|
-
cmd.name,
|
|
142
|
-
cmd.args,
|
|
143
|
-
authUser,
|
|
144
|
-
hostname,
|
|
145
|
-
mode,
|
|
146
|
-
cwd,
|
|
147
|
-
shell,
|
|
148
|
-
stdin,
|
|
149
|
-
env,
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
if (cmd.outputFile) {
|
|
153
|
-
const outputPath = resolvePath(cwd, cmd.outputFile);
|
|
154
|
-
const output = result.stdout || "";
|
|
155
|
-
try {
|
|
156
|
-
if (cmd.appendOutput) {
|
|
157
|
-
const existing = (() => {
|
|
158
|
-
try {
|
|
159
|
-
return shell.vfs.readFile(outputPath);
|
|
160
|
-
} catch {
|
|
161
|
-
return "";
|
|
162
|
-
}
|
|
163
|
-
})();
|
|
164
|
-
shell.writeFileAsUser(authUser, outputPath, existing + output);
|
|
165
|
-
} else {
|
|
166
|
-
shell.writeFileAsUser(authUser, outputPath, output);
|
|
167
|
-
}
|
|
168
|
-
return { ...result, stdout: "" };
|
|
169
|
-
} catch {
|
|
170
|
-
return {
|
|
171
|
-
...result,
|
|
172
|
-
stderr: `Failed to write to ${cmd.outputFile}`,
|
|
173
|
-
exitCode: 1,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return result;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async function executePipelineChain(
|
|
182
|
-
commands: PipelineCommand[],
|
|
183
|
-
authUser: string,
|
|
184
|
-
hostname: string,
|
|
185
|
-
mode: CommandMode,
|
|
186
|
-
cwd: string,
|
|
187
|
-
shell: VirtualShell,
|
|
188
|
-
env: ShellEnv,
|
|
189
|
-
): Promise<CommandResult> {
|
|
190
|
-
let currentOutput = "";
|
|
191
|
-
let exitCode = 0;
|
|
192
|
-
|
|
193
|
-
for (let i = 0; i < commands.length; i++) {
|
|
194
|
-
const cmd = commands[i] as PipelineCommand;
|
|
195
|
-
|
|
196
|
-
if (i === 0 && cmd.inputFile) {
|
|
197
|
-
const inputPath = resolvePath(cwd, cmd.inputFile);
|
|
198
|
-
try {
|
|
199
|
-
currentOutput = shell.vfs.readFile(inputPath);
|
|
200
|
-
} catch {
|
|
201
|
-
return {
|
|
202
|
-
stderr: `${cmd.inputFile}: No such file or directory`,
|
|
203
|
-
exitCode: 1,
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const result = await runCommandDirect(
|
|
209
|
-
cmd.name,
|
|
210
|
-
cmd.args,
|
|
211
|
-
authUser,
|
|
212
|
-
hostname,
|
|
213
|
-
mode,
|
|
214
|
-
cwd,
|
|
215
|
-
shell,
|
|
216
|
-
currentOutput,
|
|
217
|
-
env,
|
|
218
|
-
);
|
|
219
|
-
exitCode = result.exitCode ?? 0;
|
|
220
|
-
|
|
221
|
-
if (i === commands.length - 1 && cmd.outputFile) {
|
|
222
|
-
const outputPath = resolvePath(cwd, cmd.outputFile);
|
|
223
|
-
const output = result.stdout || "";
|
|
224
|
-
try {
|
|
225
|
-
if (cmd.appendOutput) {
|
|
226
|
-
const existing = (() => {
|
|
227
|
-
try {
|
|
228
|
-
return shell.vfs.readFile(outputPath);
|
|
229
|
-
} catch {
|
|
230
|
-
return "";
|
|
231
|
-
}
|
|
232
|
-
})();
|
|
233
|
-
shell.writeFileAsUser(authUser, outputPath, existing + output);
|
|
234
|
-
} else {
|
|
235
|
-
shell.writeFileAsUser(authUser, outputPath, output);
|
|
236
|
-
}
|
|
237
|
-
currentOutput = "";
|
|
238
|
-
} catch {
|
|
239
|
-
return { stderr: `Failed to write to ${cmd.outputFile}`, exitCode: 1 };
|
|
240
|
-
}
|
|
241
|
-
} else {
|
|
242
|
-
currentOutput = result.stdout || "";
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (result.stderr && exitCode !== 0)
|
|
246
|
-
return { stderr: result.stderr, exitCode };
|
|
247
|
-
if (result.closeSession || result.switchUser) return result;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return { stdout: currentOutput, exitCode };
|
|
251
|
-
}
|
package/src/SSHMimic/hostKey.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { generateKeyPairSync } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { dirname, resolve } from "node:path";
|
|
4
|
-
|
|
5
|
-
export function loadOrCreateHostKey(baseDir: string = process.cwd()): string {
|
|
6
|
-
const hostKeyPath = resolve(baseDir, ".ssh-mimic", "host_rsa");
|
|
7
|
-
|
|
8
|
-
if (existsSync(hostKeyPath)) {
|
|
9
|
-
return readFileSync(hostKeyPath, "utf8");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const privateKey = generateKeyPairSync("rsa", {
|
|
13
|
-
modulusLength: 2048,
|
|
14
|
-
privateKeyEncoding: { type: "pkcs1", format: "pem" },
|
|
15
|
-
publicKeyEncoding: { type: "pkcs1", format: "pem" },
|
|
16
|
-
}).privateKey;
|
|
17
|
-
|
|
18
|
-
mkdirSync(dirname(hostKeyPath), { recursive: true });
|
|
19
|
-
writeFileSync(hostKeyPath, privateKey, { mode: 0o600 });
|
|
20
|
-
return privateKey;
|
|
21
|
-
}
|
package/src/SSHMimic/index.ts
DELETED
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from "node:events";
|
|
2
|
-
import { Server as SshServer } from "ssh2";
|
|
3
|
-
import { VirtualShell } from "../VirtualShell";
|
|
4
|
-
import { userHome } from "../commands";
|
|
5
|
-
import { createPerfLogger, type PerfLogger } from "../utils/perfLogger";
|
|
6
|
-
import { runExec } from "./exec";
|
|
7
|
-
import { loadOrCreateHostKey } from "./hostKey";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* SSH server facade that wires the virtual shell runtime into ssh2 sessions.
|
|
11
|
-
*
|
|
12
|
-
* This class is exported as `VirtualSshServer` for public API compatibility.
|
|
13
|
-
* Create an instance, call {@link SshMimic.start}, and stop it with
|
|
14
|
-
* {@link SshMimic.stop} when your process exits.
|
|
15
|
-
*
|
|
16
|
-
* Features:
|
|
17
|
-
* - Password authentication
|
|
18
|
-
* - Public-key authentication
|
|
19
|
-
* - Per-IP rate limiting / lockout for brute-force protection
|
|
20
|
-
* - Interactive shell sessions
|
|
21
|
-
* - Non-interactive exec sessions
|
|
22
|
-
*/
|
|
23
|
-
const perf: PerfLogger = createPerfLogger("SshMimic");
|
|
24
|
-
|
|
25
|
-
// ── Dev-mode logger ───────────────────────────────────────────────────────────
|
|
26
|
-
const DEV = !!process.env.DEV_MODE;
|
|
27
|
-
const devLog = DEV ? console.log.bind(console) : () => {};
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/** @internal */
|
|
31
|
-
interface RateLimitEntry {
|
|
32
|
-
attempts: number;
|
|
33
|
-
lockedUntil: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
class SshMimic extends EventEmitter {
|
|
37
|
-
port: number;
|
|
38
|
-
server: SshServer | null;
|
|
39
|
-
private shell: VirtualShell;
|
|
40
|
-
|
|
41
|
-
/** Max failed auth attempts before an IP is temporarily locked. */
|
|
42
|
-
private readonly maxAuthAttempts: number;
|
|
43
|
-
/** How long (ms) a locked IP must wait before retrying. */
|
|
44
|
-
private readonly lockoutDurationMs: number;
|
|
45
|
-
private readonly authAttempts = new Map<string, RateLimitEntry>();
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Creates a new SSH mimic server instance.
|
|
49
|
-
*
|
|
50
|
-
* @param options - Configuration object for the SSH server.
|
|
51
|
-
* @param options.port - TCP port to bind on localhost.
|
|
52
|
-
* @param options.hostname - Virtual hostname used for the SSH ident and default shell label.
|
|
53
|
-
* @param options.shell - Optional preconfigured virtual shell instance to reuse.
|
|
54
|
-
* @param options.maxAuthAttempts - Max failed attempts per IP before lockout (default: 5).
|
|
55
|
-
* @param options.lockoutDurationMs - Lockout window in ms after exceeding attempts (default: 60 000).
|
|
56
|
-
*/
|
|
57
|
-
constructor({
|
|
58
|
-
port,
|
|
59
|
-
hostname = "typescript-vm",
|
|
60
|
-
shell = new VirtualShell(hostname),
|
|
61
|
-
maxAuthAttempts = 5,
|
|
62
|
-
lockoutDurationMs = 60_000,
|
|
63
|
-
}: {
|
|
64
|
-
port: number;
|
|
65
|
-
hostname?: string;
|
|
66
|
-
shell?: VirtualShell;
|
|
67
|
-
maxAuthAttempts?: number;
|
|
68
|
-
lockoutDurationMs?: number;
|
|
69
|
-
}) {
|
|
70
|
-
super();
|
|
71
|
-
perf.mark("constructor");
|
|
72
|
-
this.port = port;
|
|
73
|
-
this.server = null;
|
|
74
|
-
this.shell = shell;
|
|
75
|
-
this.maxAuthAttempts = maxAuthAttempts;
|
|
76
|
-
this.lockoutDurationMs = lockoutDurationMs;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ── Rate limiting ────────────────────────────────────────────────────────
|
|
80
|
-
|
|
81
|
-
private isLockedOut(ip: string): boolean {
|
|
82
|
-
const entry = this.authAttempts.get(ip);
|
|
83
|
-
if (!entry) return false;
|
|
84
|
-
if (Date.now() < entry.lockedUntil) return true;
|
|
85
|
-
if (entry.lockedUntil > 0) {
|
|
86
|
-
this.authAttempts.delete(ip);
|
|
87
|
-
}
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private recordFailure(ip: string): void {
|
|
92
|
-
const entry = this.authAttempts.get(ip) ?? { attempts: 0, lockedUntil: 0 };
|
|
93
|
-
entry.attempts += 1;
|
|
94
|
-
if (entry.attempts >= this.maxAuthAttempts) {
|
|
95
|
-
entry.lockedUntil = Date.now() + this.lockoutDurationMs;
|
|
96
|
-
this.emit("auth:lockout", { ip, until: new Date(entry.lockedUntil) });
|
|
97
|
-
}
|
|
98
|
-
this.authAttempts.set(ip, entry);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private recordSuccess(ip: string): void {
|
|
102
|
-
this.authAttempts.delete(ip);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ── Home directory bootstrap ─────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
private ensureHomeDir(authUser: string): void {
|
|
108
|
-
const homePath = userHome(authUser);
|
|
109
|
-
if (!this.shell.vfs.exists(homePath)) {
|
|
110
|
-
this.shell.vfs.mkdir(homePath, 0o755);
|
|
111
|
-
this.shell.vfs.writeFile(
|
|
112
|
-
`${homePath}/README.txt`,
|
|
113
|
-
`Welcome to ${this.shell.hostname}\n`,
|
|
114
|
-
);
|
|
115
|
-
void this.shell.vfs.stopAutoFlush();
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// ── Server lifecycle ─────────────────────────────────────────────────────
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Starts server and initializes virtual filesystem, users, and handlers.
|
|
123
|
-
*
|
|
124
|
-
* @returns Promise resolved with bound listening port.
|
|
125
|
-
*/
|
|
126
|
-
public async start(): Promise<number> {
|
|
127
|
-
perf.mark("start");
|
|
128
|
-
const shell = this.shell;
|
|
129
|
-
const privateKey = loadOrCreateHostKey();
|
|
130
|
-
|
|
131
|
-
await shell.ensureInitialized();
|
|
132
|
-
|
|
133
|
-
this.server = new SshServer(
|
|
134
|
-
{
|
|
135
|
-
hostKeys: [privateKey],
|
|
136
|
-
ident: `SSH-2.0-${shell.hostname}`,
|
|
137
|
-
},
|
|
138
|
-
(client) => {
|
|
139
|
-
let authUser = "root";
|
|
140
|
-
let remoteAddress = "unknown";
|
|
141
|
-
let sessionId: string | null = null;
|
|
142
|
-
|
|
143
|
-
this.emit("client:connect");
|
|
144
|
-
|
|
145
|
-
client.on("authentication", (ctx) => {
|
|
146
|
-
const candidateUser = ctx.username || "root";
|
|
147
|
-
remoteAddress = (ctx as { ip?: string }).ip ?? remoteAddress;
|
|
148
|
-
|
|
149
|
-
// Rate-limit check
|
|
150
|
-
if (this.isLockedOut(remoteAddress)) {
|
|
151
|
-
this.emit("auth:failure", {
|
|
152
|
-
username: candidateUser,
|
|
153
|
-
remoteAddress,
|
|
154
|
-
reason: "lockout",
|
|
155
|
-
});
|
|
156
|
-
ctx.reject();
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ── Password auth ──────────────────────────────────────
|
|
161
|
-
if (ctx.method === "password") {
|
|
162
|
-
if (!shell.users.hasPassword(candidateUser)) {
|
|
163
|
-
authUser = candidateUser;
|
|
164
|
-
sessionId = shell.users.registerSession(
|
|
165
|
-
authUser,
|
|
166
|
-
remoteAddress,
|
|
167
|
-
).id;
|
|
168
|
-
this.recordSuccess(remoteAddress);
|
|
169
|
-
this.emit("auth:success", { username: authUser, remoteAddress });
|
|
170
|
-
this.ensureHomeDir(authUser);
|
|
171
|
-
ctx.accept();
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (
|
|
176
|
-
!ctx.password ||
|
|
177
|
-
ctx.password === "" ||
|
|
178
|
-
!shell.users.verifyPassword(candidateUser, ctx.password)
|
|
179
|
-
) {
|
|
180
|
-
this.recordFailure(remoteAddress);
|
|
181
|
-
this.emit("auth:failure", {
|
|
182
|
-
username: candidateUser,
|
|
183
|
-
remoteAddress,
|
|
184
|
-
});
|
|
185
|
-
ctx.reject();
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
authUser = candidateUser;
|
|
190
|
-
sessionId = shell.users.registerSession(authUser, remoteAddress).id;
|
|
191
|
-
this.recordSuccess(remoteAddress);
|
|
192
|
-
this.emit("auth:success", { username: authUser, remoteAddress });
|
|
193
|
-
this.ensureHomeDir(authUser);
|
|
194
|
-
ctx.accept();
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// ── Public-key auth ────────────────────────────────────
|
|
199
|
-
if (ctx.method === "publickey") {
|
|
200
|
-
const authorizedKeys = shell.users.getAuthorizedKeys(candidateUser);
|
|
201
|
-
if (authorizedKeys.length === 0) {
|
|
202
|
-
// No keys configured — reject cleanly
|
|
203
|
-
ctx.reject();
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const incomingKey = ctx.key;
|
|
208
|
-
const keyMatches = authorizedKeys.some(
|
|
209
|
-
(k) =>
|
|
210
|
-
k.algo === incomingKey.algo && k.data.equals(incomingKey.data),
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
if (!keyMatches) {
|
|
214
|
-
this.recordFailure(remoteAddress);
|
|
215
|
-
this.emit("auth:failure", {
|
|
216
|
-
username: candidateUser,
|
|
217
|
-
remoteAddress,
|
|
218
|
-
method: "publickey",
|
|
219
|
-
});
|
|
220
|
-
ctx.reject();
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Key matched — if this is a signature check step, accept
|
|
225
|
-
if (ctx.signature) {
|
|
226
|
-
authUser = candidateUser;
|
|
227
|
-
sessionId = shell.users.registerSession(
|
|
228
|
-
authUser,
|
|
229
|
-
remoteAddress,
|
|
230
|
-
).id;
|
|
231
|
-
this.recordSuccess(remoteAddress);
|
|
232
|
-
this.emit("auth:success", {
|
|
233
|
-
username: authUser,
|
|
234
|
-
remoteAddress,
|
|
235
|
-
method: "publickey",
|
|
236
|
-
});
|
|
237
|
-
this.ensureHomeDir(authUser);
|
|
238
|
-
ctx.accept();
|
|
239
|
-
} else {
|
|
240
|
-
// Key exists but no signature yet — ssh2 will call again with signature
|
|
241
|
-
ctx.accept();
|
|
242
|
-
}
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
ctx.reject(["password", "publickey"]);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
client.on("close", () => {
|
|
250
|
-
shell.users.unregisterSession(sessionId);
|
|
251
|
-
this.emit("client:disconnect", { user: authUser });
|
|
252
|
-
sessionId = null;
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
client.on("ready", () => {
|
|
256
|
-
client.on("session", (accept) => {
|
|
257
|
-
const session = accept();
|
|
258
|
-
const terminalSize = { cols: 80, rows: 24 };
|
|
259
|
-
|
|
260
|
-
session.on("pty", (acceptPty, _rejectPty, info) => {
|
|
261
|
-
terminalSize.cols = info?.cols ?? terminalSize.cols;
|
|
262
|
-
terminalSize.rows = info?.rows ?? terminalSize.rows;
|
|
263
|
-
acceptPty();
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
session.on(
|
|
267
|
-
"window-change",
|
|
268
|
-
(_acceptChange, _rejectChange, info) => {
|
|
269
|
-
terminalSize.cols = info?.cols ?? terminalSize.cols;
|
|
270
|
-
terminalSize.rows = info?.rows ?? terminalSize.rows;
|
|
271
|
-
},
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
session.on("shell", (acceptShell) => {
|
|
275
|
-
const stream = acceptShell();
|
|
276
|
-
shell?.startInteractiveSession(
|
|
277
|
-
stream,
|
|
278
|
-
authUser,
|
|
279
|
-
sessionId,
|
|
280
|
-
remoteAddress,
|
|
281
|
-
terminalSize,
|
|
282
|
-
);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
session.on("exec", (acceptExec, _rejectExec, info) => {
|
|
286
|
-
const stream = acceptExec();
|
|
287
|
-
if (stream) {
|
|
288
|
-
runExec(
|
|
289
|
-
stream,
|
|
290
|
-
info.command.trim(),
|
|
291
|
-
authUser,
|
|
292
|
-
shell.hostname,
|
|
293
|
-
shell,
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
},
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
return new Promise<number>((resolve, reject) => {
|
|
303
|
-
this.server?.once("error", (err: unknown) => reject(err));
|
|
304
|
-
this.server?.listen(this.port, "0.0.0.0", () => {
|
|
305
|
-
devLog(`SSH Mimic listening on port ${this.port}`);
|
|
306
|
-
this.emit("start", { port: this.port });
|
|
307
|
-
resolve(this.port);
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Stops server if running.
|
|
314
|
-
*/
|
|
315
|
-
public stop(): void {
|
|
316
|
-
perf.mark("stop");
|
|
317
|
-
// Flush pending WAL journal before closing
|
|
318
|
-
void this.shell.vfs.stopAutoFlush();
|
|
319
|
-
if (this.server) {
|
|
320
|
-
this.server.close(() => {
|
|
321
|
-
devLog("SSH Mimic stopped");
|
|
322
|
-
this.emit("stop");
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Manually clears the rate-limit record for an IP address.
|
|
329
|
-
* Useful in tests or admin tooling.
|
|
330
|
-
*/
|
|
331
|
-
public clearLockout(ip: string): void {
|
|
332
|
-
this.authAttempts.delete(ip);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
export { SftpMimic } from "./sftp";
|
|
337
|
-
export { SshMimic };
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { ShellProperties } from "../VirtualShell";
|
|
2
|
-
import { formatLoginDate } from "./loginFormat";
|
|
3
|
-
|
|
4
|
-
export interface LoginBannerState {
|
|
5
|
-
at: string;
|
|
6
|
-
from: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function buildLoginBanner(
|
|
10
|
-
hostname: string,
|
|
11
|
-
properties: ShellProperties,
|
|
12
|
-
lastLogin: LoginBannerState | null,
|
|
13
|
-
): string {
|
|
14
|
-
const lines = [
|
|
15
|
-
`Linux ${hostname} ${properties.kernel} ${properties.arch}`,
|
|
16
|
-
"",
|
|
17
|
-
"The programs included with the Fortune GNU/Linux system are free software;",
|
|
18
|
-
"the exact distribution terms for each program are described in the",
|
|
19
|
-
"individual files in /usr/share/doc/*/copyright.",
|
|
20
|
-
"",
|
|
21
|
-
"Fortune GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent",
|
|
22
|
-
"permitted by applicable law.",
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
if (lastLogin) {
|
|
26
|
-
const when = new Date(lastLogin.at);
|
|
27
|
-
const displayed = Number.isNaN(when.getTime())
|
|
28
|
-
? lastLogin.at
|
|
29
|
-
: formatLoginDate(when);
|
|
30
|
-
lines.push(`Last login: ${displayed} from ${lastLogin.from || "unknown"}`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
lines.push("");
|
|
34
|
-
|
|
35
|
-
return `${lines.map((line) => `${line}\r\n`).join("")}`;
|
|
36
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export function formatLoginDate(date: Date): string {
|
|
2
|
-
const weekday = date.toLocaleString("en-US", { weekday: "short" });
|
|
3
|
-
const month = date.toLocaleString("en-US", { month: "short" });
|
|
4
|
-
const day = date.getDate().toString().padStart(2, "0");
|
|
5
|
-
const hh = date.getHours().toString().padStart(2, "0");
|
|
6
|
-
const mm = date.getMinutes().toString().padStart(2, "0");
|
|
7
|
-
const ss = date.getSeconds().toString().padStart(2, "0");
|
|
8
|
-
const year = date.getFullYear();
|
|
9
|
-
return `${weekday} ${month} ${day} ${hh}:${mm}:${ss} ${year}`;
|
|
10
|
-
}
|