@akms/mcp-ssh 0.2.0 → 0.2.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/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/_defs/defaults.ts","../src/_defs/command-rules.ts","../src/_defs/protected-paths.ts","../src/_defs/env-vars.ts","../src/_libs/logger.ts","../src/_libs/text.ts","../src/_libs/fs-path.ts","../src/modules/config/host-profile.ts","../src/modules/guard/command-guard.ts","../src/modules/guard/path-guard.ts","../src/modules/guard/guard-error.ts","../src/modules/ssh/ssh-connection.ts","../src/modules/ssh/session-manager.ts","../src/modules/ssh/sftp-operations.ts","../src/modules/tools/format.ts","../src/modules/tools/tool-context.ts","../src/modules/tools/exec-tools.ts","../src/modules/tools/file-tools.ts","../src/modules/tools/host-tools.ts","../src/modules/tools/session-tools.ts","../src/modules/tools/index.ts","../src/server.ts"],"names":["path","fs","StringDecoder","z"],"mappings":"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","file":"index.js","sourcesContent":["/** Env var overriding the stderr log level (\"debug\" | \"info\" | \"warn\" | \"error\" | \"silent\"). */\r\nexport const LOG_LEVEL_ENV = \"SSH_MCP_LOG_LEVEL\";\r\n\r\nexport const DEFAULT_SSH_PORT = 22;\r\n\r\nexport const DEFAULT_EXEC_TIMEOUT_MS = 60_000;\r\n\r\nexport const DEFAULT_CONNECT_TIMEOUT_MS = 20_000;\r\n\r\n/** stdout / stderr cutoff — roughly 25k tokens of context at worst. */\r\nexport const DEFAULT_MAX_OUTPUT_CHARACTERS = 100_000;\r\n\r\nexport const DEFAULT_MAX_READ_FILE_BYTES = 200_000;\r\n\r\n/** ssh2 keepalive so idle sessions survive NAT / firewall timeouts. */\r\nexport const KEEPALIVE_INTERVAL_MS = 15_000;\r\n\r\n/** Sessions untouched for this long are closed by the sweeper. */\r\nexport const SESSION_IDLE_TIMEOUT_MS = 15 * 60 * 1000;\r\n\r\nexport const SESSION_SWEEP_INTERVAL_MS = 60_000;\r\n\r\n/** Shutdown waits this long for the sessions and the transport to close, then exits anyway — a dead peer must not keep the process alive. */\r\nexport const SHUTDOWN_DEADLINE_MS = 5_000;\r\n\r\n/** Emitted after the user command so the session can track `cd` between execs. */\r\nexport const CWD_MARKER = \"__AKMS_SSH_CWD__\";\r\n\r\n/** `ssh_list_dir` stops here — a directory with 500k entries would otherwise return megabytes. */\r\nexport const MAX_DIRECTORY_ENTRIES = 1_000;\r\n\r\nexport const SERVER_NAME = \"akms-mcp-ssh\";\r\n\r\n/** What `npx -y …` resolves — the `args` the `config` command writes into every entry. */\r\nexport const PACKAGE_NAME = \"@akms/mcp-ssh\";\r\n\r\nexport const SERVER_VERSION = \"0.2.0\";\r\n","/** One deny rule: a regex tested against a command segment plus the message shown on a hit. */\r\nexport interface CommandRule {\r\n pattern: RegExp;\r\n reason: string;\r\n}\r\n\r\n/**\r\n * A rule keyed on the resolved binary plus the shape of its operands.\r\n *\r\n * Regex-only rules could not express \"`rm -rf` where *any* operand is a critical path\" —\r\n * an anchored pattern reads operands positionally, so `rm -rf /tmp/x /` slipped through\r\n * because the root argument was not the first one.\r\n */\r\nexport interface ArgumentRule {\r\n binaries: string[];\r\n /** Rule fires only when at least one flag matches this, when set. */\r\n requiredFlagPattern?: RegExp;\r\n /** Tested against every non-flag operand. */\r\n argumentPattern: RegExp;\r\n reason: string;\r\n}\r\n\r\n/**\r\n * Directories whose recursive deletion or permission rewrite destroys the machine.\r\n * Anchored as a whole-operand match, so `/var/log/myapp` stays allowed while\r\n * `/var`, `/var/`, `/var/*` do not.\r\n */\r\nexport const CRITICAL_PATH_PATTERN = new RegExp(\r\n \"^(?:\"\r\n + \"/|//|/\\\\*|/\\\\.|/\\\\.\\\\.|/\\\\*/|//\\\\*|\"\r\n + \"~|~/|~/\\\\*|\\\\$HOME|\\\\$HOME/|\\\\$HOME/\\\\*|\\\\$\\\\{HOME\\\\}/?\\\\*?|\"\r\n + \"/(?:bin|boot|dev|etc|home|lib|lib32|lib64|libx32|media|mnt|opt|proc|root|run|sbin|srv|sys|usr|var|data)(?:/\\\\*)?/?\"\r\n + \")$\",\r\n \"i\"\r\n);\r\n\r\n/** Individual files whose loss alone bricks the host or locks everyone out. */\r\nexport const CRITICAL_FILE_PATTERN = new RegExp(\r\n \"^/(?:\"\r\n + \"etc/(?:passwd|shadow|group|sudoers|fstab|hostname|hosts|resolv\\\\.conf)\"\r\n + \"|boot/(?:vmlinuz|initrd|grub).*\"\r\n + \"|etc/ssh/sshd_config\"\r\n + \")$\",\r\n \"i\"\r\n);\r\n\r\n/**\r\n * Binaries that only wrap another command — the guard steps past them (and their flags,\r\n * `FOO=bar` assignments, and numeric operands like `timeout 30`) to the binary that runs.\r\n */\r\nexport const COMMAND_WRAPPER_BINARIES = [\r\n \"sudo\",\r\n \"doas\",\r\n \"env\",\r\n \"eval\",\r\n \"nohup\",\r\n \"time\",\r\n \"nice\",\r\n \"ionice\",\r\n \"timeout\",\r\n \"stdbuf\",\r\n \"command\",\r\n \"exec\",\r\n \"builtin\",\r\n \"watch\",\r\n \"xargs\",\r\n \"setsid\",\r\n \"unbuffer\",\r\n \"busybox\",\r\n \"toybox\",\r\n];\r\n\r\n/**\r\n * `timeout 30 cmd`, `nice -n 10 cmd`, `watch 2 cmd` — these wrappers take a bare numeric\r\n * operand before the real command, so a duration must not be mistaken for the binary.\r\n */\r\nexport const NUMERIC_OPERAND_PATTERN = /^\\d+(?:\\.\\d+)?[smhd]?$/i;\r\n\r\n/**\r\n * Wrapper flags that consume the following token as their value.\r\n *\r\n * Without this, `env -u FOO rm -rf /` resolves its binary to `FOO`: the flag is skipped,\r\n * its value is not, and every binary-keyed rule then looks at the wrong word.\r\n */\r\nexport const WRAPPER_VALUE_FLAGS: Record<string, string[]> = {\r\n env: [\"-u\", \"--unset\", \"-C\", \"--chdir\", \"-S\", \"--split-string\"],\r\n sudo: [\"-u\", \"--user\", \"-g\", \"--group\", \"-p\", \"--prompt\", \"-C\", \"--close-from\", \"-h\", \"--host\", \"-D\", \"--chdir\", \"-R\", \"--chroot\"],\r\n doas: [\"-u\", \"-C\"],\r\n timeout: [\"-s\", \"--signal\", \"-k\", \"--kill-after\"],\r\n nice: [\"-n\", \"--adjustment\"],\r\n ionice: [\"-c\", \"--class\", \"-n\", \"--classdata\", \"-p\", \"--pid\"],\r\n stdbuf: [\"-i\", \"--input\", \"-o\", \"--output\", \"-e\", \"--error\"],\r\n xargs: [\"-a\", \"--arg-file\", \"-d\", \"--delimiter\", \"-E\", \"-I\", \"--replace\", \"-L\", \"-n\", \"--max-args\", \"-P\", \"--max-procs\", \"-s\", \"--max-chars\"],\r\n watch: [\"-n\", \"--interval\"],\r\n setsid: [],\r\n nohup: [],\r\n \"systemd-run\": [\"-u\", \"--unit\", \"-p\", \"--property\", \"--uid\", \"--gid\", \"--setenv\"],\r\n runuser: [\"-u\", \"--user\", \"-g\", \"--group\", \"-s\", \"--shell\"],\r\n su: [\"-s\", \"--shell\", \"-g\", \"--group\"],\r\n};\r\n\r\n/** Rejected when `allowSudo` is false. Default profiles allow these. */\r\nexport const PRIVILEGE_ESCALATION_BINARIES = [\r\n \"sudo\",\r\n \"su\",\r\n \"doas\",\r\n \"pkexec\",\r\n \"runuser\",\r\n \"chroot\",\r\n \"nsenter\",\r\n \"systemd-run\",\r\n];\r\n\r\n/**\r\n * Binaries that take a command *string* (`-c \"…\"`) and run it in a fresh shell.\r\n * The payload is pulled out and screened as its own command — otherwise\r\n * `bash -c \"rm -rf /\"` would only ever be judged as \"the binary is bash\".\r\n */\r\nexport const SHELL_PAYLOAD_BINARIES = [\r\n \"sh\",\r\n \"bash\",\r\n \"zsh\",\r\n \"ksh\",\r\n \"dash\",\r\n \"ash\",\r\n \"mksh\",\r\n \"csh\",\r\n \"tcsh\",\r\n \"fish\",\r\n \"su\",\r\n \"runuser\",\r\n \"chroot\",\r\n \"nsenter\",\r\n \"systemd-run\",\r\n];\r\n\r\n/**\r\n * Always rejected, on every profile, not configurable.\r\n *\r\n * Scope is deliberately narrow: **irreversible destruction of the machine, or cutting the\r\n * connection you are working through**. Everything short of that — installing packages,\r\n * restarting services, deleting an application's files, piping an install script into a\r\n * shell — is ordinary administration and is allowed by default. A profile that wants more\r\n * restraint opts in with `readonly`, `allowCommands` or `denyPatterns`.\r\n */\r\nexport const DANGEROUS_COMMAND_RULES: CommandRule[] = [\r\n {\r\n pattern: /^mkfs(\\.\\w+)?\\b/i,\r\n reason: \"formats a filesystem\",\r\n },\r\n {\r\n pattern: />\\s*\\/dev\\/(sd|nvme|hd|vd|xvd|mapper)/i,\r\n reason: \"redirects output onto a block device\",\r\n },\r\n {\r\n pattern: /^(shutdown|reboot|halt|poweroff)\\b/i,\r\n reason: \"powers off or reboots the host, ending this session\",\r\n },\r\n {\r\n pattern: /^init\\s+[06]\\b/i,\r\n reason: \"changes runlevel to halt or reboot\",\r\n },\r\n {\r\n pattern: /^systemctl\\s+(poweroff|reboot|halt|emergency|rescue)\\b/i,\r\n reason: \"powers off, reboots or degrades the host\",\r\n },\r\n {\r\n // Anchored on `/` as the whole search root: `find /tmp -delete` is ordinary\r\n // cleanup, `find / -delete` empties the machine.\r\n pattern: /^find\\s+\\/\\s+.*(-delete|-exec(dir)?\\s+(\\/\\S+\\/)?rm)/i,\r\n reason: \"deletes files found from the filesystem root\",\r\n },\r\n {\r\n pattern: /^crontab\\s+(-\\S+\\s+)*-r\\b/i,\r\n reason: \"removes every cron job for the user\",\r\n },\r\n {\r\n pattern: /^(iptables|ip6tables|nft)\\s+(-F|--flush|flush)\\b/i,\r\n reason: \"flushes firewall rules and can lock this session out\",\r\n },\r\n {\r\n pattern: /--no-preserve-root/i,\r\n reason: \"explicitly disables the root-deletion safeguard\",\r\n },\r\n];\r\n\r\n/** Danger rules keyed on the binary plus its operands. */\r\nexport const DANGEROUS_ARGUMENT_RULES: ArgumentRule[] = [\r\n {\r\n binaries: [\"rm\"],\r\n requiredFlagPattern: /^--?(?:[a-z]*[rRf][a-z]*|recursive|force)$/,\r\n argumentPattern: CRITICAL_PATH_PATTERN,\r\n reason: \"deletes the filesystem root, a system directory or an entire home directory\",\r\n },\r\n {\r\n binaries: [\"rm\", \"unlink\", \"shred\", \"srm\", \"wipe\"],\r\n argumentPattern: CRITICAL_FILE_PATTERN,\r\n reason: \"deletes a file the host cannot boot or authenticate without\",\r\n },\r\n {\r\n binaries: [\"shred\", \"srm\", \"wipe\"],\r\n argumentPattern: CRITICAL_PATH_PATTERN,\r\n reason: \"irreversibly destroys a system path\",\r\n },\r\n {\r\n binaries: [\"chmod\", \"chown\", \"chgrp\"],\r\n requiredFlagPattern: /^--?(R|recursive)$/,\r\n argumentPattern: CRITICAL_PATH_PATTERN,\r\n reason: \"recursively rewrites ownership or permissions on a system path\",\r\n },\r\n {\r\n binaries: [\"dd\"],\r\n argumentPattern: /^of=(\\/dev\\/(?!null|zero|stdout|stderr|tty)|\\/etc\\/|\\/boot\\/|\\/bin\\/|\\/sbin\\/|\\/usr\\/|\\/lib)/i,\r\n reason: \"writes raw bytes over a device or system file\",\r\n },\r\n {\r\n binaries: [\"mkfs\", \"fdisk\", \"parted\", \"sgdisk\", \"wipefs\"],\r\n argumentPattern: /^\\/dev\\//i,\r\n reason: \"repartitions or formats a block device\",\r\n },\r\n];\r\n\r\n/** Danger rules tested against the whole command, before it is split on `;`, `&&`, `|`. */\r\nexport const DANGEROUS_PIPELINE_RULES: CommandRule[] = [\r\n {\r\n pattern: /\\{\\s*:\\s*\\|\\s*:\\s*&\\s*\\}/,\r\n reason: \"fork bomb\",\r\n },\r\n];\r\n\r\n/**\r\n * Blocked when the profile is read-only: binaries whose whole purpose is to change the\r\n * host's files, packages, services or accounts.\r\n *\r\n * Interpreters, `awk`, database clients and archive listing are deliberately **absent**.\r\n * A read-only profile exists to make a host safe to *inspect*, and log analysis routinely\r\n * needs `python3 -c`, `awk`, `psql`. Blocking them made the mode unusable for its own\r\n * purpose while stopping nobody who wanted around it — a shell payload is still screened\r\n * (see SHELL_PAYLOAD_BINARIES), so `bash -c \"rm x\"` is caught by the `rm` rule.\r\n */\r\nexport const WRITE_COMMAND_BINARIES = [\r\n // filesystem mutation\r\n \"rm\", \"rmdir\", \"mv\", \"cp\", \"dd\", \"mkdir\", \"touch\", \"ln\", \"chmod\", \"chown\", \"chgrp\",\r\n \"truncate\", \"shred\", \"srm\", \"wipe\", \"install\", \"tee\", \"patch\", \"rename\", \"chattr\", \"setfacl\",\r\n // storage / mounts\r\n \"mkfs\", \"fdisk\", \"parted\", \"sgdisk\", \"wipefs\", \"mount\", \"umount\", \"swapon\", \"swapoff\",\r\n // accounts and scheduling\r\n \"useradd\", \"usermod\", \"userdel\", \"groupadd\", \"groupmod\", \"groupdel\",\r\n \"passwd\", \"chpasswd\", \"visudo\", \"crontab\", \"at\", \"batch\",\r\n // process and power control\r\n \"kill\", \"killall\", \"pkill\", \"reboot\", \"shutdown\", \"halt\", \"poweroff\", \"init\", \"service\",\r\n // package managers\r\n \"apt\", \"apt-get\", \"aptitude\", \"dpkg\", \"yum\", \"dnf\", \"rpm\", \"zypper\", \"pacman\", \"apk\",\r\n \"snap\", \"brew\", \"npm\", \"pnpm\", \"yarn\", \"npx\", \"pip\", \"pip3\", \"gem\", \"cargo\", \"composer\",\r\n \"bundle\", \"make\", \"cmake\",\r\n // editors — interactive and write-oriented\r\n \"vi\", \"vim\", \"nvim\", \"nano\", \"emacs\", \"ex\", \"ed\",\r\n // transfer to/from other hosts, and archive extraction\r\n \"scp\", \"rsync\", \"sftp\", \"ftp\", \"ssh\", \"ssh-keygen\", \"ssh-copy-id\", \"wget\",\r\n \"unzip\", \"gunzip\", \"bunzip2\", \"unxz\", \"cpio\",\r\n // firewall\r\n \"iptables\", \"ip6tables\", \"ufw\", \"firewall-cmd\", \"nft\",\r\n];\r\n\r\n/**\r\n * Blocked when the profile is read-only: tools that read *or* write depending on the\r\n * subcommand or flag, so only the mutating form is matched.\r\n */\r\nexport const WRITE_USAGE_RULES: CommandRule[] = [\r\n {\r\n pattern: /^sed\\b.*(?:(\\s|^)-[a-zA-Z]*i([a-zA-Z]|\\s|$|\\.)|--in-place)/,\r\n reason: \"sed edits files in place\",\r\n },\r\n {\r\n pattern: /^sort\\b.*(\\s|^)(-o|--output)\\b/i,\r\n reason: \"sort writes its result to a file\",\r\n },\r\n {\r\n pattern: /^find\\b.*(-delete|-exec|-execdir|-ok|-okdir|-fls|-fprint)/i,\r\n reason: \"find action deletes files or runs an arbitrary command\",\r\n },\r\n {\r\n pattern: /^git\\s+(push|reset|checkout|switch|restore|clean|rebase|merge|commit|add|rm|mv|stash|apply|revert|cherry-pick|pull|remote|tag|gc|prune|init|clone)\\b/i,\r\n reason: \"git subcommand mutates the working tree or the remote\",\r\n },\r\n {\r\n pattern: /^systemctl\\s+(start|stop|restart|reload|enable|disable|mask|unmask|kill|isolate|set-property|edit|daemon-reload|daemon-reexec)\\b/i,\r\n reason: \"systemctl subcommand changes service state\",\r\n },\r\n {\r\n pattern: /^docker\\s+(run|rm|rmi|stop|start|restart|kill|exec|create|build|push|pull|cp|commit|prune|load|import|tag|update|rename|volume|network|swarm)\\b/i,\r\n reason: \"docker subcommand mutates containers, images or volumes\",\r\n },\r\n {\r\n pattern: /^(kubectl|oc)\\s+(apply|delete|create|edit|scale|patch|replace|rollout|drain|cordon|uncordon|exec|cp|set|annotate|label|taint)\\b/i,\r\n reason: \"kubectl subcommand mutates cluster state\",\r\n },\r\n {\r\n pattern: /^tar\\b.*(--extract|--create|--delete|(\\s|^)-{1,2}[a-zA-Z]*[xc][a-zA-Z]*(\\s|$))/i,\r\n reason: \"tar extract/create writes files\",\r\n },\r\n {\r\n pattern: /^curl\\b.*(\\s|^)(-o|-O|--output|--remote-name|--upload-file|-T)\\b/i,\r\n reason: \"curl writes a file or uploads one\",\r\n },\r\n {\r\n pattern: /^(zip|gzip|bzip2|xz)\\b/i,\r\n reason: \"compression tool rewrites files on disk\",\r\n },\r\n];\r\n\r\n/**\r\n * Harmless redirection forms stripped before checking for output redirection —\r\n * `2>&1`, `>/dev/null`, `&>/dev/null` are diagnostics plumbing, not writes.\r\n */\r\nexport const BENIGN_REDIRECT_PATTERN = /(\\d?>>?\\s*&\\s*\\d|&>>?\\s*\\/dev\\/null|\\d?>>?\\s*\\/dev\\/null)/g;\r\n\r\n/** Any remaining `>` after the benign forms are stripped means a real file write. */\r\nexport const OUTPUT_REDIRECT_PATTERN = />/;\r\n\r\n/** Leading `2>`, `>>`, `<`, `2>&1` tokens — stripped so the binary scan finds the command. */\r\nexport const REDIRECTION_TOKEN_PATTERN = /^(?:\\d*>>?|<|&>>?)/;\r\n\r\n/**\r\n * A redirection operator with no target attached, so the *next* token is its filename\r\n * rather than a command. `2>&1` is excluded: it names its target inline.\r\n */\r\nexport const BARE_REDIRECTION_OPERATOR_PATTERN = /^(?:\\d*>>?|<|&>>?)$/;\r\n\r\n/**\r\n * Shell word splitting: redirection operators first so `tee</dev/null` yields\r\n * `tee`, `<`, `/dev/null` instead of one word whose basename reads as \"null\".\r\n */\r\nexport const SHELL_TOKEN_PATTERN = /\\d*>>?&?\\d*|&>>?|<&?\\d*|(?:[^\\s'\"\\\\<>&|]|\\\\.|'[^']*'|\"[^\"]*\")+/g;\r\n\r\n/** Shell grouping and keyword tokens that precede a command rather than being one. */\r\nexport const SHELL_KEYWORD_TOKENS = [\r\n \"if\", \"then\", \"else\", \"elif\", \"fi\",\r\n \"for\", \"while\", \"until\", \"do\", \"done\",\r\n \"case\", \"esac\", \"in\", \"select\",\r\n \"function\", \"{\", \"}\", \"(\", \")\", \"!\", \"[[\", \"]]\", \"[\", \"]\",\r\n];\r\n\r\n/**\r\n * Shell metacharacters forbidden in a `cwd` argument.\r\n *\r\n * `cwd` is interpolated into the generated `cd` prefix, so anything that can end a word\r\n * and start a new command is remote code execution that never passes through the command\r\n * guard. Directory names legitimately containing these are vanishingly rare; the trade is\r\n * obviously worth it.\r\n */\r\nexport const UNSAFE_PATH_CHARACTER_PATTERN = /[`$;|&<>(){}!*?\\[\\]\\\\\"'\\n\\r\\t]/;\r\n","import type { CommandRule } from \"./command-rules\";\r\n\r\n/**\r\n * Local paths the file tools may never read or write, whatever the profile allows.\r\n *\r\n * Deliberately narrow — two categories only:\r\n *\r\n * 1. **Credentials.** Uploading these exfiltrates the operator's keys to a remote host.\r\n * 2. **Files that define the guards themselves** — the MCP client configuration, which is\r\n * where this server's `SSH_*` variables live, plus autostart locations. Downloading\r\n * onto one of these replaces the policy that governs the next run, so the guard would\r\n * be self-defeating without it.\r\n *\r\n * Ordinary operational files are intentionally *not* here. `.env` uploads, `.bashrc`\r\n * backups and cron fetches are normal deployment work, and blocking them broke real tasks\r\n * to stop an attack that a `python3 -c` one-liner performs anyway.\r\n *\r\n * Matched against the **real** path (symlinks resolved) with `\\` normalized to `/`.\r\n */\r\nexport const PROTECTED_LOCAL_PATH_RULES: CommandRule[] = [\r\n {\r\n pattern: /(^|\\/)\\.ssh(\\/|$)/i,\r\n reason: \"SSH key directory\",\r\n },\r\n {\r\n pattern: /(^|\\/)authorized_keys$/i,\r\n reason: \"SSH authorized_keys\",\r\n },\r\n {\r\n pattern: /(^|\\/)id_(rsa|dsa|ecdsa|ed25519)/i,\r\n reason: \"private key file\",\r\n },\r\n {\r\n pattern: /\\.(pem|ppk|p12|pfx|key)$/i,\r\n reason: \"private key or certificate file\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.gnupg(\\/|$)/i,\r\n reason: \"GPG keyring\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.aws(\\/|$)/i,\r\n reason: \"AWS credentials\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.kube(\\/|$)/i,\r\n reason: \"kubeconfig\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.docker(\\/|$)/i,\r\n reason: \"docker credentials\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.npmrc$/i,\r\n reason: \"npm credentials\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.netrc$/i,\r\n reason: \"netrc credentials\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.claude(\\.json)?(\\/|$)/i,\r\n reason: \"MCP client configuration\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.mcp\\.json$/i,\r\n reason: \"MCP server configuration\",\r\n },\r\n {\r\n pattern: /(^|\\/)claude_desktop_config\\.json$/i,\r\n reason: \"MCP client configuration\",\r\n },\r\n {\r\n pattern: /(^|\\/)start ?menu\\/programs\\/startup(\\/|$)/i,\r\n reason: \"Windows startup folder\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.config\\/autostart(\\/|$)/i,\r\n reason: \"desktop autostart directory\",\r\n },\r\n];\r\n","/**\r\n * Environment variables that define the host, one MCP server entry per host.\r\n *\r\n * This is the only way to configure the server. Each variable is a plain value, so an\r\n * MCP client's `env` block reads like a connection form — no JSON inside JSON, no\r\n * escaped quotes, and a typo shows up on the line it is on.\r\n */\r\nexport const SINGLE_HOST_ENV = {\r\n /** Presence of this variable is what registers a host at all. */\r\n HOST: \"SSH_HOST\",\r\n PORT: \"SSH_PORT\",\r\n USER: \"SSH_USER\",\r\n /** Path to a private key file. */\r\n KEY: \"SSH_KEY\",\r\n PASSPHRASE: \"SSH_PASSPHRASE\",\r\n PASSWORD: \"SSH_PASSWORD\",\r\n /** Pinned host key, `SHA256:…`. */\r\n HOST_KEY: \"SSH_HOST_KEY\",\r\n /** Alias shown to the model; defaults to the hostname. */\r\n NAME: \"SSH_NAME\",\r\n DESCRIPTION: \"SSH_DESCRIPTION\",\r\n /** Directory new sessions start in. */\r\n CWD: \"SSH_CWD\",\r\n /**\r\n * Command whose stdin/stdout carry the SSH stream, like OpenSSH's `ProxyCommand`.\r\n * `%h` / `%p` / `%r` expand to the first hop's host, port and user.\r\n */\r\n PROXY_COMMAND: \"SSH_PROXY_COMMAND\",\r\n /** Presence of this variable routes the connection through a jump host. */\r\n JUMP_HOST: \"SSH_JUMP_HOST\",\r\n JUMP_PORT: \"SSH_JUMP_PORT\",\r\n /** Defaults to `SSH_USER`. */\r\n JUMP_USER: \"SSH_JUMP_USER\",\r\n /** Path to the jump host's private key file; defaults to `SSH_KEY`. */\r\n JUMP_KEY: \"SSH_JUMP_KEY\",\r\n JUMP_PASSPHRASE: \"SSH_JUMP_PASSPHRASE\",\r\n JUMP_PASSWORD: \"SSH_JUMP_PASSWORD\",\r\n /** Pinned jump host key, `SHA256:…`. */\r\n JUMP_HOST_KEY: \"SSH_JUMP_HOST_KEY\",\r\n READONLY: \"SSH_READONLY\",\r\n ALLOW_SUDO: \"SSH_ALLOW_SUDO\",\r\n /** Comma-separated binary names. */\r\n ALLOW_COMMANDS: \"SSH_ALLOW_COMMANDS\",\r\n /** Comma-separated regex sources. */\r\n DENY_PATTERNS: \"SSH_DENY_PATTERNS\",\r\n /** Comma-separated absolute path prefixes. */\r\n ALLOWED_PATHS: \"SSH_ALLOWED_PATHS\",\r\n EXEC_TIMEOUT_MS: \"SSH_EXEC_TIMEOUT_MS\",\r\n CONNECT_TIMEOUT_MS: \"SSH_CONNECT_TIMEOUT_MS\",\r\n MAX_OUTPUT_CHARACTERS: \"SSH_MAX_OUTPUT\",\r\n MAX_READ_FILE_BYTES: \"SSH_MAX_READ_BYTES\",\r\n} as const;\r\n\r\n/** Accepted spellings for a true boolean env value. */\r\nexport const TRUTHY_ENV_VALUES = [\"true\", \"1\", \"yes\", \"on\"];\r\n\r\n/** Accepted spellings for a false boolean env value. */\r\nexport const FALSY_ENV_VALUES = [\"false\", \"0\", \"no\", \"off\"];\r\n","import { LOG_LEVEL_ENV } from \"@/_defs\";\r\n\r\nexport type LogLevel = \"silent\" | \"debug\" | \"info\" | \"warn\" | \"error\";\r\n\r\nexport interface Logger {\r\n /** Returns a logger that appends `meta` to every record it writes. */\r\n child(meta: Record<string, unknown>): Logger;\r\n debug(message: string, meta?: Record<string, unknown>): void;\r\n info(message: string, meta?: Record<string, unknown>): void;\r\n warn(message: string, meta?: Record<string, unknown>): void;\r\n error(error: unknown, message: string, meta?: Record<string, unknown>): void;\r\n}\r\n\r\nconst LEVEL_WEIGHTS: Record<LogLevel, number> = {\r\n silent: 0,\r\n debug: 1,\r\n info: 2,\r\n warn: 3,\r\n error: 4,\r\n};\r\n\r\n/** Meta keys whose values never reach the log output. */\r\nconst SECRET_META_KEYS = [\"password\", \"passphrase\", \"privatekey\", \"privatekeypath\", \"secret\", \"token\"];\r\n\r\n/**\r\n * Set once a write to stderr failed; every later line is dropped. When the MCP client dies, nothing\r\n * reads stderr any more and the next line fails with EPIPE. Logging that failure fails again, and the\r\n * loop starves the event loop — the server stays behind at 100% CPU and never reaches its own\r\n * process.exit (measured 2026-10-13 in @akms/mcp-db under Claude Code's `cmd /c` launch).\r\n */\r\nlet stderrBroken = false;\r\nprocess.stderr.on(\"error\", () => {\r\n stderrBroken = true;\r\n});\r\n\r\nfunction resolveThreshold(): LogLevel {\r\n const configured = process.env[LOG_LEVEL_ENV];\r\n if (configured == null) {\r\n return \"info\";\r\n }\r\n\r\n // Own-property check: a plain `LEVEL_WEIGHTS[x] == null` test walks the prototype, so\r\n // SSH_MCP_LOG_LEVEL=constructor would pass validation and disable all filtering.\r\n const normalized = configured.toLowerCase() as LogLevel;\r\n if (Object.hasOwn(LEVEL_WEIGHTS, normalized) == false) {\r\n return \"info\";\r\n }\r\n\r\n return normalized;\r\n}\r\n\r\nfunction maskMeta(meta: Record<string, unknown>): Record<string, unknown> {\r\n const masked: Record<string, unknown> = {};\r\n for (const [key, value] of Object.entries(meta)) {\r\n if (SECRET_META_KEYS.includes(key.toLowerCase()) == true) {\r\n masked[key] = \"***\";\r\n continue;\r\n }\r\n\r\n masked[key] = value;\r\n }\r\n\r\n return masked;\r\n}\r\n\r\n/**\r\n * Writes to stderr only. stdout carries the MCP JSON-RPC stream over stdio transport,\r\n * so a single stray `console.log` corrupts the protocol and kills the session.\r\n */\r\nfunction write(level: LogLevel, message: string, meta: Record<string, unknown>): void {\r\n if (stderrBroken == true) {\r\n return;\r\n }\r\n\r\n const threshold = resolveThreshold();\r\n if (threshold === \"silent\") {\r\n return;\r\n }\r\n\r\n if (LEVEL_WEIGHTS[level] < LEVEL_WEIGHTS[threshold]) {\r\n return;\r\n }\r\n\r\n const timestamp = new Date().toISOString();\r\n const masked = maskMeta(meta);\r\n\r\n let line = `${timestamp} [${level}] ${message}`;\r\n if (Object.keys(masked).length > 0) {\r\n line += ` ${JSON.stringify(masked)}`;\r\n }\r\n\r\n // On POSIX a pipe write is synchronous and can throw EPIPE right here instead of emitting 'error'\r\n try {\r\n process.stderr.write(`${line}\\n`);\r\n }\r\n catch {\r\n stderrBroken = true;\r\n }\r\n}\r\n\r\nfunction createLogger(boundMeta: Record<string, unknown>): Logger {\r\n return {\r\n child(meta: Record<string, unknown>): Logger {\r\n return createLogger({ ...boundMeta, ...meta });\r\n },\r\n debug(message: string, meta?: Record<string, unknown>): void {\r\n write(\"debug\", message, { ...boundMeta, ...meta });\r\n },\r\n info(message: string, meta?: Record<string, unknown>): void {\r\n write(\"info\", message, { ...boundMeta, ...meta });\r\n },\r\n warn(message: string, meta?: Record<string, unknown>): void {\r\n write(\"warn\", message, { ...boundMeta, ...meta });\r\n },\r\n error(error: unknown, message: string, meta?: Record<string, unknown>): void {\r\n const errorMeta: Record<string, unknown> = { ...boundMeta, ...meta };\r\n if (error instanceof Error) {\r\n errorMeta.error_name = error.name;\r\n errorMeta.error_message = error.message;\r\n errorMeta.stack = error.stack;\r\n }\r\n else if (error != null) {\r\n errorMeta.error_message = String(error);\r\n }\r\n\r\n write(\"error\", message, errorMeta);\r\n },\r\n };\r\n}\r\n\r\nexport const $logger = createLogger({});\r\n","/** Output cut to `maxCharacters`, with a note appended when anything was dropped. */\r\nexport interface TruncatedText {\r\n text: string;\r\n truncated: boolean;\r\n}\r\n\r\n/**\r\n * Cuts `text` to `maxCharacters`, appending a note saying how much was dropped.\r\n *\r\n * @returns The possibly-cut text and whether anything was removed.\r\n */\r\nexport function truncateOutput(text: string, maxCharacters: number): TruncatedText {\r\n if (text.length <= maxCharacters) {\r\n return { text: text, truncated: false };\r\n }\r\n\r\n // Never cut between the halves of a surrogate pair: the orphaned half renders as a\r\n // replacement character, so an emoji at the boundary would come back corrupted.\r\n let cutAt = maxCharacters;\r\n const lastKeptCode = text.charCodeAt(cutAt - 1);\r\n if (lastKeptCode >= 0xd800 && lastKeptCode <= 0xdbff) {\r\n cutAt -= 1;\r\n }\r\n\r\n const droppedCharacters = text.length - cutAt;\r\n const kept = text.slice(0, cutAt);\r\n return {\r\n text: `${kept}\\n... [truncated ${droppedCharacters} characters of ${text.length}]`,\r\n truncated: true,\r\n };\r\n}\r\n\r\n/**\r\n * Wraps a value in single quotes for POSIX shells, escaping embedded quotes as `'\\''`.\r\n * Used for interpolating paths into the `cd` prefix — never for user commands, which\r\n * are passed through verbatim so the remote shell parses them as written.\r\n */\r\nexport function quoteShellArgument(value: string): string {\r\n const escaped = value.replace(/'/g, `'\\\\''`);\r\n return `'${escaped}'`;\r\n}\r\n\r\n/** Renders a byte count as `B` / `KB` / `MB` for display in tool output. */\r\nexport function formatBytes(bytes: number): string {\r\n if (bytes < 1024) {\r\n return `${bytes} B`;\r\n }\r\n\r\n if (bytes < 1024 * 1024) {\r\n const kilobytes = bytes / 1024;\r\n return `${kilobytes.toFixed(1)} KB`;\r\n }\r\n\r\n const megabytes = bytes / (1024 * 1024);\r\n return `${megabytes.toFixed(1)} MB`;\r\n}\r\n","import fs from \"node:fs\";\r\nimport os from \"node:os\";\r\nimport path from \"node:path\";\r\n\r\n/**\r\n * Absolute path of an executable on `PATH`, or null. On Windows only `PATHEXT` suffixes are\r\n * tried — npm drops an extensionless POSIX shell script next to every `.cmd` shim, and that\r\n * file exists but cannot be launched.\r\n */\r\nexport function findExecutable(name: string): string | null {\r\n const searchPath = process.env.PATH ?? \"\";\r\n const directories = searchPath.split(path.delimiter).filter((directory) => directory !== \"\");\r\n\r\n let suffixes = [\"\"];\r\n if (process.platform === \"win32\") {\r\n const pathExt = process.env.PATHEXT ?? \".COM;.EXE;.BAT;.CMD\";\r\n suffixes = pathExt.split(\";\").filter((suffix) => suffix !== \"\");\r\n }\r\n\r\n for (const directory of directories) {\r\n for (const suffix of suffixes) {\r\n const candidate = path.join(directory, `${name}${suffix}`);\r\n if (fs.existsSync(candidate) == true && fs.statSync(candidate).isFile() == true) {\r\n return candidate;\r\n }\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\n/** Expands a leading `~` to the local home directory and returns an absolute path. */\r\nexport function expandHomePath(inputPath: string): string {\r\n let expanded = inputPath;\r\n if (inputPath === \"~\") {\r\n expanded = os.homedir();\r\n }\r\n else if (inputPath.startsWith(\"~/\") == true || inputPath.startsWith(\"~\\\\\") == true) {\r\n expanded = path.join(os.homedir(), inputPath.slice(2));\r\n }\r\n\r\n return path.resolve(expanded);\r\n}\r\n\r\n/** True for POSIX absolute remote paths — the only form the path guard can reason about. */\r\nexport function isAbsoluteRemotePath(remotePath: string): boolean {\r\n return remotePath.startsWith(\"/\");\r\n}\r\n\r\n/** Collapses `.`, `..` and duplicate slashes using POSIX rules, whatever the local OS is. */\r\nexport function normalizeRemotePath(remotePath: string): string {\r\n return path.posix.normalize(remotePath);\r\n}\r\n\r\n/** True when `candidate` is `allowedPrefix` itself or sits underneath it. */\r\nexport function isRemotePathWithin(candidate: string, allowedPrefix: string): boolean {\r\n const normalizedCandidate = normalizeRemotePath(candidate);\r\n const normalizedPrefix = normalizeRemotePath(allowedPrefix).replace(/\\/+$/, \"\");\r\n\r\n if (normalizedPrefix === \"\" || normalizedPrefix === \"/\") {\r\n return true;\r\n }\r\n\r\n if (normalizedCandidate === normalizedPrefix) {\r\n return true;\r\n }\r\n\r\n return normalizedCandidate.startsWith(`${normalizedPrefix}/`);\r\n}\r\n","import fs from \"node:fs\";\r\n\r\nimport {\r\n DEFAULT_CONNECT_TIMEOUT_MS,\r\n DEFAULT_EXEC_TIMEOUT_MS,\r\n DEFAULT_MAX_OUTPUT_CHARACTERS,\r\n DEFAULT_MAX_READ_FILE_BYTES,\r\n DEFAULT_SSH_PORT,\r\n FALSY_ENV_VALUES,\r\n SINGLE_HOST_ENV,\r\n TRUTHY_ENV_VALUES,\r\n} from \"@/_defs\";\r\nimport { $logger, expandHomePath } from \"@/_libs\";\r\nimport type { SshHostProfile, SshHostSummary, SshPolicy } from \"@/_types\";\r\n\r\n/** Trimmed value, or undefined when the variable is unset or blank. */\r\nfunction readEnv(env: NodeJS.ProcessEnv, name: string): string | undefined {\r\n const value = env[name];\r\n if (value == null) {\r\n return undefined;\r\n }\r\n\r\n const trimmed = value.trim();\r\n if (trimmed === \"\") {\r\n return undefined;\r\n }\r\n\r\n return trimmed;\r\n}\r\n\r\n/**\r\n * @throws {Error} If the value is set but is neither a truthy nor a falsy spelling —\r\n * `SSH_READONLY=ture` must fail loudly, not resolve to \"not read-only\".\r\n */\r\nfunction readBooleanEnv(env: NodeJS.ProcessEnv, name: string): boolean | undefined {\r\n const value = readEnv(env, name);\r\n if (value == null) {\r\n return undefined;\r\n }\r\n\r\n const normalized = value.toLowerCase();\r\n if (TRUTHY_ENV_VALUES.includes(normalized) == true) {\r\n return true;\r\n }\r\n\r\n if (FALSY_ENV_VALUES.includes(normalized) == false) {\r\n throw new Error(`${name} must be one of ${[...TRUTHY_ENV_VALUES, ...FALSY_ENV_VALUES].join(\", \")} (got \"${value}\")`);\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * @throws {Error} If the value is set but is not a positive integer.\r\n */\r\nfunction readIntegerEnv(env: NodeJS.ProcessEnv, name: string): number | undefined {\r\n const value = readEnv(env, name);\r\n if (value == null) {\r\n return undefined;\r\n }\r\n\r\n const parsed = Number(value);\r\n if (Number.isInteger(parsed) == false || parsed <= 0) {\r\n throw new Error(`${name} must be a positive integer (got \"${value}\")`);\r\n }\r\n\r\n return parsed;\r\n}\r\n\r\n/** Comma-separated list, blanks dropped. */\r\nfunction readListEnv(env: NodeJS.ProcessEnv, name: string): string[] | undefined {\r\n const value = readEnv(env, name);\r\n if (value == null) {\r\n return undefined;\r\n }\r\n\r\n const items = value.split(\",\").map((item) => item.trim()).filter((item) => item !== \"\");\r\n if (items.length === 0) {\r\n return undefined;\r\n }\r\n\r\n return items;\r\n}\r\n\r\n/**\r\n * Compiles `SSH_DENY_PATTERNS` at startup.\r\n *\r\n * Lazily compiling them at first use meant a typo'd regex logged one stderr line and then\r\n * simply wasn't in force — the operator's own deny rule, silently absent, on a server that\r\n * reported \"host profile loaded\". Every other malformed value aborts startup; so does this.\r\n *\r\n * @throws {Error} If a pattern is not a valid regular expression.\r\n */\r\nfunction compileDenyPatterns(sources: string[]): RegExp[] {\r\n return sources.map((source) => {\r\n try {\r\n return new RegExp(source, \"i\");\r\n }\r\n catch (ex) {\r\n const _logger = $logger.child({ context: \"compileDenyPatterns\" });\r\n _logger.error(ex, \"denyPatterns entry is not a valid regular expression\");\r\n\r\n let detail = String(ex);\r\n if (ex instanceof Error) {\r\n detail = ex.message;\r\n }\r\n\r\n throw new Error(`${SINGLE_HOST_ENV.DENY_PATTERNS} has an invalid entry /${source}/: ${detail}`);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Builds the host profile from the `SSH_*` variables, or null when `SSH_HOST` is unset.\r\n *\r\n * One MCP server entry fronts one host, so every setting is a plain variable — no JSON\r\n * inside JSON, no escaped quotes, and a typo shows up on the line it is on. A malformed\r\n * value aborts startup rather than being ignored, because silently dropping `SSH_READONLY`\r\n * would leave a host the operator believed was read-only fully writable.\r\n *\r\n * Flow:\r\n * 1) Guard: no `SSH_HOST` means nothing is registered — the server still starts and says so\r\n * 2) Resolve the policy over the built-in permissive stance; each variable is opt-in restriction\r\n * 3) Assemble the connection fields\r\n * 4) Expand the private key path and check it exists\r\n * 5) Route: proxy command and / or jump host\r\n *\r\n * @param env - Where the `SSH_*` variables are read from. Defaults to the process\r\n * environment; the `config` command passes the entry it is about to write.\r\n * @throws {Error} If `SSH_HOST` is set without `SSH_USER`, a `SSH_JUMP_*` variable is set\r\n * without `SSH_JUMP_HOST`, or a typed value is malformed.\r\n */\r\nexport function loadHostProfile(env: NodeJS.ProcessEnv = process.env): SshHostProfile | null {\r\n // (1) Guard — a missing host is not an error: the server reports \"not configured\"\r\n // through the tools, which is a far clearer signal to the model than a dead server.\r\n const host = readEnv(env, SINGLE_HOST_ENV.HOST);\r\n if (host == null) {\r\n const _startupLogger = $logger.child({ context: \"loadHostProfile\" });\r\n _startupLogger.warn(\"no host is registered; set the connection variables and restart\", {\r\n env_vars: [SINGLE_HOST_ENV.HOST, SINGLE_HOST_ENV.USER],\r\n });\r\n return null;\r\n }\r\n\r\n const username = readEnv(env, SINGLE_HOST_ENV.USER);\r\n if (username == null) {\r\n throw new Error(`${SINGLE_HOST_ENV.HOST} is set, so ${SINGLE_HOST_ENV.USER} is required`);\r\n }\r\n\r\n const name = readEnv(env, SINGLE_HOST_ENV.NAME) ?? host;\r\n const _logger = $logger.child({ context: \"loadHostProfile\", profile: name });\r\n\r\n // (2) Policy — the built-in stance is permissive: an unconfigured host allows\r\n // everything except the catastrophe rules, because a guard nobody can work through\r\n // gets turned off wholesale rather than tuned.\r\n const allowCommands = readListEnv(env, SINGLE_HOST_ENV.ALLOW_COMMANDS) ?? [];\r\n\r\n // `ssh_connect` probes with `pwd` to settle the session's starting directory; without\r\n // this an allow-list profile could never open a session at all.\r\n if (allowCommands.length > 0 && allowCommands.includes(\"pwd\") == false) {\r\n allowCommands.push(\"pwd\");\r\n }\r\n\r\n const policy: SshPolicy = {\r\n readonly: readBooleanEnv(env, SINGLE_HOST_ENV.READONLY) ?? false,\r\n allowSudo: readBooleanEnv(env, SINGLE_HOST_ENV.ALLOW_SUDO) ?? true,\r\n execTimeoutMs: readIntegerEnv(env, SINGLE_HOST_ENV.EXEC_TIMEOUT_MS) ?? DEFAULT_EXEC_TIMEOUT_MS,\r\n connectTimeoutMs: readIntegerEnv(env, SINGLE_HOST_ENV.CONNECT_TIMEOUT_MS) ?? DEFAULT_CONNECT_TIMEOUT_MS,\r\n maxOutputCharacters: readIntegerEnv(env, SINGLE_HOST_ENV.MAX_OUTPUT_CHARACTERS) ?? DEFAULT_MAX_OUTPUT_CHARACTERS,\r\n maxReadFileBytes: readIntegerEnv(env, SINGLE_HOST_ENV.MAX_READ_FILE_BYTES) ?? DEFAULT_MAX_READ_FILE_BYTES,\r\n allowCommands: allowCommands,\r\n denyPatterns: compileDenyPatterns(readListEnv(env, SINGLE_HOST_ENV.DENY_PATTERNS) ?? []),\r\n allowedPaths: readListEnv(env, SINGLE_HOST_ENV.ALLOWED_PATHS) ?? [],\r\n };\r\n\r\n // (3) Connection\r\n const profile: SshHostProfile = {\r\n name: name,\r\n host: host,\r\n port: readIntegerEnv(env, SINGLE_HOST_ENV.PORT) ?? DEFAULT_SSH_PORT,\r\n username: username,\r\n description: readEnv(env, SINGLE_HOST_ENV.DESCRIPTION),\r\n passphrase: readEnv(env, SINGLE_HOST_ENV.PASSPHRASE),\r\n password: readEnv(env, SINGLE_HOST_ENV.PASSWORD),\r\n hostKeyFingerprint: readEnv(env, SINGLE_HOST_ENV.HOST_KEY),\r\n defaultCwd: readEnv(env, SINGLE_HOST_ENV.CWD),\r\n policy: policy,\r\n };\r\n\r\n // (4) Key path — checked at load, not at first use: a typo would otherwise surface as\r\n // a connection failure minutes later, with the path withheld from the message.\r\n const keyPath = readEnv(env, SINGLE_HOST_ENV.KEY);\r\n if (keyPath != null) {\r\n const expandedKeyPath = expandHomePath(keyPath);\r\n profile.privateKeyPath = expandedKeyPath;\r\n\r\n if (fs.existsSync(expandedKeyPath) == false) {\r\n _logger.warn(`${SINGLE_HOST_ENV.KEY} does not exist; connections will fail`, {\r\n private_key_path: expandedKeyPath,\r\n });\r\n }\r\n }\r\n\r\n // (5) Route — the proxy command carries the first hop (the jump host when both are\r\n // set), as an OpenSSH `ProxyCommand` on the jump host's own config entry would.\r\n profile.proxyCommand = readEnv(env, SINGLE_HOST_ENV.PROXY_COMMAND);\r\n\r\n const jumpHost = readEnv(env, SINGLE_HOST_ENV.JUMP_HOST);\r\n if (jumpHost == null) {\r\n // A jump setting without a jump host is a half-finished entry; ignoring it would\r\n // quietly connect straight to the target the operator meant to put behind a hop.\r\n const strayJumpVariables = [\r\n SINGLE_HOST_ENV.JUMP_PORT,\r\n SINGLE_HOST_ENV.JUMP_USER,\r\n SINGLE_HOST_ENV.JUMP_KEY,\r\n SINGLE_HOST_ENV.JUMP_PASSPHRASE,\r\n SINGLE_HOST_ENV.JUMP_PASSWORD,\r\n SINGLE_HOST_ENV.JUMP_HOST_KEY,\r\n ].filter((variable) => readEnv(env, variable) != null);\r\n\r\n if (strayJumpVariables.length > 0) {\r\n throw new Error(`${strayJumpVariables.join(\", \")} is set, so ${SINGLE_HOST_ENV.JUMP_HOST} is required`);\r\n }\r\n }\r\n else {\r\n profile.jump = {\r\n host: jumpHost,\r\n port: readIntegerEnv(env, SINGLE_HOST_ENV.JUMP_PORT) ?? DEFAULT_SSH_PORT,\r\n username: readEnv(env, SINGLE_HOST_ENV.JUMP_USER) ?? username,\r\n password: readEnv(env, SINGLE_HOST_ENV.JUMP_PASSWORD),\r\n hostKeyFingerprint: readEnv(env, SINGLE_HOST_ENV.JUMP_HOST_KEY),\r\n };\r\n\r\n // The target's key (and its passphrase) is the default, as `ssh -J` authenticates\r\n // both hops with the same identity unless told otherwise.\r\n const jumpKeyPath = readEnv(env, SINGLE_HOST_ENV.JUMP_KEY);\r\n if (jumpKeyPath != null) {\r\n profile.jump.privateKeyPath = expandHomePath(jumpKeyPath);\r\n profile.jump.passphrase = readEnv(env, SINGLE_HOST_ENV.JUMP_PASSPHRASE);\r\n\r\n if (fs.existsSync(profile.jump.privateKeyPath) == false) {\r\n _logger.warn(`${SINGLE_HOST_ENV.JUMP_KEY} does not exist; connections will fail`, {\r\n private_key_path: profile.jump.privateKeyPath,\r\n });\r\n }\r\n }\r\n else {\r\n profile.jump.privateKeyPath = profile.privateKeyPath;\r\n profile.jump.passphrase = readEnv(env, SINGLE_HOST_ENV.JUMP_PASSPHRASE) ?? profile.passphrase;\r\n }\r\n }\r\n\r\n _logger.info(\"host profile loaded\", {\r\n host: host,\r\n port: profile.port,\r\n username: username,\r\n readonly: policy.readonly,\r\n jump_host: profile.jump?.host,\r\n via_proxy_command: profile.proxyCommand != null,\r\n });\r\n\r\n return profile;\r\n}\r\n\r\n/** Strips every secret so the profile can be described back to the model. */\r\nexport function toHostSummary(profile: SshHostProfile): SshHostSummary {\r\n let authMethod: SshHostSummary[\"authMethod\"] = \"agent\";\r\n if (profile.privateKeyPath != null) {\r\n authMethod = \"privateKey\";\r\n }\r\n else if (profile.password != null) {\r\n authMethod = \"password\";\r\n }\r\n\r\n const summary: SshHostSummary = {\r\n name: profile.name,\r\n host: profile.host,\r\n port: profile.port,\r\n username: profile.username,\r\n description: profile.description,\r\n authMethod: authMethod,\r\n viaProxyCommand: profile.proxyCommand != null,\r\n readonly: profile.policy.readonly,\r\n allowSudo: profile.policy.allowSudo,\r\n allowedPaths: profile.policy.allowedPaths,\r\n allowCommands: profile.policy.allowCommands,\r\n };\r\n\r\n if (profile.jump != null) {\r\n summary.jump = {\r\n host: profile.jump.host,\r\n port: profile.jump.port,\r\n username: profile.jump.username,\r\n };\r\n }\r\n\r\n return summary;\r\n}\r\n","import {\r\n BARE_REDIRECTION_OPERATOR_PATTERN,\r\n BENIGN_REDIRECT_PATTERN,\r\n COMMAND_WRAPPER_BINARIES,\r\n DANGEROUS_ARGUMENT_RULES,\r\n DANGEROUS_COMMAND_RULES,\r\n DANGEROUS_PIPELINE_RULES,\r\n NUMERIC_OPERAND_PATTERN,\r\n OUTPUT_REDIRECT_PATTERN,\r\n PRIVILEGE_ESCALATION_BINARIES,\r\n REDIRECTION_TOKEN_PATTERN,\r\n SHELL_KEYWORD_TOKENS,\r\n SHELL_PAYLOAD_BINARIES,\r\n SHELL_TOKEN_PATTERN,\r\n WRAPPER_VALUE_FLAGS,\r\n WRITE_COMMAND_BINARIES,\r\n WRITE_USAGE_RULES,\r\n} from \"@/_defs\";\r\nimport type { GuardVerdict, SshPolicy } from \"@/_types\";\r\n\r\n/** Membership lists as sets — these are hit once per token, per segment, per command. */\r\nconst WRAPPER_BINARY_SET = new Set(COMMAND_WRAPPER_BINARIES);\r\nconst PRIVILEGE_BINARY_SET = new Set(PRIVILEGE_ESCALATION_BINARIES);\r\nconst SHELL_PAYLOAD_BINARY_SET = new Set(SHELL_PAYLOAD_BINARIES);\r\nconst WRITE_BINARY_SET = new Set(WRITE_COMMAND_BINARIES);\r\nconst SHELL_KEYWORD_SET = new Set(SHELL_KEYWORD_TOKENS);\r\n\r\n/** `bash -c \"sh -c '…'\"` nests; three levels is far past anything legitimate. */\r\nconst MAX_INSPECTION_DEPTH = 3;\r\n\r\nconst ASSIGNMENT_TOKEN_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=/;\r\n\r\n/** `-c` alone, or bundled into a short-flag cluster like `-lc` / `-exc`. */\r\nconst SHELL_COMMAND_FLAG_PATTERN = /^-[a-z]*c$/i;\r\n\r\n/**\r\n * Rewrites shell constructs that change how the text tokenizes but not what it runs,\r\n * so the rules below see the same command the remote shell will.\r\n *\r\n * - `\\` + newline is a line continuation. POSIX removes it entirely — folding it to a\r\n * space instead would split `r\\⏎m` into two words and hide `rm` from every rule, so it\r\n * is removed, and the *canonical* text is what gets executed (see `canonicalizeCommand`).\r\n * - `${IFS}` / `$IFS` expand to whitespace, the standard way to write a command with no\r\n * literal spaces in it.\r\n */\r\nexport function normalizeCommandText(command: string): string {\r\n return command\r\n .replace(/\\\\\\r?\\n/g, \"\")\r\n .replace(/\\$\\{IFS\\}/g, \" \")\r\n .replace(/\\$IFS/g, \" \");\r\n}\r\n\r\n/**\r\n * The exact text that must be sent to the remote shell.\r\n *\r\n * Validating one string and executing another is a bypass by construction: the guard saw\r\n * `r m -rf /` while the host ran `rm -rf /`. Callers run this, then send its result.\r\n */\r\nexport function canonicalizeCommand(command: string): string {\r\n return normalizeCommandText(command);\r\n}\r\n\r\n/**\r\n * Splits a command line on `;`, `&&`, `||`, `|`, `&` and newlines, leaving quoted text intact.\r\n * `2>&1` keeps its `&` — a `&` directly after `>` is redirection, not a background operator.\r\n */\r\nexport function splitCommandSegments(command: string): string[] {\r\n const segments: string[] = [];\r\n let current = \"\";\r\n let openQuote: string | null = null;\r\n\r\n for (let index = 0; index < command.length; index++) {\r\n const character = command[index];\r\n const nextCharacter = command[index + 1];\r\n\r\n if (openQuote != null) {\r\n current += character;\r\n // Single quotes take no escapes in POSIX; double quotes do.\r\n if (character === \"\\\\\" && openQuote === `\"` && nextCharacter != null) {\r\n current += nextCharacter;\r\n index++;\r\n continue;\r\n }\r\n if (character === openQuote) {\r\n openQuote = null;\r\n }\r\n continue;\r\n }\r\n\r\n if (character === \"'\" || character === `\"`) {\r\n openQuote = character;\r\n current += character;\r\n continue;\r\n }\r\n\r\n if (character === \"\\\\\") {\r\n current += character;\r\n if (nextCharacter != null) {\r\n current += nextCharacter;\r\n index++;\r\n }\r\n continue;\r\n }\r\n\r\n if (character === \"&\" && current.trimEnd().endsWith(\">\") == true) {\r\n current += character;\r\n continue;\r\n }\r\n\r\n const isDoubleOperator = (character === \"&\" && nextCharacter === \"&\") || (character === \"|\" && nextCharacter === \"|\");\r\n if (isDoubleOperator == true) {\r\n segments.push(current);\r\n current = \"\";\r\n index++;\r\n continue;\r\n }\r\n\r\n if (character === \";\" || character === \"\\n\" || character === \"|\" || character === \"&\") {\r\n segments.push(current);\r\n current = \"\";\r\n continue;\r\n }\r\n\r\n current += character;\r\n }\r\n\r\n segments.push(current);\r\n\r\n return segments\r\n .map((segment) => segment.trim())\r\n .filter((segment) => segment !== \"\");\r\n}\r\n\r\n/**\r\n * Drops quoted spans so `echo \"a > b\"` isn't read as a redirection.\r\n *\r\n * Quote state is checked *before* the backslash branch: POSIX single quotes take no\r\n * escapes, so treating `'x\\'` as an escaped quote left the span open and swallowed the\r\n * rest of the line — including a real `> /etc/…` redirection.\r\n */\r\nexport function stripQuotedText(segment: string): string {\r\n let result = \"\";\r\n let openQuote: string | null = null;\r\n\r\n for (let index = 0; index < segment.length; index++) {\r\n const character = segment[index];\r\n\r\n if (openQuote === \"'\") {\r\n if (character === \"'\") {\r\n openQuote = null;\r\n }\r\n continue;\r\n }\r\n\r\n if (openQuote === `\"`) {\r\n if (character === \"\\\\\") {\r\n index++;\r\n continue;\r\n }\r\n if (character === `\"`) {\r\n openQuote = null;\r\n }\r\n continue;\r\n }\r\n\r\n if (character === \"\\\\\") {\r\n index++;\r\n continue;\r\n }\r\n\r\n if (character === \"'\" || character === `\"`) {\r\n openQuote = character;\r\n continue;\r\n }\r\n\r\n result += character;\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/** Splits a segment into shell words, keeping quoted spans as one word. */\r\nfunction tokenizeSegment(segment: string): string[] {\r\n const tokens = segment.match(SHELL_TOKEN_PATTERN);\r\n if (tokens == null) {\r\n return [];\r\n }\r\n\r\n return tokens;\r\n}\r\n\r\n/** Removes quoting so an operand can be compared against a path pattern. */\r\nfunction unquoteToken(token: string): string {\r\n if (token.includes(\"'\") == false && token.includes(`\"`) == false && token.includes(\"\\\\\") == false) {\r\n return token;\r\n }\r\n\r\n return token\r\n // ANSI-C quoting: `$'rm'` is the word `rm`, and dropping only the quotes would\r\n // leave `$rm` — a binary name no rule matches.\r\n .replace(/\\$'([^']*)'/g, \"$1\")\r\n .replace(/'([^']*)'/g, \"$1\")\r\n .replace(/\"([^\"]*)\"/g, \"$1\")\r\n .replace(/\\\\(.)/g, \"$1\");\r\n}\r\n\r\n/**\r\n * Collapses `.`, `..` and duplicate slashes in an operand before it is matched.\r\n *\r\n * `rm -rf //*`, `/./*` and `/bin/../bin` all name the same targets as the plain forms,\r\n * and a literal-spelling comparison would miss every one of them. Trailing glob characters\r\n * are preserved because the patterns match on them.\r\n */\r\nfunction normalizeOperand(operand: string): string {\r\n if (operand.startsWith(\"/\") == false) {\r\n return operand;\r\n }\r\n\r\n let globSuffix = \"\";\r\n if (operand.endsWith(\"*\") == true) {\r\n globSuffix = \"*\";\r\n }\r\n\r\n const withoutGlob = operand.slice(0, operand.length - globSuffix.length);\r\n const collapsed = withoutGlob.replace(/\\/{2,}/g, \"/\").replace(/\\/\\.(?=\\/|$)/g, \"/\").replace(/\\/{2,}/g, \"/\");\r\n\r\n if (collapsed === \"\") {\r\n return `/${globSuffix}`;\r\n }\r\n\r\n return `${collapsed}${globSuffix}`;\r\n}\r\n\r\n/**\r\n * Commands nested inside the text: `$(…)`, backticks, and process substitution\r\n * `<(…)` / `>(…)`. Each runs as a command in its own right.\r\n */\r\nexport function extractSubstitutions(command: string): string[] {\r\n const found: string[] = [];\r\n\r\n const nestedPattern = /(?:\\$|<|>)\\(([^()]*(?:\\([^()]*\\)[^()]*)*)\\)/g;\r\n let nestedMatch = nestedPattern.exec(command);\r\n while (nestedMatch != null) {\r\n found.push(nestedMatch[1]);\r\n nestedMatch = nestedPattern.exec(command);\r\n }\r\n\r\n const backtickPattern = /`([^`]*)`/g;\r\n let backtickMatch = backtickPattern.exec(command);\r\n while (backtickMatch != null) {\r\n found.push(backtickMatch[1]);\r\n backtickMatch = backtickPattern.exec(command);\r\n }\r\n\r\n return found.filter((substitution) => substitution.trim() !== \"\");\r\n}\r\n\r\nexport interface SegmentAnalysis {\r\n /** Wrapper binaries preceding the real command, in order — e.g. `[\"sudo\", \"env\"]`. */\r\n wrappers: string[];\r\n /** Basename of the binary that actually runs; empty when the segment has no command. */\r\n binary: string;\r\n /** Flags that follow the binary, unquoted. */\r\n flags: string[];\r\n /** Non-flag operands that follow the binary, unquoted. */\r\n operands: string[];\r\n /**\r\n * The segment reduced to `<basename> <args…>`, with assignment, redirection, wrapper\r\n * and shell-keyword prefixes removed. Rules are `^`-anchored, so they must see the\r\n * command at position zero — otherwise `nohup rm -rf /` and `/sbin/reboot` slide past.\r\n */\r\n normalizedText: string;\r\n /** Tokens of the segment, computed once and reused. */\r\n tokens: string[];\r\n}\r\n\r\n/**\r\n * Walks past `FOO=bar` assignments, redirection tokens, shell keywords, wrapper binaries,\r\n * their flags and their numeric operands (`timeout 30`) to the command that executes.\r\n */\r\nexport function analyzeSegment(segment: string): SegmentAnalysis {\r\n const tokens = tokenizeSegment(segment);\r\n const wrappers: string[] = [];\r\n const flags: string[] = [];\r\n const operands: string[] = [];\r\n\r\n let binary = \"\";\r\n let binaryIndex = -1;\r\n let expectRedirectionTarget = false;\r\n let expectFlagValue = false;\r\n\r\n for (let index = 0; index < tokens.length; index++) {\r\n const token = tokens[index];\r\n\r\n // The filename after a bare `>` / `<` is a redirection target, not a command.\r\n if (expectRedirectionTarget == true) {\r\n expectRedirectionTarget = false;\r\n continue;\r\n }\r\n\r\n // The value of a wrapper flag such as `env -u FOO` / `sudo -u deploy`.\r\n if (expectFlagValue == true) {\r\n expectFlagValue = false;\r\n continue;\r\n }\r\n\r\n if (ASSIGNMENT_TOKEN_PATTERN.test(token) == true) {\r\n continue;\r\n }\r\n\r\n if (REDIRECTION_TOKEN_PATTERN.test(token) == true) {\r\n if (BARE_REDIRECTION_OPERATOR_PATTERN.test(token) == true) {\r\n expectRedirectionTarget = true;\r\n }\r\n continue;\r\n }\r\n\r\n if (token.startsWith(\"-\") == true) {\r\n const currentWrapper = wrappers[wrappers.length - 1];\r\n if (currentWrapper != null) {\r\n const valueFlags = WRAPPER_VALUE_FLAGS[currentWrapper];\r\n if (valueFlags != null && valueFlags.includes(token) == true) {\r\n expectFlagValue = true;\r\n }\r\n }\r\n continue;\r\n }\r\n\r\n if (NUMERIC_OPERAND_PATTERN.test(token) == true && wrappers.length > 0) {\r\n continue;\r\n }\r\n\r\n const unquoted = unquoteToken(token);\r\n const trimmed = unquoted.replace(/^[({!]+|[)}]+$/g, \"\");\r\n if (SHELL_KEYWORD_SET.has(trimmed) == true || trimmed === \"\") {\r\n continue;\r\n }\r\n\r\n const basename = trimmed.split(\"/\").pop() ?? trimmed;\r\n if (basename === \"\") {\r\n continue;\r\n }\r\n\r\n if (WRAPPER_BINARY_SET.has(basename) == true) {\r\n wrappers.push(basename);\r\n continue;\r\n }\r\n\r\n binary = basename;\r\n binaryIndex = index;\r\n break;\r\n }\r\n\r\n if (binaryIndex >= 0) {\r\n for (const token of tokens.slice(binaryIndex + 1)) {\r\n const unquoted = unquoteToken(token);\r\n if (unquoted.startsWith(\"-\") == true) {\r\n flags.push(unquoted);\r\n continue;\r\n }\r\n\r\n operands.push(normalizeOperand(unquoted));\r\n }\r\n }\r\n\r\n // `sudo -i` has wrappers but no following command: the wrapper *is* what runs.\r\n if (binary === \"\" && wrappers.length > 0) {\r\n binary = wrappers[wrappers.length - 1];\r\n }\r\n\r\n let normalizedText = segment;\r\n if (binaryIndex >= 0) {\r\n // The binary is rewritten to its basename so `/sbin/reboot` matches `^reboot`.\r\n normalizedText = [binary, ...tokens.slice(binaryIndex + 1).map(unquoteToken)].join(\" \");\r\n }\r\n\r\n return {\r\n wrappers: wrappers,\r\n binary: binary,\r\n flags: flags,\r\n operands: operands,\r\n normalizedText: normalizedText,\r\n tokens: tokens,\r\n };\r\n}\r\n\r\n/**\r\n * Commands carried as arguments of a shell-style binary, which run in their own right:\r\n * the `-c \"<string>\"` form (including bundled clusters like `-lc`), and everything after\r\n * a `--` argv separator (`runuser -u root -- rm -rf /srv`).\r\n */\r\nfunction extractShellPayloads(analysis: SegmentAnalysis): string[] {\r\n if (SHELL_PAYLOAD_BINARY_SET.has(analysis.binary) == false) {\r\n return [];\r\n }\r\n\r\n const tokens = analysis.tokens;\r\n const payloads: string[] = [];\r\n\r\n for (let index = 0; index < tokens.length - 1; index++) {\r\n if (SHELL_COMMAND_FLAG_PATTERN.test(tokens[index]) == true || tokens[index] === \"--command\") {\r\n payloads.push(unquoteToken(tokens[index + 1]));\r\n }\r\n }\r\n\r\n const separatorIndex = tokens.indexOf(\"--\");\r\n if (separatorIndex >= 0 && separatorIndex < tokens.length - 1) {\r\n payloads.push(tokens.slice(separatorIndex + 1).map(unquoteToken).join(\" \"));\r\n }\r\n\r\n return payloads;\r\n}\r\n\r\n/**\r\n * Decides whether a command may run against a host.\r\n *\r\n * Ordered cheapest-to-strictest so the reported reason is the most fundamental one:\r\n * pipeline-wide disasters → command substitutions → then, per segment, profile deny\r\n * patterns → privilege escalation → always-dangerous rules → read-only rules →\r\n * allow-list → nested shell payloads.\r\n *\r\n * **Scope.** This is a mistake-stopper, not a sandbox. It reads the command as text, so\r\n * anything that hides the real operation — an interpreter one-liner, an encoded payload,\r\n * a tool named something else — runs unexamined, and no amount of pattern-chasing closes\r\n * that. The layer that actually contains an agent is the remote account's own permissions.\r\n */\r\nexport function inspectCommand(command: string, policy: SshPolicy, depth = 0): GuardVerdict {\r\n const normalizedCommand = normalizeCommandText(command);\r\n const trimmedCommand = normalizedCommand.trim();\r\n if (trimmedCommand === \"\") {\r\n return { allowed: false, reason: \"command is empty\" };\r\n }\r\n\r\n if (depth > MAX_INSPECTION_DEPTH) {\r\n return {\r\n allowed: false,\r\n reason: \"command nests shells or substitutions too deeply to screen reliably\",\r\n offendingText: trimmedCommand,\r\n };\r\n }\r\n\r\n for (const rule of DANGEROUS_PIPELINE_RULES) {\r\n if (rule.pattern.test(trimmedCommand) == true) {\r\n return {\r\n allowed: false,\r\n reason: `blocked as dangerous: ${rule.reason}`,\r\n offendingText: trimmedCommand,\r\n };\r\n }\r\n }\r\n\r\n for (const substitution of extractSubstitutions(trimmedCommand)) {\r\n const verdict = inspectCommand(substitution, policy, depth + 1);\r\n if (verdict.allowed == false) {\r\n return {\r\n allowed: false,\r\n reason: `${verdict.reason} (inside a command substitution)`,\r\n offendingText: substitution,\r\n };\r\n }\r\n }\r\n\r\n for (const segment of splitCommandSegments(trimmedCommand)) {\r\n const verdict = inspectSegment(segment, policy, depth);\r\n if (verdict.allowed == false) {\r\n return verdict;\r\n }\r\n }\r\n\r\n return { allowed: true };\r\n}\r\n\r\nfunction inspectSegment(segment: string, policy: SshPolicy, depth: number): GuardVerdict {\r\n const analysis = analyzeSegment(segment);\r\n\r\n // Per segment, not per command: an operator writing /^rm/ expects it to catch\r\n // `ls && rm -rf x`, and testing only the whole string silently would not.\r\n for (const pattern of policy.denyPatterns) {\r\n if (pattern.test(segment) == true || pattern.test(analysis.normalizedText) == true) {\r\n return {\r\n allowed: false,\r\n reason: `blocked by the profile's denyPatterns rule /${pattern.source}/`,\r\n offendingText: segment,\r\n };\r\n }\r\n }\r\n\r\n if (policy.allowSudo == false) {\r\n const escalation = [...analysis.wrappers, analysis.binary]\r\n .find((name) => PRIVILEGE_BINARY_SET.has(name) == true);\r\n if (escalation != null) {\r\n return {\r\n allowed: false,\r\n reason: `privilege escalation via '${escalation}' is disabled for this profile (set allowSudo: true to permit it)`,\r\n offendingText: segment,\r\n };\r\n }\r\n }\r\n\r\n // The raw segment is only re-tested when normalization actually changed something —\r\n // it carries redirections (`> /dev/sda`) that the normalized form drops.\r\n const shouldTestRawSegment = analysis.normalizedText !== segment;\r\n\r\n for (const rule of DANGEROUS_COMMAND_RULES) {\r\n const matched = rule.pattern.test(analysis.normalizedText) == true\r\n || (shouldTestRawSegment == true && rule.pattern.test(segment) == true);\r\n if (matched == true) {\r\n return {\r\n allowed: false,\r\n reason: `blocked as dangerous: ${rule.reason}`,\r\n offendingText: segment,\r\n };\r\n }\r\n }\r\n\r\n for (const rule of DANGEROUS_ARGUMENT_RULES) {\r\n if (rule.binaries.includes(analysis.binary) == false) {\r\n continue;\r\n }\r\n\r\n if (rule.requiredFlagPattern != null) {\r\n const hasRequiredFlag = analysis.flags.some((flag) => rule.requiredFlagPattern?.test(flag) == true);\r\n if (hasRequiredFlag == false) {\r\n continue;\r\n }\r\n }\r\n\r\n const offendingOperand = analysis.operands.find((operand) => rule.argumentPattern.test(operand) == true);\r\n if (offendingOperand != null) {\r\n return {\r\n allowed: false,\r\n reason: `blocked as dangerous: ${rule.reason}`,\r\n offendingText: `${analysis.binary} … ${offendingOperand}`,\r\n };\r\n }\r\n }\r\n\r\n if (policy.readonly == true) {\r\n const readonlyVerdict = inspectReadOnly(segment, analysis, shouldTestRawSegment);\r\n if (readonlyVerdict.allowed == false) {\r\n return readonlyVerdict;\r\n }\r\n }\r\n\r\n if (policy.allowCommands.length > 0) {\r\n // An empty binary means the segment is only a redirection (`> /etc/cron.d/x`),\r\n // which writes a file without naming a command — it must not skip the allow-list.\r\n if (analysis.binary === \"\") {\r\n return {\r\n allowed: false,\r\n reason: `this profile only permits ${policy.allowCommands.join(\", \")}, and this segment runs no listed command`,\r\n offendingText: segment,\r\n };\r\n }\r\n\r\n if (policy.allowCommands.includes(analysis.binary) == false) {\r\n return {\r\n allowed: false,\r\n reason: `'${analysis.binary}' is not in this profile's allowCommands list (${policy.allowCommands.join(\", \")})`,\r\n offendingText: segment,\r\n };\r\n }\r\n }\r\n\r\n for (const payload of extractShellPayloads(analysis)) {\r\n const verdict = inspectCommand(payload, policy, depth + 1);\r\n if (verdict.allowed == false) {\r\n return {\r\n allowed: false,\r\n reason: `${verdict.reason} (inside a '${analysis.binary} -c' payload)`,\r\n offendingText: payload,\r\n };\r\n }\r\n }\r\n\r\n return { allowed: true };\r\n}\r\n\r\nfunction inspectReadOnly(segment: string, analysis: SegmentAnalysis, shouldTestRawSegment: boolean): GuardVerdict {\r\n if (WRITE_BINARY_SET.has(analysis.binary) == true) {\r\n return {\r\n allowed: false,\r\n reason: `profile is read-only and '${analysis.binary}' can modify the host`,\r\n offendingText: segment,\r\n };\r\n }\r\n\r\n for (const rule of WRITE_USAGE_RULES) {\r\n const matched = rule.pattern.test(analysis.normalizedText) == true\r\n || (shouldTestRawSegment == true && rule.pattern.test(segment) == true);\r\n if (matched == true) {\r\n return {\r\n allowed: false,\r\n reason: `profile is read-only: ${rule.reason}`,\r\n offendingText: segment,\r\n };\r\n }\r\n }\r\n\r\n const withoutQuotes = stripQuotedText(segment);\r\n const withoutBenignRedirects = withoutQuotes.replace(BENIGN_REDIRECT_PATTERN, \"\");\r\n if (OUTPUT_REDIRECT_PATTERN.test(withoutBenignRedirects) == true) {\r\n return {\r\n allowed: false,\r\n reason: \"profile is read-only and the command redirects output into a file\",\r\n offendingText: segment,\r\n };\r\n }\r\n\r\n return { allowed: true };\r\n}\r\n","import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\n\r\nimport { PROTECTED_LOCAL_PATH_RULES, UNSAFE_PATH_CHARACTER_PATTERN } from \"@/_defs\";\r\nimport { $logger, isAbsoluteRemotePath, isRemotePathWithin, normalizeRemotePath } from \"@/_libs\";\r\nimport type { GuardVerdict, SshPolicy } from \"@/_types\";\r\n\r\nexport type RemotePathAccess = \"read\" | \"write\";\r\n\r\n/**\r\n * Decides whether a file tool may touch a remote path.\r\n *\r\n * With `allowedPaths` set, only absolute paths are accepted: a relative path resolves\r\n * against the SFTP session's own working directory, which the guard cannot see, so it\r\n * could escape the allow-list without any way to detect it here.\r\n *\r\n * Symlinks on the remote side are not resolved — the guard has no view of that\r\n * filesystem. `allowedPaths` therefore bounds *names*, not inodes.\r\n */\r\nexport function inspectRemotePath(\r\n remotePath: string,\r\n policy: SshPolicy,\r\n access: RemotePathAccess\r\n): GuardVerdict {\r\n const trimmedPath = remotePath.trim();\r\n if (trimmedPath === \"\") {\r\n return { allowed: false, reason: \"path is empty\" };\r\n }\r\n\r\n if (access === \"write\" && policy.readonly == true) {\r\n return {\r\n allowed: false,\r\n reason: \"profile is read-only, remote writes are not permitted\",\r\n offendingText: trimmedPath,\r\n };\r\n }\r\n\r\n if (policy.allowedPaths.length === 0) {\r\n return { allowed: true };\r\n }\r\n\r\n if (isAbsoluteRemotePath(trimmedPath) == false) {\r\n return {\r\n allowed: false,\r\n reason: `this profile restricts file access to ${policy.allowedPaths.join(\", \")}, so the path must be absolute`,\r\n offendingText: trimmedPath,\r\n };\r\n }\r\n\r\n const normalizedPath = normalizeRemotePath(trimmedPath);\r\n const matched = policy.allowedPaths.some((allowedPath) => isRemotePathWithin(normalizedPath, allowedPath) == true);\r\n if (matched == false) {\r\n return {\r\n allowed: false,\r\n reason: `path is outside this profile's allowedPaths (${policy.allowedPaths.join(\", \")})`,\r\n offendingText: normalizedPath,\r\n };\r\n }\r\n\r\n return { allowed: true };\r\n}\r\n\r\n/**\r\n * Resolves symlinks so the guard compares the file that will actually be touched.\r\n *\r\n * A lexical `path.resolve` is not enough: `~/notes.txt` symlinked to `~/.ssh/id_ed25519`\r\n * matches no rule by name, and both `fastPut` and `fastGet` follow the link. For a path\r\n * that does not exist yet (the normal download case) the parent directory is resolved\r\n * instead, which catches a symlinked *directory*.\r\n */\r\nfunction toRealPath(inputPath: string): string {\r\n const _logger = $logger.child({ context: \"toRealPath\" });\r\n\r\n try {\r\n return fs.realpathSync.native(inputPath);\r\n }\r\n catch (ex) {\r\n // Expected for a download destination that does not exist yet, so this is not an\r\n // error — but it must not pass unrecorded, since the fallback guards less.\r\n _logger.debug(\"path does not resolve, falling back to its parent directory\", {\r\n reason: ex instanceof Error ? ex.name : \"unknown\",\r\n });\r\n }\r\n\r\n try {\r\n const parentRealPath = fs.realpathSync.native(path.dirname(inputPath));\r\n return path.join(parentRealPath, path.basename(inputPath));\r\n }\r\n catch (ex) {\r\n _logger.debug(\"parent directory does not resolve either, comparing lexically\", {\r\n reason: ex instanceof Error ? ex.name : \"unknown\",\r\n });\r\n return path.resolve(inputPath);\r\n }\r\n}\r\n\r\n/** Path form the protected-path rules are written against. */\r\nfunction toComparablePath(inputPath: string): string {\r\n return toRealPath(inputPath).replace(/\\\\/g, \"/\");\r\n}\r\n\r\n/**\r\n * Decides whether a file tool may touch a path on the machine running this server.\r\n *\r\n * Separate from the remote guard because the threat is inverted: here the danger is\r\n * reading the operator's own credentials out (upload) or overwriting the files that\r\n * define the guards themselves (download).\r\n *\r\n * @param resolvedLocalPath Absolute path, already `~`-expanded.\r\n * @param extraProtectedPaths Runtime-known paths to protect on top of the built-in rules,\r\n * for a host embedding this server as a library.\r\n */\r\nexport function inspectLocalPath(\r\n resolvedLocalPath: string,\r\n access: RemotePathAccess,\r\n extraProtectedPaths: string[] = []\r\n): GuardVerdict {\r\n const trimmedPath = resolvedLocalPath.trim();\r\n if (trimmedPath === \"\") {\r\n return { allowed: false, reason: \"local path is empty\" };\r\n }\r\n\r\n const comparablePath = toComparablePath(trimmedPath);\r\n\r\n for (const rule of PROTECTED_LOCAL_PATH_RULES) {\r\n if (rule.pattern.test(comparablePath) == true) {\r\n let verb = \"read from\";\r\n if (access === \"write\") {\r\n verb = \"written to\";\r\n }\r\n\r\n return {\r\n allowed: false,\r\n reason: `local path is protected and cannot be ${verb}: ${rule.reason}`,\r\n offendingText: trimmedPath,\r\n };\r\n }\r\n }\r\n\r\n for (const protectedPath of extraProtectedPaths) {\r\n if (protectedPath === \"\") {\r\n continue;\r\n }\r\n\r\n if (comparablePath.toLowerCase() === toComparablePath(protectedPath).toLowerCase()) {\r\n return {\r\n allowed: false,\r\n reason: \"local path is this server's own configuration file\",\r\n offendingText: trimmedPath,\r\n };\r\n }\r\n }\r\n\r\n return { allowed: true };\r\n}\r\n\r\n/**\r\n * Decides whether a `cwd` argument is safe to interpolate into the generated `cd` prefix.\r\n *\r\n * `cwd` never reaches the command guard — it is not a command — yet it lands in the same\r\n * remote shell script. Without this check, `cwd: '/tmp\"; rm -rf /; #'` executes with every\r\n * command guard bypassed.\r\n */\r\nexport function inspectWorkingDirectory(cwd: string): GuardVerdict {\r\n const trimmedCwd = cwd.trim();\r\n if (trimmedCwd === \"\") {\r\n return { allowed: true };\r\n }\r\n\r\n const unsafeMatch = UNSAFE_PATH_CHARACTER_PATTERN.exec(trimmedCwd);\r\n if (unsafeMatch != null) {\r\n const displayCharacter = unsafeMatch[0]\r\n .replace(/\\n/, \"\\\\n\")\r\n .replace(/\\r/, \"\\\\r\")\r\n .replace(/\\t/, \"\\\\t\");\r\n return {\r\n allowed: false,\r\n reason: `cwd contains the shell metacharacter '${displayCharacter}', which is not allowed in a directory argument`,\r\n offendingText: trimmedCwd,\r\n };\r\n }\r\n\r\n return { allowed: true };\r\n}\r\n\r\n/** Warns once when a profile's own settings make its guards inert. */\r\nexport function warnOnInertPolicy(profileName: string, policy: SshPolicy): void {\r\n if (policy.readonly == true || policy.allowCommands.length > 0 || policy.denyPatterns.length > 0) {\r\n return;\r\n }\r\n\r\n const _logger = $logger.child({ context: \"warnOnInertPolicy\", profile: profileName });\r\n _logger.debug(\"profile runs with catastrophe-only guards; remote account permissions are the real boundary\");\r\n}\r\n","import type { GuardVerdict } from \"@/_types\";\r\n\r\n/**\r\n * Thrown when a guard refuses an operation.\r\n *\r\n * A distinct class because callers genuinely branch on it: the tool layer renders a\r\n * rejection with `formatGuardRejection` and marks it a policy decision, while any other\r\n * error is an operational failure worth retrying. It carries the verdict so that\r\n * rendering needs no string parsing.\r\n */\r\nexport class GuardRejectionError extends Error {\r\n constructor(public readonly verdict: GuardVerdict) {\r\n super(verdict.reason ?? \"rejected by the host profile's guard policy\");\r\n this.name = \"GuardRejectionError\";\r\n }\r\n}\r\n","import { spawn } from \"node:child_process\";\r\nimport { createHash } from \"node:crypto\";\r\nimport fs from \"node:fs\";\r\nimport { Duplex } from \"node:stream\";\r\nimport { StringDecoder } from \"node:string_decoder\";\r\n\r\nimport type ssh2 from \"ssh2\";\r\nimport type { ConnectConfig, SFTPWrapper } from \"ssh2\";\r\n\r\nimport { CWD_MARKER, KEEPALIVE_INTERVAL_MS } from \"@/_defs\";\r\nimport { $logger, quoteShellArgument, truncateOutput } from \"@/_libs\";\r\nimport type { SshExecResult, SshHostProfile, SshJumpProfile } from \"@/_types\";\r\nimport { GuardRejectionError, canonicalizeCommand, inspectCommand, inspectWorkingDirectory } from \"@/modules/guard\";\r\n\r\n/** Stops runaway output from filling the heap before truncation can happen. */\r\nconst OUTPUT_HARD_CAP_MULTIPLIER = 4;\r\n\r\n/** Key file contents by absolute path — immutable for the process lifetime. */\r\nconst PRIVATE_KEY_CACHE = new Map<string, Buffer>();\r\n\r\nexport interface SshExecOptions {\r\n command: string;\r\n /** Directory to `cd` into first; the login shell's default is used when absent. */\r\n cwd?: string;\r\n timeoutMs: number;\r\n maxOutputCharacters: number;\r\n /** Appends a `$PWD` marker so a session can follow `cd` between commands. */\r\n trackCwd: boolean;\r\n}\r\n\r\n/** Windows OpenSSH agent pipe — ssh2 accepts it wherever a socket path is expected. */\r\nconst WINDOWS_AGENT_PIPE = \"\\\\\\\\.\\\\pipe\\\\openssh-ssh-agent\";\r\n\r\nfunction resolveAgentPath(): string | undefined {\r\n const authSocket = process.env.SSH_AUTH_SOCK;\r\n if (authSocket != null && authSocket !== \"\") {\r\n return authSocket;\r\n }\r\n\r\n if (process.platform === \"win32\") {\r\n return WINDOWS_AGENT_PIPE;\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\n/**\r\n * Reads a private key, caching by path.\r\n *\r\n * Without the cache every one-off tool call re-read the same file synchronously, stalling\r\n * the event loop for every other in-flight call.\r\n *\r\n * @param label Names the hop in the error, e.g. the profile name or \"<name> jump host\".\r\n * @throws {Error} If the file cannot be read. The path is deliberately kept out of the\r\n * message — it reaches the model verbatim, and the rest of the codebase\r\n * (logger masking, `toHostSummary`) treats it as a secret.\r\n */\r\nfunction readPrivateKey(label: string, keyPath: string): Buffer {\r\n const cached = PRIVATE_KEY_CACHE.get(keyPath);\r\n if (cached != null) {\r\n return cached;\r\n }\r\n\r\n try {\r\n const contents = fs.readFileSync(keyPath);\r\n PRIVATE_KEY_CACHE.set(keyPath, contents);\r\n return contents;\r\n }\r\n catch (ex) {\r\n const _logger = $logger.child({ context: \"readPrivateKey\", profile: label });\r\n _logger.error(ex, \"cannot read the private key file\");\r\n throw new Error(`cannot read the private key configured for '${label}' (see the server log for the path)`);\r\n }\r\n}\r\n\r\n/** Credentials a hop authenticates with — the same three fields on the target and the jump host. */\r\ninterface HopCredentials {\r\n privateKeyPath?: string;\r\n passphrase?: string;\r\n password?: string;\r\n}\r\n\r\n/** Applies the auth order — private key, password, then the SSH agent — onto a connect config. */\r\nfunction applyAuth(connectConfig: ConnectConfig, credentials: HopCredentials, label: string): void {\r\n if (credentials.privateKeyPath != null) {\r\n connectConfig.privateKey = readPrivateKey(label, credentials.privateKeyPath);\r\n }\r\n\r\n if (credentials.passphrase != null) {\r\n connectConfig.passphrase = credentials.passphrase;\r\n }\r\n\r\n if (credentials.password != null) {\r\n connectConfig.password = credentials.password;\r\n }\r\n\r\n // No key and no password — fall back to the running SSH agent, which is how most\r\n // interactive setups already authenticate.\r\n if (connectConfig.privateKey == null && connectConfig.password == null) {\r\n connectConfig.agent = resolveAgentPath();\r\n }\r\n}\r\n\r\n/** OpenSSH-style fingerprint — the same string `ssh-keyscan` and `ssh` itself print. */\r\nexport function toHostKeyFingerprint(hostKey: Buffer): string {\r\n const digest = createHash(\"sha256\").update(hostKey).digest(\"base64\").replace(/=+$/, \"\");\r\n return `SHA256:${digest}`;\r\n}\r\n\r\n/**\r\n * Builds the host key check for one hop.\r\n *\r\n * ssh2 accepts *any* host key when no verifier is supplied, so without this a\r\n * man-in-the-middle — or a rebuilt server presenting a new key — is indistinguishable\r\n * from the real host. Pinning stays opt-in (`pinnedFingerprint`) to match the permissive\r\n * defaults, but the fingerprint is always logged on first connect so it can be copied\r\n * into the profile.\r\n */\r\nfunction buildHostVerifier(\r\n hop: { label: string; host: string; pinnedFingerprint?: string },\r\n onHostKeySeen: (fingerprint: string) => void\r\n): (hostKey: Buffer) => boolean {\r\n return (hostKey: Buffer) => {\r\n const fingerprint = toHostKeyFingerprint(hostKey);\r\n onHostKeySeen(fingerprint);\r\n\r\n const _logger = $logger.child({ context: \"hostVerifier\", profile: hop.label, host: hop.host });\r\n\r\n if (hop.pinnedFingerprint == null) {\r\n _logger.info(\"host key accepted unpinned; pin the fingerprint in the profile to detect a changed key\", {\r\n host_key_fingerprint: fingerprint,\r\n });\r\n return true;\r\n }\r\n\r\n if (hop.pinnedFingerprint === fingerprint) {\r\n _logger.debug(\"host key matches the pinned fingerprint\");\r\n return true;\r\n }\r\n\r\n _logger.error(null, \"host key does not match the pinned fingerprint\", {\r\n expected: hop.pinnedFingerprint,\r\n actual: fingerprint,\r\n });\r\n return false;\r\n };\r\n}\r\n\r\n/** Profile → ssh2 connect options for the target hop. */\r\nfunction buildConnectConfig(\r\n profile: SshHostProfile,\r\n onHostKeySeen: (fingerprint: string) => void\r\n): ConnectConfig {\r\n const connectConfig: ConnectConfig = {\r\n host: profile.host,\r\n port: profile.port,\r\n username: profile.username,\r\n readyTimeout: profile.policy.connectTimeoutMs,\r\n keepaliveInterval: KEEPALIVE_INTERVAL_MS,\r\n hostVerifier: buildHostVerifier(\r\n { label: profile.name, host: profile.host, pinnedFingerprint: profile.hostKeyFingerprint },\r\n onHostKeySeen\r\n ),\r\n };\r\n\r\n applyAuth(connectConfig, profile, profile.name);\r\n\r\n return connectConfig;\r\n}\r\n\r\n/**\r\n * The stream the target handshake runs over when it is not a plain TCP socket, plus the\r\n * resource behind it — a jump client or a proxy process — that must go when the target\r\n * connection does.\r\n */\r\ninterface Transport {\r\n sock: Duplex;\r\n close(): void;\r\n}\r\n\r\n/** OpenSSH `ProxyCommand` tokens: `%h` host, `%p` port, `%r` user, `%%` a literal `%`. */\r\nexport function expandProxyCommand(template: string, hop: { host: string; port: number; username: string }): string {\r\n return template.replace(/%[hpr%]/g, (token) => {\r\n if (token === \"%h\") {\r\n return hop.host;\r\n }\r\n\r\n if (token === \"%p\") {\r\n return String(hop.port);\r\n }\r\n\r\n if (token === \"%r\") {\r\n return hop.username;\r\n }\r\n\r\n return \"%\";\r\n });\r\n}\r\n\r\n/**\r\n * Starts the proxy command and exposes its stdin/stdout as the SSH stream.\r\n *\r\n * Runs through the shell, as OpenSSH does, so the operator can quote arguments the way\r\n * they would in `~/.ssh/config`. The process is killed when the transport closes: on\r\n * Windows the shell is `cmd.exe` and the real command is its child, so the whole tree\r\n * has to go or `cloudflared` would outlive the connection it was carrying.\r\n */\r\nfunction openProxyCommand(profile: SshHostProfile, hop: { host: string; port: number; username: string }): Transport {\r\n const command = expandProxyCommand(profile.proxyCommand ?? \"\", hop);\r\n const _logger = $logger.child({ context: \"openProxyCommand\", profile: profile.name });\r\n _logger.debug(\"starting the proxy command\", { command: command });\r\n\r\n const child = spawn(command, { shell: true, stdio: [\"pipe\", \"pipe\", \"pipe\"], windowsHide: true });\r\n\r\n // The command's own diagnostics — a cloudflared login prompt, a refused tunnel — are\r\n // the only clue when the handshake never starts, so they go to the log at warn.\r\n const stderrDecoder = new StringDecoder(\"utf8\");\r\n child.stderr.on(\"data\", (chunk: Buffer) => {\r\n const text = stderrDecoder.write(chunk).trim();\r\n if (text !== \"\") {\r\n _logger.warn(\"proxy command stderr\", { text: text });\r\n }\r\n });\r\n\r\n child.on(\"exit\", (code, signal) => {\r\n _logger.debug(\"proxy command exited\", { exit_code: code, signal: signal });\r\n });\r\n\r\n const sock = Duplex.from({ readable: child.stdout, writable: child.stdin });\r\n\r\n // Permanent listener: killing the process tears its pipes down, and the stream reports\r\n // that as an error. ssh2 only listens once it has been handed the stream, so a kill\r\n // before the handshake (an unreadable jump key, a disconnect mid-setup) would\r\n // otherwise surface as an unhandled 'error' and take the whole server down.\r\n sock.on(\"error\", (error: Error) => {\r\n _logger.debug(\"proxy command stream error\", { error_message: error.message });\r\n });\r\n\r\n // A spawn failure surfaces on the child, not on the stream; forward it so ssh2\r\n // reports it as a socket error instead of waiting for the ready timeout.\r\n child.on(\"error\", (error: Error) => {\r\n sock.destroy(error);\r\n });\r\n\r\n // Latch: close() is reached from both the jump client's and the target client's\r\n // 'close' handlers when the two are stacked, and one kill is enough.\r\n let closed = false;\r\n\r\n return {\r\n sock: sock,\r\n close: () => {\r\n if (closed == true) {\r\n return;\r\n }\r\n closed = true;\r\n\r\n if (child.exitCode != null || child.signalCode != null) {\r\n return;\r\n }\r\n\r\n if (process.platform === \"win32\" && child.pid != null) {\r\n const killer = spawn(\"taskkill\", [\"/pid\", String(child.pid), \"/T\", \"/F\"], { stdio: \"ignore\", windowsHide: true });\r\n // Without a listener a failure to even start taskkill is an unhandled\r\n // 'error' event, which takes the whole server down with it.\r\n killer.on(\"error\", (error: Error) => {\r\n _logger.error(error, \"failed to kill the proxy command's process tree\");\r\n });\r\n return;\r\n }\r\n\r\n child.kill();\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Connects to the jump host and opens a forwarded TCP channel to the target, which the\r\n * target handshake then runs over — what `ssh -J` does.\r\n *\r\n * @param proxy Carries the jump hop itself when a proxy command is also configured.\r\n * @throws {Error} If the jump host cannot be reached or refuses the forward. The message\r\n * names the jump host, so a failure there is not mistaken for the target.\r\n */\r\nfunction openJumpTransport(\r\n profile: SshHostProfile,\r\n jump: SshJumpProfile,\r\n proxy: Transport | null,\r\n onHostKeySeen: (fingerprint: string) => void,\r\n Client: typeof ssh2.Client\r\n): Promise<Transport> {\r\n const label = `${profile.name} jump host`;\r\n const endpoint = `${jump.username}@${jump.host}:${jump.port}`;\r\n const _logger = $logger.child({ context: \"openJumpTransport\", profile: profile.name, jump_host: jump.host });\r\n\r\n const jumpConfig: ConnectConfig = {\r\n host: jump.host,\r\n port: jump.port,\r\n username: jump.username,\r\n readyTimeout: profile.policy.connectTimeoutMs,\r\n keepaliveInterval: KEEPALIVE_INTERVAL_MS,\r\n hostVerifier: buildHostVerifier(\r\n { label: label, host: jump.host, pinnedFingerprint: jump.hostKeyFingerprint },\r\n onHostKeySeen\r\n ),\r\n };\r\n\r\n applyAuth(jumpConfig, jump, label);\r\n\r\n if (proxy != null) {\r\n jumpConfig.sock = proxy.sock;\r\n }\r\n\r\n return new Promise<Transport>((resolve, reject) => {\r\n const jumpClient = new Client();\r\n let settled = false;\r\n\r\n jumpClient.on(\"error\", (error: Error) => {\r\n if (settled == false) {\r\n settled = true;\r\n jumpClient.destroy();\r\n reject(new Error(`jump host ${endpoint}: ${error.message}`));\r\n return;\r\n }\r\n\r\n _logger.error(error, \"jump host connection error after ready\");\r\n });\r\n\r\n jumpClient.on(\"close\", () => {\r\n _logger.debug(\"jump host connection closed\");\r\n proxy?.close();\r\n\r\n if (settled == false) {\r\n settled = true;\r\n reject(new Error(`jump host ${endpoint}: connection closed before the handshake completed`));\r\n }\r\n });\r\n\r\n jumpClient.once(\"ready\", () => {\r\n if (settled == true) {\r\n return;\r\n }\r\n\r\n jumpClient.forwardOut(\"127.0.0.1\", 0, profile.host, profile.port, (error, stream) => {\r\n if (settled == true) {\r\n return;\r\n }\r\n settled = true;\r\n\r\n if (error != null) {\r\n jumpClient.end();\r\n reject(new Error(`jump host ${endpoint} refused the forward to ${profile.host}:${profile.port}: ${error.message}`));\r\n return;\r\n }\r\n\r\n _logger.info(\"jump host connected, target channel forwarded\");\r\n resolve({\r\n sock: stream,\r\n close: () => {\r\n jumpClient.end();\r\n },\r\n });\r\n });\r\n });\r\n\r\n jumpClient.connect(jumpConfig);\r\n });\r\n}\r\n\r\n/**\r\n * Opens whatever carries the target handshake: nothing for a direct TCP connection, a\r\n * proxy process, a jump channel, or a jump channel whose own hop runs over the proxy.\r\n */\r\nasync function openTransport(\r\n profile: SshHostProfile,\r\n onJumpHostKeySeen: (fingerprint: string) => void,\r\n Client: typeof ssh2.Client\r\n): Promise<Transport | null> {\r\n let proxy: Transport | null = null;\r\n if (profile.proxyCommand != null) {\r\n proxy = openProxyCommand(profile, profile.jump ?? profile);\r\n }\r\n\r\n if (profile.jump == null) {\r\n return proxy;\r\n }\r\n\r\n // The proxy process is already running by now; a jump hop that cannot even be set\r\n // up (an unreadable jump key throws before any socket is touched) must not leave it\r\n // behind as an orphan.\r\n try {\r\n const jumpTransport = await openJumpTransport(profile, profile.jump, proxy, onJumpHostKeySeen, Client);\r\n return jumpTransport;\r\n }\r\n catch (ex) {\r\n proxy?.close();\r\n throw ex;\r\n }\r\n}\r\n\r\n/**\r\n * Wraps the user command so the remote shell reports where it ended up.\r\n *\r\n * `$?` is captured before the marker is printed and replayed via `exit`, so the exit\r\n * status the caller sees is the command's, not `printf`'s.\r\n *\r\n * `cwd` appears exactly once, inside `quoteShellArgument`. It must never be interpolated\r\n * anywhere else — an earlier version echoed it into a double-quoted diagnostic, which let\r\n * `cwd: '/x\"; rm -rf /; #'` run arbitrary commands with every guard bypassed. The failure\r\n * message is therefore a fixed string.\r\n */\r\nexport function buildRemoteCommand(command: string, cwd: string | undefined, trackCwd: boolean): string {\r\n const lines: string[] = [];\r\n\r\n if (cwd != null && cwd !== \"\") {\r\n const quotedCwd = quoteShellArgument(cwd);\r\n lines.push(`cd ${quotedCwd} 2>/dev/null || { echo 'akms-mcp-ssh: cannot enter the requested working directory' >&2; exit 1; }`);\r\n }\r\n\r\n lines.push(command);\r\n\r\n if (trackCwd == true) {\r\n lines.push(\"__akms_exit=$?\");\r\n lines.push(`printf '\\\\n${CWD_MARKER}%s' \"$PWD\"`);\r\n lines.push(\"exit $__akms_exit\");\r\n }\r\n\r\n return lines.join(\"\\n\");\r\n}\r\n\r\n/** Splits the trailing `$PWD` marker off stdout. */\r\nexport function extractCwdMarker(stdout: string): { output: string; cwd?: string } {\r\n const markerIndex = stdout.lastIndexOf(CWD_MARKER);\r\n if (markerIndex < 0) {\r\n return { output: stdout };\r\n }\r\n\r\n // First line only: `$PWD` is a single line, so anything past a newline is not the\r\n // directory and must not end up in the `cd` prefix of the next command.\r\n const markerPayload = stdout.slice(markerIndex + CWD_MARKER.length);\r\n const cwd = (markerPayload.split(\"\\n\")[0] ?? \"\").trim();\r\n\r\n let output = stdout.slice(0, markerIndex);\r\n if (output.endsWith(\"\\n\") == true) {\r\n output = output.slice(0, -1);\r\n }\r\n\r\n if (cwd === \"\") {\r\n return { output: output };\r\n }\r\n\r\n return { output: output, cwd: cwd };\r\n}\r\n\r\n/**\r\n * One ssh2 client bound to a profile, plus the exec / SFTP operations run over it.\r\n *\r\n * This class is the chokepoint: it holds the profile, so it applies the command guard\r\n * itself rather than trusting each caller to remember. Enforcement in the tool handlers\r\n * was one forgotten line away from shipping an unscreened command — and one such caller\r\n * (the `ssh_connect` probe) already existed.\r\n */\r\nexport class SshConnection {\r\n private client: ssh2.Client | null = null;\r\n private pendingClient: ssh2.Client | null = null;\r\n private connecting: Promise<void> | null = null;\r\n private disposed = false;\r\n /** Set when the peer closed a previously established connection. */\r\n private droppedAfterReady = false;\r\n private observedFingerprint: string | null = null;\r\n private observedJumpFingerprint: string | null = null;\r\n\r\n constructor(public readonly profile: SshHostProfile) {\r\n }\r\n\r\n get isConnected(): boolean {\r\n return this.client != null;\r\n }\r\n\r\n /** Host key the server presented, once a handshake has been attempted. */\r\n get hostKeyFingerprint(): string | null {\r\n return this.observedFingerprint;\r\n }\r\n\r\n /** Host key the jump host presented; null without a jump host or before its handshake. */\r\n get jumpHostKeyFingerprint(): string | null {\r\n return this.observedJumpFingerprint;\r\n }\r\n\r\n /** True when a connection was established and then lost, so shell state is gone. */\r\n get wasDropped(): boolean {\r\n return this.droppedAfterReady;\r\n }\r\n\r\n private assertNotDisposed(): void {\r\n if (this.disposed == true) {\r\n throw new Error(`ssh connection for '${this.profile.name}' has been closed; open a new session`);\r\n }\r\n }\r\n\r\n /**\r\n * Opens the connection, or joins the in-flight attempt when one is already running.\r\n *\r\n * With a proxy command or a jump host the carrier is opened first and handed to ssh2\r\n * as the socket; it is torn down with the target connection, whichever side closes.\r\n *\r\n * @throws {Error} If the connection was closed, authentication fails, the host is\r\n * unreachable, the handshake times out, or the carrier cannot be opened.\r\n */\r\n async connect(onProgress?: (stage: \"transport\" | \"host\") => void): Promise<void> {\r\n this.assertNotDisposed();\r\n\r\n if (this.client != null) {\r\n return;\r\n }\r\n\r\n if (this.connecting != null) {\r\n await this.connecting;\r\n return;\r\n }\r\n\r\n const _logger = $logger.child({\r\n context: \"SshConnection.connect\",\r\n profile: this.profile.name,\r\n host: this.profile.host,\r\n port: this.profile.port,\r\n username: this.profile.username,\r\n });\r\n\r\n const connectConfig = buildConnectConfig(this.profile, (fingerprint) => {\r\n this.observedFingerprint = fingerprint;\r\n });\r\n\r\n this.connecting = (async () => {\r\n // Loading ssh2 starts its crypto runtime. Idle MCP connections and local\r\n // configuration work do not need it; share this load with the handshake.\r\n const { default: { Client } } = await import(\"ssh2\");\r\n this.assertNotDisposed();\r\n if (this.profile.jump != null || this.profile.proxyCommand != null) {\r\n onProgress?.(\"transport\");\r\n }\r\n const transport = await openTransport(this.profile, (fingerprint) => {\r\n this.observedJumpFingerprint = fingerprint;\r\n }, Client);\r\n\r\n // disconnect() may have run while the carrier was being opened.\r\n if (this.disposed == true) {\r\n transport?.close();\r\n throw new Error(`ssh connection for '${this.profile.name}' was closed during the handshake`);\r\n }\r\n\r\n if (transport != null) {\r\n connectConfig.sock = transport.sock;\r\n }\r\n\r\n onProgress?.(\"host\");\r\n await new Promise<void>((resolve, reject) => {\r\n const client = new Client();\r\n this.pendingClient = client;\r\n let settled = false;\r\n\r\n // One permanent 'error' listener, installed before connect() and never\r\n // removed. Registering it with `once` left the client listener-less after the\r\n // first error, and ssh2 emits 'error' again while the socket tears down —\r\n // an unhandled 'error' event takes down the whole server process.\r\n client.on(\"error\", (error: Error) => {\r\n if (settled == false) {\r\n settled = true;\r\n this.pendingClient = null;\r\n client.destroy();\r\n reject(error);\r\n return;\r\n }\r\n\r\n // Only a live connection's error is news. A failed handshake is\r\n // already reported through reject(); ssh2 keeps emitting while the\r\n // socket tears down (\"Connection lost before handshake\", an abort\r\n // from the destroyed proxy stream) and those are just the aftermath.\r\n if (this.client !== client) {\r\n _logger.debug(\"ssh connection error during teardown\", { error_message: error.message });\r\n return;\r\n }\r\n\r\n _logger.error(error, \"ssh connection error after ready\");\r\n });\r\n\r\n client.on(\"close\", () => {\r\n // Whichever side closed, the carrier behind this connection is done with.\r\n transport?.close();\r\n\r\n if (this.client === client) {\r\n _logger.debug(\"ssh connection closed by the peer\");\r\n this.client = null;\r\n this.droppedAfterReady = true;\r\n return;\r\n }\r\n\r\n // A carrier that ends before the handshake (a proxy command that exited at\r\n // once) closes without a usable 'error'; settle here or connect() hangs.\r\n if (settled == false) {\r\n settled = true;\r\n this.pendingClient = null;\r\n reject(new Error(\"connection closed before the handshake completed\"));\r\n }\r\n });\r\n\r\n client.once(\"ready\", () => {\r\n if (settled == true) {\r\n return;\r\n }\r\n settled = true;\r\n this.pendingClient = null;\r\n\r\n // disconnect() may have run while the handshake was in flight; adopting\r\n // the client now would leak a live connection nothing can ever close.\r\n if (this.disposed == true) {\r\n client.destroy();\r\n reject(new Error(`ssh connection for '${this.profile.name}' was closed during the handshake`));\r\n return;\r\n }\r\n\r\n this.client = client;\r\n resolve();\r\n });\r\n\r\n client.connect(connectConfig);\r\n });\r\n })();\r\n\r\n try {\r\n await this.connecting;\r\n _logger.info(\"ssh connection established\");\r\n }\r\n catch (ex) {\r\n _logger.error(ex, \"ssh connection failed\");\r\n\r\n let detail = String(ex);\r\n if (ex instanceof Error) {\r\n detail = ex.message;\r\n }\r\n\r\n throw new Error(`ssh connect failed for '${this.profile.name}' (${this.profile.username}@${this.profile.host}:${this.profile.port}): ${detail}`);\r\n }\r\n finally {\r\n this.connecting = null;\r\n }\r\n }\r\n\r\n /**\r\n * Screens a command against this profile's policy, then runs it on its own channel.\r\n *\r\n * The canonical form is what gets screened *and* what gets sent — validating one\r\n * string while executing another is a bypass by construction.\r\n *\r\n * @throws {GuardRejectionError} If the policy refuses the command or the cwd.\r\n * @throws {Error} If the connection is closed or the channel cannot be opened.\r\n */\r\n async exec(options: SshExecOptions): Promise<SshExecResult> {\r\n const canonicalCommand = canonicalizeCommand(options.command);\r\n\r\n const verdict = inspectCommand(canonicalCommand, this.profile.policy);\r\n if (verdict.allowed == false) {\r\n throw new GuardRejectionError(verdict);\r\n }\r\n\r\n if (options.cwd != null) {\r\n const cwdVerdict = inspectWorkingDirectory(options.cwd);\r\n if (cwdVerdict.allowed == false) {\r\n throw new GuardRejectionError(cwdVerdict);\r\n }\r\n }\r\n\r\n await this.connect();\r\n\r\n const client = this.client;\r\n if (client == null) {\r\n throw new Error(`ssh connection for '${this.profile.name}' is not open`);\r\n }\r\n\r\n const _logger = $logger.child({\r\n context: \"SshConnection.exec\",\r\n profile: this.profile.name,\r\n cwd: options.cwd,\r\n });\r\n\r\n const remoteCommand = buildRemoteCommand(canonicalCommand, options.cwd, options.trackCwd);\r\n const hardCap = options.maxOutputCharacters * OUTPUT_HARD_CAP_MULTIPLIER;\r\n const startedAt = Date.now();\r\n\r\n const raw = await new Promise<{\r\n stdout: string;\r\n stderr: string;\r\n exitCode: number | null;\r\n signal?: string;\r\n timedOut: boolean;\r\n }>((resolve, reject) => {\r\n client.exec(remoteCommand, (error, channel) => {\r\n if (error != null) {\r\n reject(error);\r\n return;\r\n }\r\n\r\n let stdout = \"\";\r\n let stderr = \"\";\r\n let exitCode: number | null = null;\r\n let signal: string | undefined = undefined;\r\n let timedOut = false;\r\n let settled = false;\r\n // Latch: channel.close() is a round trip, so packets keep arriving after\r\n // the cap trips. Without it each one re-logged and re-grew the string.\r\n let capExceeded = false;\r\n\r\n // One decoder per stream: a multi-byte character split across two SSH\r\n // packets decodes to U+FFFD if each chunk is converted on its own.\r\n const stdoutDecoder = new StringDecoder(\"utf8\");\r\n const stderrDecoder = new StringDecoder(\"utf8\");\r\n\r\n const timeoutTimer = setTimeout(() => {\r\n timedOut = true;\r\n _logger.warn(\"command timed out, closing the channel\", { timeout_ms: options.timeoutMs });\r\n channel.close();\r\n }, options.timeoutMs);\r\n timeoutTimer.unref();\r\n\r\n // Only 'close' settles the promise. 'end' fires on CHANNEL_EOF, which the\r\n // remote may send before the separate exit-status request arrives — settling\r\n // there reports a successful command as `exit code: none (killed)`.\r\n const finish = (): void => {\r\n if (settled == true) {\r\n return;\r\n }\r\n settled = true;\r\n clearTimeout(timeoutTimer);\r\n\r\n stdout += stdoutDecoder.end();\r\n stderr += stderrDecoder.end();\r\n\r\n resolve({ stdout: stdout, stderr: stderr, exitCode: exitCode, signal: signal, timedOut: timedOut });\r\n };\r\n\r\n channel.on(\"data\", (chunk: Buffer) => {\r\n if (capExceeded == true) {\r\n return;\r\n }\r\n\r\n stdout += stdoutDecoder.write(chunk);\r\n if (stdout.length > hardCap) {\r\n capExceeded = true;\r\n _logger.warn(\"stdout exceeded the hard cap, closing the channel\", { hard_cap: hardCap });\r\n channel.close();\r\n }\r\n });\r\n\r\n channel.stderr.on(\"data\", (chunk: Buffer) => {\r\n if (capExceeded == true) {\r\n return;\r\n }\r\n\r\n stderr += stderrDecoder.write(chunk);\r\n if (stderr.length > hardCap) {\r\n capExceeded = true;\r\n channel.close();\r\n }\r\n });\r\n\r\n channel.on(\"exit\", (code: number | null, exitSignal?: string) => {\r\n exitCode = code;\r\n signal = exitSignal;\r\n });\r\n\r\n channel.on(\"close\", finish);\r\n channel.on(\"error\", (channelError: Error) => {\r\n clearTimeout(timeoutTimer);\r\n if (settled == true) {\r\n return;\r\n }\r\n settled = true;\r\n reject(channelError);\r\n });\r\n\r\n // Send EOF on the remote stdin straight away. Nothing will ever be written\r\n // to it, and a command that reads — a `sudo` password prompt, an apt\r\n // confirmation, `ssh-keygen` — would otherwise sit there until the timeout\r\n // fires. With stdin closed it fails in milliseconds with a usable message.\r\n //\r\n // `eof()`, not `end()`: end() also closes the channel, which loses the\r\n // exit-status request the server sends afterwards.\r\n channel.eof();\r\n });\r\n });\r\n\r\n // Only parse the marker when this call asked for it — otherwise output that merely\r\n // contains the marker text would be silently cut at that point.\r\n let output = raw.stdout;\r\n let cwd: string | undefined = undefined;\r\n if (options.trackCwd == true) {\r\n const parsed = extractCwdMarker(raw.stdout);\r\n output = parsed.output;\r\n cwd = parsed.cwd;\r\n }\r\n\r\n const truncatedStdout = truncateOutput(output, options.maxOutputCharacters);\r\n const truncatedStderr = truncateOutput(raw.stderr, options.maxOutputCharacters);\r\n\r\n const result: SshExecResult = {\r\n stdout: truncatedStdout.text,\r\n stderr: truncatedStderr.text,\r\n exitCode: raw.exitCode,\r\n timedOut: raw.timedOut,\r\n truncated: truncatedStdout.truncated == true || truncatedStderr.truncated == true,\r\n durationMs: Date.now() - startedAt,\r\n };\r\n\r\n if (raw.signal != null) {\r\n result.signal = raw.signal;\r\n }\r\n\r\n if (cwd != null) {\r\n result.cwd = cwd;\r\n }\r\n\r\n _logger.debug(\"command finished\", {\r\n exit_code: result.exitCode,\r\n duration_ms: result.durationMs,\r\n timed_out: result.timedOut,\r\n });\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Opens an SFTP channel, hands it to `handler`, and closes it afterwards.\r\n *\r\n * A channel per operation costs one round trip but keeps a broken transfer from\r\n * poisoning later ones — SFTP channels do not recover from a mid-stream error.\r\n *\r\n * The channel's own `error` event is wired into the returned promise: ssh2 strips its\r\n * setup listeners before handing the wrapper over, so an unhandled SFTP protocol error\r\n * would otherwise crash the process rather than fail this one call.\r\n *\r\n * @throws {Error} If the SFTP subsystem cannot be started or the channel errors.\r\n */\r\n async withSftp<T>(handler: (sftp: SFTPWrapper) => Promise<T>): Promise<T> {\r\n await this.connect();\r\n\r\n const client = this.client;\r\n if (client == null) {\r\n throw new Error(`ssh connection for '${this.profile.name}' is not open`);\r\n }\r\n\r\n const sftp = await new Promise<SFTPWrapper>((resolve, reject) => {\r\n client.sftp((error, sftpWrapper) => {\r\n if (error != null) {\r\n reject(error);\r\n return;\r\n }\r\n\r\n resolve(sftpWrapper);\r\n });\r\n });\r\n\r\n return await new Promise<T>((resolve, reject) => {\r\n let settled = false;\r\n\r\n const finish = (settle: () => void): void => {\r\n if (settled == true) {\r\n return;\r\n }\r\n settled = true;\r\n\r\n try {\r\n sftp.end();\r\n }\r\n catch (ex) {\r\n const _logger = $logger.child({ context: \"SshConnection.withSftp\", profile: this.profile.name });\r\n _logger.error(ex, \"failed to close the sftp channel\");\r\n }\r\n\r\n settle();\r\n };\r\n\r\n sftp.on(\"error\", (error: Error) => {\r\n finish(() => {\r\n reject(error);\r\n });\r\n });\r\n\r\n handler(sftp).then(\r\n (value) => {\r\n finish(() => {\r\n resolve(value);\r\n });\r\n },\r\n (error: unknown) => {\r\n finish(() => {\r\n reject(error);\r\n });\r\n }\r\n );\r\n });\r\n }\r\n\r\n /** Closes the connection for good; a disposed connection will not re-dial. */\r\n disconnect(): void {\r\n this.disposed = true;\r\n\r\n // A handshake still in flight would otherwise complete and install its client on\r\n // an object nothing holds a reference to any more.\r\n if (this.pendingClient != null) {\r\n this.pendingClient.destroy();\r\n this.pendingClient = null;\r\n }\r\n\r\n if (this.client == null) {\r\n return;\r\n }\r\n\r\n this.client.end();\r\n this.client = null;\r\n }\r\n}\r\n","import { setInterval, clearInterval } from \"node:timers\";\r\n\r\nimport { SESSION_IDLE_TIMEOUT_MS, SESSION_SWEEP_INTERVAL_MS } from \"@/_defs\";\r\nimport { $logger } from \"@/_libs\";\r\nimport type { SshExecResult, SshHostProfile, SshSessionInfo } from \"@/_types\";\r\nimport { inspectWorkingDirectory } from \"@/modules/guard\";\r\n\r\nimport { SshConnection } from \"./ssh-connection\";\r\n\r\n/** Ceiling on concurrently open sessions, so a runaway loop can't exhaust file descriptors. */\r\nconst MAX_OPEN_SESSIONS = 32;\r\n\r\n/** A connection plus the shell state (working directory) carried across commands. */\r\nexport class SshSession {\r\n /** Empty until the first command reports `$PWD`; then the real remote path. */\r\n cwd: string;\r\n lastUsedAt: Date;\r\n execCount = 0;\r\n\r\n readonly openedAt = new Date();\r\n\r\n /** Tail of the serialized operation chain; see `runExclusive`. */\r\n private operationChain: Promise<unknown> = Promise.resolve();\r\n private activeOperationCount = 0;\r\n\r\n constructor(\r\n public readonly sessionId: string,\r\n public readonly connection: SshConnection\r\n ) {\r\n this.cwd = connection.profile.defaultCwd ?? \"\";\r\n this.lastUsedAt = new Date();\r\n }\r\n\r\n get profile(): SshHostProfile {\r\n return this.connection.profile;\r\n }\r\n\r\n /** True while an operation is running, so the idle sweeper leaves it alone. */\r\n get isBusy(): boolean {\r\n return this.activeOperationCount > 0;\r\n }\r\n\r\n /** Marks the session as in use. */\r\n touch(): void {\r\n this.lastUsedAt = new Date();\r\n }\r\n\r\n /**\r\n * Runs `operation` with exclusive use of this session.\r\n *\r\n * Serialized because `cwd` is read-modify-write across a network round trip: two\r\n * concurrent calls both read the old directory, and the one that finishes second\r\n * writes it back — silently undoing the other's `cd`. Bracketing with `touch()` also\r\n * keeps the idle sweeper off a session that is mid-transfer.\r\n */\r\n async runExclusive<T>(operation: (connection: SshConnection) => Promise<T>): Promise<T> {\r\n const previous = this.operationChain;\r\n\r\n let releaseChain: () => void = () => {\r\n // Replaced below; assigned here so the type is not nullable.\r\n };\r\n this.operationChain = new Promise<void>((resolve) => {\r\n releaseChain = resolve;\r\n });\r\n\r\n await previous.catch(() => {\r\n // A failed predecessor must not poison the queue for later callers.\r\n });\r\n\r\n this.activeOperationCount += 1;\r\n this.touch();\r\n\r\n try {\r\n return await operation(this.connection);\r\n }\r\n finally {\r\n this.activeOperationCount -= 1;\r\n this.touch();\r\n releaseChain();\r\n }\r\n }\r\n\r\n /**\r\n * Runs a command in this session's working directory and adopts wherever it ended up,\r\n * so a `cd` in one call is still in effect on the next.\r\n *\r\n * Passing `cwd` runs this command elsewhere *and* moves the session there — same\r\n * result as prefixing `cd <cwd> &&`, which is what a shell user would expect.\r\n *\r\n * @throws {Error} If the connection was dropped since the session opened. A silent\r\n * re-dial would hand back a fresh login shell while the model still\r\n * believes it holds the old one — environment, sudo cache and\r\n * background jobs all gone with no signal.\r\n */\r\n async exec(command: string, options?: { cwd?: string; timeoutMs?: number }): Promise<SshExecResult> {\r\n if (this.connection.wasDropped == true) {\r\n throw new Error(`session ${this.sessionId} lost its connection to ${this.profile.host}; its shell state is gone — open a new session with ssh_connect`);\r\n }\r\n\r\n const policy = this.profile.policy;\r\n const targetCwd = options?.cwd ?? this.cwd;\r\n\r\n return await this.runExclusive(async (connection) => {\r\n const result = await connection.exec({\r\n command: command,\r\n cwd: targetCwd,\r\n timeoutMs: options?.timeoutMs ?? policy.execTimeoutMs,\r\n maxOutputCharacters: policy.maxOutputCharacters,\r\n trackCwd: true,\r\n });\r\n\r\n if (result.cwd != null && result.cwd !== \"\") {\r\n // The reported `$PWD` becomes the `cd` argument of the next command, so it\r\n // goes through the same metacharacter check as a caller-supplied cwd.\r\n const verdict = inspectWorkingDirectory(result.cwd);\r\n if (verdict.allowed == true) {\r\n this.cwd = result.cwd;\r\n }\r\n else {\r\n const _logger = $logger.child({ context: \"SshSession.exec\", session_id: this.sessionId });\r\n _logger.warn(\"remote working directory contains unsafe characters, keeping the previous cwd\", {\r\n reported_cwd: result.cwd,\r\n });\r\n }\r\n }\r\n\r\n this.execCount += 1;\r\n return result;\r\n });\r\n }\r\n\r\n toInfo(): SshSessionInfo {\r\n let displayCwd = this.cwd;\r\n if (displayCwd === \"\") {\r\n displayCwd = \"(login default)\";\r\n }\r\n\r\n return {\r\n sessionId: this.sessionId,\r\n profileName: this.profile.name,\r\n host: this.profile.host,\r\n username: this.profile.username,\r\n cwd: displayCwd,\r\n openedAt: this.openedAt.toISOString(),\r\n lastUsedAt: this.lastUsedAt.toISOString(),\r\n execCount: this.execCount,\r\n };\r\n }\r\n}\r\n\r\n/** Owns every open session: creation, lookup, idle expiry and shutdown. */\r\nexport class SshSessionManager {\r\n private readonly sessions = new Map<string, SshSession>();\r\n private sequence = 0;\r\n private pendingOpenCount = 0;\r\n private sweepTimer: NodeJS.Timeout | null = null;\r\n\r\n /**\r\n * Opens a connection and registers it as a session.\r\n *\r\n * `pendingOpenCount` reserves the slot across the `await`: checking `sessions.size`\r\n * alone let N concurrent calls all observe the same pre-connect count and blow past\r\n * the ceiling together.\r\n *\r\n * @throws {Error} If the session ceiling is reached or the connection fails.\r\n */\r\n async open(profile: SshHostProfile): Promise<SshSession> {\r\n if (this.sessions.size + this.pendingOpenCount >= MAX_OPEN_SESSIONS) {\r\n throw new Error(`too many open ssh sessions (${MAX_OPEN_SESSIONS}), close some with ssh_disconnect first`);\r\n }\r\n\r\n this.sequence += 1;\r\n const sessionId = `${profile.name}#${this.sequence}`;\r\n\r\n const connection = new SshConnection(profile);\r\n\r\n this.pendingOpenCount += 1;\r\n try {\r\n await connection.connect();\r\n }\r\n finally {\r\n this.pendingOpenCount -= 1;\r\n }\r\n\r\n const session = new SshSession(sessionId, connection);\r\n this.sessions.set(sessionId, session);\r\n\r\n const _logger = $logger.child({ context: \"SshSessionManager.open\", session_id: sessionId, profile: profile.name });\r\n _logger.info(\"ssh session opened\", { open_sessions: this.sessions.size });\r\n\r\n return session;\r\n }\r\n\r\n /**\r\n * Looks up a session, failing with the list of live ids so the caller can self-correct.\r\n *\r\n * @throws {Error} If no session matches `sessionId`.\r\n */\r\n require(sessionId: string): SshSession {\r\n const session = this.sessions.get(sessionId);\r\n if (session == null) {\r\n const openIds = Array.from(this.sessions.keys());\r\n\r\n let openSummary = \"none\";\r\n if (openIds.length > 0) {\r\n openSummary = openIds.join(\", \");\r\n }\r\n\r\n throw new Error(`unknown ssh session '${sessionId}' (open sessions: ${openSummary})`);\r\n }\r\n\r\n return session;\r\n }\r\n\r\n close(sessionId: string): boolean {\r\n const session = this.sessions.get(sessionId);\r\n if (session == null) {\r\n return false;\r\n }\r\n\r\n session.connection.disconnect();\r\n this.sessions.delete(sessionId);\r\n\r\n const _logger = $logger.child({ context: \"SshSessionManager.close\", session_id: sessionId });\r\n _logger.info(\"ssh session closed\", { open_sessions: this.sessions.size });\r\n\r\n return true;\r\n }\r\n\r\n closeAll(): void {\r\n for (const sessionId of Array.from(this.sessions.keys())) {\r\n this.close(sessionId);\r\n }\r\n }\r\n\r\n list(): SshSessionInfo[] {\r\n return Array.from(this.sessions.values()).map((session) => session.toInfo());\r\n }\r\n\r\n /** Starts the idle sweeper; the timer is unref'd so it never holds the process open. */\r\n startIdleSweeper(): void {\r\n if (this.sweepTimer != null) {\r\n return;\r\n }\r\n\r\n this.sweepTimer = setInterval(() => {\r\n const expiredAt = Date.now() - SESSION_IDLE_TIMEOUT_MS;\r\n for (const session of Array.from(this.sessions.values())) {\r\n // A long exec or a large transfer keeps a session busy well past the idle\r\n // window; reaping it there would abort the operation mid-flight.\r\n if (session.isBusy == true) {\r\n continue;\r\n }\r\n\r\n if (session.lastUsedAt.getTime() > expiredAt) {\r\n continue;\r\n }\r\n\r\n const _logger = $logger.child({\r\n context: \"SshSessionManager.sweep\",\r\n session_id: session.sessionId,\r\n idle_ms: Date.now() - session.lastUsedAt.getTime(),\r\n });\r\n _logger.info(\"closing idle ssh session\");\r\n this.close(session.sessionId);\r\n }\r\n }, SESSION_SWEEP_INTERVAL_MS);\r\n\r\n this.sweepTimer.unref();\r\n }\r\n\r\n stopIdleSweeper(): void {\r\n if (this.sweepTimer == null) {\r\n return;\r\n }\r\n\r\n clearInterval(this.sweepTimer);\r\n this.sweepTimer = null;\r\n }\r\n}\r\n","import fs from \"node:fs\";\r\nimport { StringDecoder } from \"node:string_decoder\";\r\n\r\nimport type { FileEntryWithStats, SFTPWrapper, Stats } from \"ssh2\";\r\n\r\nimport { MAX_DIRECTORY_ENTRIES } from \"@/_defs\";\r\nimport { $logger } from \"@/_libs\";\r\nimport type { SshRemoteEntry } from \"@/_types\";\r\nimport { GuardRejectionError, inspectRemotePath } from \"@/modules/guard\";\r\nimport type { RemotePathAccess } from \"@/modules/guard\";\r\n\r\nimport type { SshConnection } from \"./ssh-connection\";\r\n\r\n/**\r\n * Screens a remote path against the connection's own policy.\r\n *\r\n * Enforced here rather than in the tool handlers for the same reason `exec` screens its\r\n * own command: this is the layer every path must pass through, so a new tool cannot\r\n * forget the check.\r\n *\r\n * @throws {GuardRejectionError} If the policy refuses the path.\r\n */\r\nfunction requirePermittedPath(connection: SshConnection, remotePath: string, access: RemotePathAccess): void {\r\n const verdict = inspectRemotePath(remotePath, connection.profile.policy, access);\r\n if (verdict.allowed == false) {\r\n throw new GuardRejectionError(verdict);\r\n }\r\n}\r\n\r\nfunction toEntryType(attrs: Stats): SshRemoteEntry[\"type\"] {\r\n // SFTP attributes are flag-gated; without PERMISSIONS every predicate answers false.\r\n if (typeof attrs.mode !== \"number\") {\r\n return \"other\";\r\n }\r\n\r\n if (attrs.isDirectory() == true) {\r\n return \"directory\";\r\n }\r\n\r\n if (attrs.isSymbolicLink() == true) {\r\n return \"symlink\";\r\n }\r\n\r\n if (attrs.isFile() == true) {\r\n return \"file\";\r\n }\r\n\r\n return \"other\";\r\n}\r\n\r\n/**\r\n * ssh2 `Stats` → the listing shape returned to the model.\r\n *\r\n * Every field is optional on the wire: a server may answer readdir without SIZE or\r\n * ACMODTIME. Reading them unguarded turned one such entry into `new Date(NaN)` and a\r\n * thrown RangeError that failed the whole directory listing.\r\n */\r\nfunction toRemoteEntry(entry: FileEntryWithStats): SshRemoteEntry {\r\n const attrs = entry.attrs;\r\n\r\n let mode = \"?\";\r\n if (typeof attrs.mode === \"number\") {\r\n mode = `0${(attrs.mode & 0o7777).toString(8)}`;\r\n }\r\n\r\n let sizeBytes = 0;\r\n if (typeof attrs.size === \"number\" && Number.isFinite(attrs.size) == true) {\r\n sizeBytes = attrs.size;\r\n }\r\n\r\n let modifiedAt = \"unknown\";\r\n if (typeof attrs.mtime === \"number\" && Number.isFinite(attrs.mtime) == true) {\r\n modifiedAt = new Date(attrs.mtime * 1000).toISOString();\r\n }\r\n\r\n return {\r\n name: entry.filename,\r\n type: toEntryType(attrs),\r\n sizeBytes: sizeBytes,\r\n mode: mode,\r\n modifiedAt: modifiedAt,\r\n };\r\n}\r\n\r\nexport interface RemoteDirectoryListing {\r\n entries: SshRemoteEntry[];\r\n /** Entries the remote host reported, before the cap was applied. */\r\n totalCount: number;\r\n truncated: boolean;\r\n}\r\n\r\n/**\r\n * Lists a remote directory, directories first then files, each group alphabetical.\r\n *\r\n * Capped at `MAX_DIRECTORY_ENTRIES`: this is the one output path with no byte ceiling,\r\n * and a spool directory with half a million files would otherwise return megabytes.\r\n *\r\n * @throws {Error} If the path does not exist or is not readable.\r\n */\r\nexport async function listRemoteDirectory(\r\n connection: SshConnection,\r\n remotePath: string\r\n): Promise<RemoteDirectoryListing> {\r\n requirePermittedPath(connection, remotePath, \"read\");\r\n\r\n return await connection.withSftp(async (sftp: SFTPWrapper) => {\r\n const entries = await new Promise<FileEntryWithStats[]>((resolve, reject) => {\r\n sftp.readdir(remotePath, (error, list) => {\r\n if (error != null) {\r\n reject(error);\r\n return;\r\n }\r\n\r\n resolve(list);\r\n });\r\n });\r\n\r\n const mapped = entries.map(toRemoteEntry);\r\n mapped.sort((left, right) => {\r\n if (left.type === \"directory\" && right.type !== \"directory\") {\r\n return -1;\r\n }\r\n\r\n if (left.type !== \"directory\" && right.type === \"directory\") {\r\n return 1;\r\n }\r\n\r\n return left.name.localeCompare(right.name);\r\n });\r\n\r\n return {\r\n entries: mapped.slice(0, MAX_DIRECTORY_ENTRIES),\r\n totalCount: mapped.length,\r\n truncated: mapped.length > MAX_DIRECTORY_ENTRIES,\r\n };\r\n });\r\n}\r\n\r\nexport interface RemoteFileContent {\r\n content: string;\r\n /** Size the remote host reported; 0 for virtual files that do not declare one. */\r\n sizeBytes: number;\r\n /** Bytes actually read. */\r\n readBytes: number;\r\n truncated: boolean;\r\n}\r\n\r\n/**\r\n * Reads a remote file as UTF-8, stopping after `maxBytes`.\r\n *\r\n * Oversized files return their leading bytes rather than an error — a 2 GB log is exactly\r\n * the case where the first chunk is what you wanted anyway.\r\n *\r\n * A reported size of 0 does not mean empty: every file under /proc and most of /sys says 0\r\n * while holding real content. Those are read unbounded up to `maxBytes` instead of being\r\n * range-limited, which previously returned exactly one byte and called it the whole file.\r\n *\r\n * @throws {Error} If the path is missing, is a directory, or is not readable.\r\n */\r\nexport async function readRemoteFile(\r\n connection: SshConnection,\r\n remotePath: string,\r\n maxBytes: number\r\n): Promise<RemoteFileContent> {\r\n requirePermittedPath(connection, remotePath, \"read\");\r\n\r\n return await connection.withSftp(async (sftp: SFTPWrapper) => {\r\n const attrs = await new Promise<Stats>((resolve, reject) => {\r\n sftp.stat(remotePath, (error, stats) => {\r\n if (error != null) {\r\n reject(error);\r\n return;\r\n }\r\n\r\n resolve(stats);\r\n });\r\n });\r\n\r\n if (attrs.isDirectory() == true) {\r\n throw new Error(`${remotePath} is a directory, use ssh_list_dir instead`);\r\n }\r\n\r\n let declaredSize = 0;\r\n if (typeof attrs.size === \"number\" && Number.isFinite(attrs.size) == true) {\r\n declaredSize = attrs.size;\r\n }\r\n\r\n const isSizeKnown = declaredSize > 0;\r\n\r\n const streamOptions: { start?: number; end?: number } = {};\r\n if (isSizeKnown == true) {\r\n streamOptions.start = 0;\r\n streamOptions.end = Math.min(declaredSize, maxBytes) - 1;\r\n }\r\n\r\n const chunks: Buffer[] = [];\r\n let collectedBytes = 0;\r\n\r\n await new Promise<void>((resolve, reject) => {\r\n const stream = sftp.createReadStream(remotePath, streamOptions);\r\n\r\n stream.on(\"data\", (chunk: Buffer) => {\r\n chunks.push(chunk);\r\n collectedBytes += chunk.length;\r\n\r\n if (collectedBytes >= maxBytes) {\r\n stream.destroy();\r\n resolve();\r\n }\r\n });\r\n stream.on(\"error\", (error: Error) => {\r\n reject(error);\r\n });\r\n stream.on(\"end\", () => {\r\n resolve();\r\n });\r\n stream.on(\"close\", () => {\r\n resolve();\r\n });\r\n });\r\n\r\n const buffer = Buffer.concat(chunks, Math.min(collectedBytes, maxBytes));\r\n\r\n let truncated = false;\r\n if (isSizeKnown == true) {\r\n truncated = declaredSize > buffer.length;\r\n }\r\n else {\r\n truncated = buffer.length >= maxBytes;\r\n }\r\n\r\n // Decoded through StringDecoder for the same reason the exec path does: the cut\r\n // lands at an arbitrary byte offset, and a plain toString() turns the multi-byte\r\n // character straddling it into U+FFFD. `end()` drops the incomplete tail instead.\r\n const decoder = new StringDecoder(\"utf8\");\r\n const content = decoder.write(buffer) + decoder.end();\r\n\r\n return {\r\n content: content,\r\n sizeBytes: declaredSize,\r\n readBytes: buffer.length,\r\n truncated: truncated,\r\n };\r\n });\r\n}\r\n\r\n/**\r\n * Writes UTF-8 text to a remote path, replacing or appending.\r\n *\r\n * @throws {Error} If the parent directory is missing or the account cannot write there.\r\n */\r\nexport async function writeRemoteFile(\r\n connection: SshConnection,\r\n args: { remotePath: string; content: string; append: boolean }\r\n): Promise<number> {\r\n const { remotePath, content, append } = args;\r\n\r\n requirePermittedPath(connection, remotePath, \"write\");\r\n\r\n const payload = Buffer.from(content, \"utf8\");\r\n\r\n await connection.withSftp(async (sftp: SFTPWrapper) => {\r\n await new Promise<void>((resolve, reject) => {\r\n const callback = (error: Error | null | undefined): void => {\r\n if (error != null) {\r\n reject(error);\r\n return;\r\n }\r\n\r\n resolve();\r\n };\r\n\r\n if (append == true) {\r\n sftp.appendFile(remotePath, payload, callback);\r\n return;\r\n }\r\n\r\n sftp.writeFile(remotePath, payload, callback);\r\n });\r\n });\r\n\r\n const _logger = $logger.child({\r\n context: \"writeRemoteFile\",\r\n profile: connection.profile.name,\r\n remote_path: remotePath,\r\n append: append,\r\n });\r\n _logger.info(\"remote file written\", { bytes: payload.length });\r\n\r\n return payload.length;\r\n}\r\n\r\n/**\r\n * Uploads a local file with SFTP.\r\n *\r\n * @returns Bytes transferred.\r\n * @throws {Error} If the local file is missing or the remote path is not writable.\r\n */\r\nexport async function uploadFile(\r\n connection: SshConnection,\r\n localPath: string,\r\n remotePath: string\r\n): Promise<number> {\r\n requirePermittedPath(connection, remotePath, \"write\");\r\n\r\n const localStats = fs.statSync(localPath);\r\n if (localStats.isFile() == false) {\r\n throw new Error(`local path is not a file: ${localPath}`);\r\n }\r\n\r\n await connection.withSftp(async (sftp: SFTPWrapper) => {\r\n await new Promise<void>((resolve, reject) => {\r\n sftp.fastPut(localPath, remotePath, (error) => {\r\n if (error != null) {\r\n reject(error);\r\n return;\r\n }\r\n\r\n resolve();\r\n });\r\n });\r\n });\r\n\r\n const _logger = $logger.child({\r\n context: \"uploadFile\",\r\n profile: connection.profile.name,\r\n local_path: localPath,\r\n remote_path: remotePath,\r\n });\r\n _logger.info(\"file uploaded\", { bytes: localStats.size });\r\n\r\n return localStats.size;\r\n}\r\n\r\n/**\r\n * Downloads a remote file with SFTP.\r\n *\r\n * @returns Bytes written locally.\r\n * @throws {Error} If the remote file is missing or the local path is not writable.\r\n */\r\nexport async function downloadFile(\r\n connection: SshConnection,\r\n remotePath: string,\r\n localPath: string\r\n): Promise<number> {\r\n requirePermittedPath(connection, remotePath, \"read\");\r\n\r\n await connection.withSftp(async (sftp: SFTPWrapper) => {\r\n await new Promise<void>((resolve, reject) => {\r\n sftp.fastGet(remotePath, localPath, (error) => {\r\n if (error != null) {\r\n reject(error);\r\n return;\r\n }\r\n\r\n resolve();\r\n });\r\n });\r\n });\r\n\r\n const localStats = fs.statSync(localPath);\r\n\r\n const _logger = $logger.child({\r\n context: \"downloadFile\",\r\n profile: connection.profile.name,\r\n remote_path: remotePath,\r\n local_path: localPath,\r\n });\r\n _logger.info(\"file downloaded\", { bytes: localStats.size });\r\n\r\n return localStats.size;\r\n}\r\n","import { SINGLE_HOST_ENV } from \"@/_defs\";\r\nimport { formatBytes } from \"@/_libs\";\r\nimport type {\r\n GuardVerdict,\r\n SshExecResult,\r\n SshHostProfile,\r\n SshRemoteEntry,\r\n SshSessionInfo,\r\n} from \"@/_types\";\r\nimport { toHostSummary } from \"@/modules/config\";\r\nimport type { RemoteDirectoryListing } from \"@/modules/ssh\";\r\n\r\nconst ENTRY_TYPE_MARKERS: Record<SshRemoteEntry[\"type\"], string> = {\r\n file: \"-\",\r\n directory: \"d\",\r\n symlink: \"l\",\r\n other: \"?\",\r\n};\r\n\r\n/**\r\n * Renders the configured host for `ssh_list_hosts`, or setup instructions when none is\r\n * registered. Credential fields never appear — only which auth method is in use.\r\n */\r\nexport function formatHostProfile(profile: SshHostProfile | null): string {\r\n if (profile == null) {\r\n return [\r\n \"No SSH host is registered.\",\r\n \"\",\r\n \"Put the connection in this server's MCP entry — one server entry per host:\",\r\n \"\",\r\n ` \"env\": {`,\r\n ` \"${SINGLE_HOST_ENV.HOST}\": \"10.0.0.5\",`,\r\n ` \"${SINGLE_HOST_ENV.USER}\": \"deploy\",`,\r\n ` \"${SINGLE_HOST_ENV.KEY}\": \"~/.ssh/id_ed25519\"`,\r\n \" }\",\r\n \"\",\r\n `${SINGLE_HOST_ENV.HOST} and ${SINGLE_HOST_ENV.USER} are the whole minimum.`,\r\n \"Restart this server after changing it — environment variables are fixed for the life of the process.\",\r\n ].join(\"\\n\");\r\n }\r\n\r\n const summary = toHostSummary(profile);\r\n\r\n let accessLabel = \"writable\";\r\n if (summary.readonly == true) {\r\n accessLabel = \"READ-ONLY\";\r\n }\r\n\r\n let sudoLabel = \"sudo=disabled\";\r\n if (summary.allowSudo == true) {\r\n sudoLabel = \"sudo=enabled\";\r\n }\r\n\r\n const lines: string[] = [];\r\n lines.push(`${summary.name} → ${summary.username}@${summary.host}:${summary.port} auth=${summary.authMethod} ${accessLabel} ${sudoLabel}`);\r\n\r\n if (summary.description != null) {\r\n lines.push(` ${summary.description}`);\r\n }\r\n\r\n const routeParts: string[] = [];\r\n if (summary.viaProxyCommand == true) {\r\n routeParts.push(\"proxy command\");\r\n }\r\n if (summary.jump != null) {\r\n routeParts.push(`jump host ${summary.jump.username}@${summary.jump.host}:${summary.jump.port}`);\r\n }\r\n if (routeParts.length > 0) {\r\n lines.push(` via ${routeParts.join(\" → \")}`);\r\n }\r\n\r\n if (summary.allowedPaths.length > 0) {\r\n // Scope stated explicitly: allowedPaths binds the SFTP tools only, and reading\r\n // it as a filesystem-wide restriction would be a dangerous misunderstanding —\r\n // ssh_exec can still `cat` anything the remote account can read.\r\n lines.push(` file tools limited to: ${summary.allowedPaths.join(\", \")} (does not restrict ssh_exec)`);\r\n }\r\n\r\n if (summary.allowCommands.length > 0) {\r\n lines.push(` commands limited to: ${summary.allowCommands.join(\", \")}`);\r\n }\r\n\r\n return lines.join(\"\\n\");\r\n}\r\n\r\n/**\r\n * Renders a command result: a status line (exit code, duration, cwd), any timeout or\r\n * truncation notice, then the stdout and stderr sections.\r\n *\r\n * @param timeoutMs The limit that applied, quoted in the timeout notice.\r\n */\r\nexport function formatExecResult(result: SshExecResult, timeoutMs: number): string {\r\n const headerParts: string[] = [];\r\n\r\n if (result.exitCode != null) {\r\n headerParts.push(`exit code: ${result.exitCode}`);\r\n }\r\n else {\r\n headerParts.push(\"exit code: none (killed)\");\r\n }\r\n\r\n if (result.signal != null) {\r\n headerParts.push(`signal: ${result.signal}`);\r\n }\r\n\r\n headerParts.push(`${result.durationMs}ms`);\r\n\r\n if (result.cwd != null) {\r\n headerParts.push(`cwd: ${result.cwd}`);\r\n }\r\n\r\n const lines: string[] = [headerParts.join(\" · \")];\r\n\r\n if (result.timedOut == true) {\r\n lines.push(`TIMED OUT after ${timeoutMs}ms — the channel was closed, but the remote process may still be running.`);\r\n }\r\n\r\n if (result.truncated == true) {\r\n lines.push(\"Output was truncated; narrow the command (grep / head / tail) or raise maxOutputCharacters for this profile.\");\r\n }\r\n\r\n lines.push(\"\");\r\n lines.push(\"--- stdout ---\");\r\n if (result.stdout === \"\") {\r\n lines.push(\"(empty)\");\r\n }\r\n else {\r\n lines.push(result.stdout);\r\n }\r\n\r\n if (result.stderr !== \"\") {\r\n lines.push(\"\");\r\n lines.push(\"--- stderr ---\");\r\n lines.push(result.stderr);\r\n }\r\n\r\n return lines.join(\"\\n\");\r\n}\r\n\r\n/** Rejection message: what was blocked, why, and what would make it legal. */\r\nexport function formatGuardRejection(action: string, verdict: GuardVerdict): string {\r\n const lines: string[] = [`Blocked by the host profile's guard policy: ${verdict.reason}`];\r\n\r\n if (verdict.offendingText != null && verdict.offendingText !== action) {\r\n lines.push(`Offending part: ${verdict.offendingText}`);\r\n }\r\n\r\n lines.push(`Requested: ${action}`);\r\n lines.push(\"Nothing was sent to the remote host. Adjust the request, or relax the profile's policy in the config file.\");\r\n\r\n return lines.join(\"\\n\");\r\n}\r\n\r\n/** Renders open sessions with host, working directory, command count and idle time. */\r\nexport function formatSessionList(sessions: SshSessionInfo[]): string {\r\n if (sessions.length === 0) {\r\n return \"No open SSH sessions. Open one with ssh_connect, or pass 'target' to run a one-off command.\";\r\n }\r\n\r\n const lines: string[] = [`${sessions.length} open SSH session(s):`];\r\n const now = Date.now();\r\n\r\n for (const session of sessions) {\r\n const idleSeconds = Math.round((now - new Date(session.lastUsedAt).getTime()) / 1000);\r\n lines.push(\"\");\r\n lines.push(`${session.sessionId} → ${session.username}@${session.host} profile: ${session.profileName}`);\r\n lines.push(` cwd: ${session.cwd} · ${session.execCount} command(s) · idle ${idleSeconds}s · opened ${session.openedAt}`);\r\n }\r\n\r\n return lines.join(\"\\n\");\r\n}\r\n\r\n/**\r\n * Renders a directory listing as one row per entry (type, mode, size, mtime, name),\r\n * noting the true total when the listing was capped.\r\n */\r\nexport function formatDirectoryListing(remotePath: string, listing: RemoteDirectoryListing): string {\r\n if (listing.totalCount === 0) {\r\n return `${remotePath} is empty.`;\r\n }\r\n\r\n let header = `${remotePath} — ${listing.totalCount} entrie(s):`;\r\n if (listing.truncated == true) {\r\n header = `${remotePath} — ${listing.totalCount} entrie(s), showing the first ${listing.entries.length}:`;\r\n }\r\n\r\n const lines: string[] = [header, \"\"];\r\n\r\n for (const entry of listing.entries) {\r\n const marker = ENTRY_TYPE_MARKERS[entry.type];\r\n const size = formatBytes(entry.sizeBytes).padStart(9);\r\n\r\n let name = entry.name;\r\n if (entry.type === \"directory\") {\r\n name = `${entry.name}/`;\r\n }\r\n\r\n lines.push(`${marker} ${entry.mode} ${size} ${entry.modifiedAt} ${name}`);\r\n }\r\n\r\n if (listing.truncated == true) {\r\n lines.push(\"\");\r\n lines.push(\"Listing was capped; narrow the path or filter with ssh_exec if you need the rest.\");\r\n }\r\n\r\n return lines.join(\"\\n\");\r\n}\r\n","import type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\r\n\r\nimport { SINGLE_HOST_ENV } from \"@/_defs\";\r\nimport { $logger } from \"@/_libs\";\r\nimport type { SshHostProfile } from \"@/_types\";\r\nimport { GuardRejectionError } from \"@/modules/guard\";\r\nimport { SshConnection, SshSession, SshSessionManager } from \"@/modules/ssh\";\r\n\r\nimport { formatGuardRejection } from \"./format\";\r\n\r\n/**\r\n * Ceiling on simultaneous one-off connections, which the session manager never sees.\r\n * Matched to the session cap: an MCP client can dispatch a whole tool batch at once, and\r\n * a limit low enough to reject an ordinary batch is worse than the fan-out it prevents.\r\n */\r\nconst MAX_EPHEMERAL_CONNECTIONS = 32;\r\n\r\nlet activeEphemeralCount = 0;\r\n\r\n/** Everything the tool handlers share: the configured host and the live sessions. */\r\nexport interface ToolContext {\r\n /** The host from the `SSH_*` variables, or null when none is registered. */\r\n profile: SshHostProfile | null;\r\n sessions: SshSessionManager;\r\n}\r\n\r\nexport function textResult(text: string): CallToolResult {\r\n return { content: [{ type: \"text\", text: text }] };\r\n}\r\n\r\n/** Marked `isError` so the model sees the call failed rather than reading it as output. */\r\nexport function errorResult(message: string): CallToolResult {\r\n return { content: [{ type: \"text\", text: message }], isError: true };\r\n}\r\n\r\nexport function describeError(error: unknown): string {\r\n if (error instanceof Error) {\r\n return error.message;\r\n }\r\n\r\n return String(error);\r\n}\r\n\r\n/**\r\n * Renders a failure for the model, separating the two cases it must treat differently:\r\n * a guard rejection is a policy decision not worth retrying, anything else is an\r\n * operational failure that might succeed on a second attempt.\r\n */\r\nexport function toToolError(error: unknown, toolName: string, action: string): CallToolResult {\r\n const _logger = $logger.child({ context: toolName });\r\n\r\n if (error instanceof GuardRejectionError) {\r\n _logger.warn(\"rejected by the guard\", { reason: error.verdict.reason, action: action });\r\n\r\n const rendered = formatGuardRejection(action, error.verdict);\r\n return errorResult(rendered);\r\n }\r\n\r\n _logger.error(error, `${toolName} failed`);\r\n return errorResult(`${toolName} failed: ${describeError(error)}`);\r\n}\r\n\r\n/**\r\n * Resolves the optional `session` argument to a host, without connecting.\r\n *\r\n * A session carries the profile it was opened against, so it is preferred when supplied;\r\n * otherwise the one configured host is used. There is no host argument — this server\r\n * fronts exactly the host in its own env block, which is what keeps the model from being\r\n * able to name one.\r\n *\r\n * @throws {Error} If no host is registered, or the session id is not open.\r\n */\r\nexport function resolveProfile(\r\n context: ToolContext,\r\n args: { session?: string }\r\n): { profile: SshHostProfile; session: SshSession | null } {\r\n const hasSession = args.session != null && args.session.trim() !== \"\";\r\n if (hasSession == true) {\r\n const session = context.sessions.require((args.session as string).trim());\r\n return { profile: session.profile, session: session };\r\n }\r\n\r\n if (context.profile == null) {\r\n throw new Error(`no host is registered — set ${SINGLE_HOST_ENV.HOST} / ${SINGLE_HOST_ENV.USER} in this server's env block, then restart it`);\r\n }\r\n\r\n return { profile: context.profile, session: null };\r\n}\r\n\r\n/**\r\n * Runs `operation` against the resolved host.\r\n *\r\n * A session routes through `runExclusive`, which serializes work and keeps the idle\r\n * sweeper away; without one a one-off connection is opened and closed around the call.\r\n * The connection is created but not dialled — `exec` / `withSftp` connect on demand, so a\r\n * command the guard refuses never costs a handshake.\r\n *\r\n * @throws {Error} If the one-off connection ceiling is reached.\r\n */\r\nexport async function withHostConnection<T>(\r\n profile: SshHostProfile,\r\n session: SshSession | null,\r\n operation: (connection: SshConnection) => Promise<T>\r\n): Promise<T> {\r\n if (session != null) {\r\n return await session.runExclusive(operation);\r\n }\r\n\r\n if (activeEphemeralCount >= MAX_EPHEMERAL_CONNECTIONS) {\r\n throw new Error(`too many concurrent one-off connections (${MAX_EPHEMERAL_CONNECTIONS}); open a session with ssh_connect and reuse it`);\r\n }\r\n\r\n activeEphemeralCount += 1;\r\n const connection = new SshConnection(profile);\r\n\r\n try {\r\n return await operation(connection);\r\n }\r\n finally {\r\n // Nested finally: a throwing disconnect must not leak the slot, or the ceiling\r\n // ratchets down until every call fails.\r\n try {\r\n connection.disconnect();\r\n }\r\n finally {\r\n activeEphemeralCount -= 1;\r\n }\r\n }\r\n}\r\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { z } from \"zod\";\r\n\r\nimport { $logger } from \"@/_libs\";\r\nimport type { SshExecResult } from \"@/_types\";\r\n\r\nimport { formatExecResult } from \"./format\";\r\nimport { resolveProfile, textResult, toToolError, withHostConnection } from \"./tool-context\";\r\nimport type { ToolContext } from \"./tool-context\";\r\n\r\nexport function registerExecTools(server: McpServer, context: ToolContext): void {\r\n server.registerTool(\r\n \"ssh_exec\",\r\n {\r\n title: \"Run a command over SSH\",\r\n description: [\r\n \"Run a shell command on the configured SSH host and return stdout, stderr and the exit code.\",\r\n \"Pass 'session' to reuse an open session, which remembers its working directory; omit it and a one-off connection is opened and closed around the call.\",\r\n \"A non-zero exit code is reported as normal output, not as a tool error — read the exit code and stderr to judge the outcome.\",\r\n \"Almost everything is permitted: package installs, service restarts, sudo, interpreters. Only catastrophic operations are refused outright (wiping the filesystem root, formatting a disk, rebooting, flushing the firewall), and the host may add its own limits.\",\r\n ].join(\" \"),\r\n inputSchema: {\r\n command: z.string().min(1).describe(\"Shell command, exactly as it would be typed in a terminal. Chaining with ';', '&&', '||' and pipes is allowed; each part is screened separately.\"),\r\n session: z.string().optional().describe(\"Session id from ssh_connect. Reuses that connection and keeps the working directory across calls.\"),\r\n cwd: z.string().optional().describe(\"Absolute directory to run in, with no shell metacharacters. With a session, the session also moves there.\"),\r\n timeoutMs: z.number().int().positive().optional().describe(\"Lowers the command timeout for this call; it cannot exceed the profile's own limit. The channel is closed when it elapses; the remote process may survive.\"),\r\n },\r\n annotations: {\r\n readOnlyHint: false,\r\n destructiveHint: true,\r\n idempotentHint: false,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ command, session, cwd, timeoutMs }) => {\r\n let _logger = $logger.child({ context: \"ssh_exec\" });\r\n\r\n try {\r\n const { profile, session: openSession } = resolveProfile(context, { session: session });\r\n _logger = _logger.child({ profile: profile.name, session_id: openSession?.sessionId });\r\n\r\n // Clamped, not defaulted: a per-call override that could exceed the profile\r\n // ceiling would make the ceiling advisory.\r\n const requestedTimeoutMs = timeoutMs ?? profile.policy.execTimeoutMs;\r\n const effectiveTimeoutMs = Math.min(requestedTimeoutMs, profile.policy.execTimeoutMs);\r\n\r\n // An empty string is \"not specified\", not \"the root of nowhere\" — passing it\r\n // through would silently reset a session's tracked directory.\r\n let requestedCwd: string | undefined = undefined;\r\n if (cwd != null && cwd.trim() !== \"\") {\r\n requestedCwd = cwd.trim();\r\n }\r\n\r\n _logger.debug(\"running command\", { command: command, timeout_ms: effectiveTimeoutMs });\r\n\r\n let result: SshExecResult;\r\n if (openSession != null) {\r\n result = await openSession.exec(command, { cwd: requestedCwd, timeoutMs: effectiveTimeoutMs });\r\n }\r\n else {\r\n result = await withHostConnection(profile, null, async (connection) => {\r\n return await connection.exec({\r\n command: command,\r\n cwd: requestedCwd,\r\n timeoutMs: effectiveTimeoutMs,\r\n maxOutputCharacters: profile.policy.maxOutputCharacters,\r\n trackCwd: false,\r\n });\r\n });\r\n }\r\n\r\n _logger.info(\"command executed\", {\r\n exit_code: result.exitCode,\r\n duration_ms: result.durationMs,\r\n timed_out: result.timedOut,\r\n });\r\n\r\n const rendered = formatExecResult(result, effectiveTimeoutMs);\r\n return textResult(rendered);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_exec\", command);\r\n }\r\n }\r\n );\r\n}\r\n","import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\n\r\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { z } from \"zod\";\r\n\r\nimport { $logger, expandHomePath, formatBytes } from \"@/_libs\";\r\nimport { GuardRejectionError, inspectLocalPath } from \"@/modules/guard\";\r\nimport {\r\n downloadFile,\r\n listRemoteDirectory,\r\n readRemoteFile,\r\n uploadFile,\r\n writeRemoteFile,\r\n} from \"@/modules/ssh\";\r\n\r\nimport { formatDirectoryListing } from \"./format\";\r\nimport {\r\n resolveProfile,\r\n textResult,\r\n toToolError,\r\n withHostConnection,\r\n} from \"./tool-context\";\r\nimport type { ToolContext } from \"./tool-context\";\r\n\r\nconst SESSION_DESCRIPTION = \"Session id from ssh_connect, whose connection is reused. Omit it to open a one-off connection.\";\r\n\r\n/**\r\n * Screens a path on the machine running this server.\r\n *\r\n * The remote side is guarded inside the SFTP layer, where the policy lives. The local\r\n * side has no policy — it is the operator's own filesystem — so it is checked here, at\r\n * the only layer that knows the path came from a tool argument.\r\n *\r\n * @throws {GuardRejectionError} If the path is protected.\r\n */\r\nfunction requirePermittedLocalPath(localPath: string, access: \"read\" | \"write\"): void {\r\n const verdict = inspectLocalPath(localPath, access);\r\n if (verdict.allowed == false) {\r\n throw new GuardRejectionError(verdict);\r\n }\r\n}\r\n\r\nexport function registerFileTools(server: McpServer, context: ToolContext): void {\r\n server.registerTool(\r\n \"ssh_list_dir\",\r\n {\r\n title: \"List a remote directory\",\r\n description: [\r\n \"List a directory on the configured SSH host over SFTP, with type, permissions, size and mtime.\",\r\n \"Cheaper and more structured than running 'ls -la' through ssh_exec, and it works on a read-only host.\",\r\n ].join(\" \"),\r\n inputSchema: {\r\n path: z.string().min(1).describe(\"Absolute remote directory path. When SSH_ALLOWED_PATHS is set, only paths inside it are accepted.\"),\r\n session: z.string().optional().describe(SESSION_DESCRIPTION),\r\n },\r\n annotations: {\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ path: remotePath, session }) => {\r\n const _logger = $logger.child({ context: \"ssh_list_dir\", remote_path: remotePath });\r\n\r\n try {\r\n const { profile, session: openSession } = resolveProfile(context, { session: session });\r\n\r\n const listing = await withHostConnection(profile, openSession, async (connection) => {\r\n return await listRemoteDirectory(connection, remotePath);\r\n });\r\n\r\n _logger.info(\"remote directory listed\", {\r\n profile: profile.name,\r\n entry_count: listing.totalCount,\r\n truncated: listing.truncated,\r\n });\r\n const rendered = formatDirectoryListing(remotePath, listing);\r\n return textResult(rendered);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_list_dir\", `list ${remotePath}`);\r\n }\r\n }\r\n );\r\n\r\n server.registerTool(\r\n \"ssh_read_file\",\r\n {\r\n title: \"Read a remote file\",\r\n description: [\r\n \"Read a remote text file over SFTP and return its contents.\",\r\n \"Oversized files come back truncated to their leading bytes rather than failing, so pointing this at a large log is safe.\",\r\n \"Files that report size 0 but hold content (/proc, /sys) are read in full up to the ceiling.\",\r\n ].join(\" \"),\r\n inputSchema: {\r\n path: z.string().min(1).describe(\"Absolute remote file path.\"),\r\n session: z.string().optional().describe(SESSION_DESCRIPTION),\r\n maxBytes: z.number().int().positive().optional().describe(\"Lowers the read ceiling for this call; it cannot exceed SSH_MAX_READ_BYTES.\"),\r\n },\r\n annotations: {\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ path: remotePath, session, maxBytes }) => {\r\n const _logger = $logger.child({ context: \"ssh_read_file\", remote_path: remotePath });\r\n\r\n try {\r\n const { profile, session: openSession } = resolveProfile(context, { session: session });\r\n\r\n // Clamped, not defaulted: an override above the profile ceiling would let one\r\n // call buffer hundreds of megabytes and take the server down.\r\n const requestedLimit = maxBytes ?? profile.policy.maxReadFileBytes;\r\n const readLimit = Math.min(requestedLimit, profile.policy.maxReadFileBytes);\r\n\r\n const file = await withHostConnection(profile, openSession, async (connection) => {\r\n return await readRemoteFile(connection, remotePath, readLimit);\r\n });\r\n\r\n let header = `${remotePath} — ${formatBytes(file.readBytes)} read`;\r\n if (file.sizeBytes > 0) {\r\n header = `${remotePath} — ${formatBytes(file.sizeBytes)}`;\r\n }\r\n\r\n if (file.truncated == true) {\r\n header += `, truncated to the first ${formatBytes(file.readBytes)}`;\r\n }\r\n\r\n _logger.info(\"remote file read\", {\r\n profile: profile.name,\r\n size_bytes: file.sizeBytes,\r\n read_bytes: file.readBytes,\r\n truncated: file.truncated,\r\n });\r\n return textResult(`${header}\\n\\n${file.content}`);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_read_file\", `read ${remotePath}`);\r\n }\r\n }\r\n );\r\n\r\n server.registerTool(\r\n \"ssh_write_file\",\r\n {\r\n title: \"Write a remote file\",\r\n description: [\r\n \"Write or append UTF-8 text to a file on the configured SSH host over SFTP.\",\r\n \"Use this instead of 'echo ... > file' through ssh_exec: no shell quoting to get wrong, and it works with multi-line content.\",\r\n \"Refused on a read-only host. The parent directory must already exist.\",\r\n ].join(\" \"),\r\n inputSchema: {\r\n path: z.string().min(1).describe(\"Absolute remote file path. Existing content is replaced unless 'append' is true.\"),\r\n content: z.string().describe(\"UTF-8 text to write.\"),\r\n session: z.string().optional().describe(SESSION_DESCRIPTION),\r\n append: z.boolean().optional().describe(\"Append instead of replacing. Defaults to false.\"),\r\n },\r\n annotations: {\r\n readOnlyHint: false,\r\n destructiveHint: true,\r\n idempotentHint: false,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ path: remotePath, content, session, append }) => {\r\n const _logger = $logger.child({ context: \"ssh_write_file\", remote_path: remotePath });\r\n\r\n try {\r\n const { profile, session: openSession } = resolveProfile(context, { session: session });\r\n const shouldAppend = append ?? false;\r\n\r\n const writtenBytes = await withHostConnection(profile, openSession, async (connection) => {\r\n return await writeRemoteFile(connection, {\r\n remotePath: remotePath,\r\n content: content,\r\n append: shouldAppend,\r\n });\r\n });\r\n\r\n let verb = \"Wrote\";\r\n if (shouldAppend == true) {\r\n verb = \"Appended\";\r\n }\r\n\r\n _logger.info(\"remote file written\", { profile: profile.name, bytes: writtenBytes });\r\n return textResult(`${verb} ${formatBytes(writtenBytes)} to ${remotePath} on ${profile.name}.`);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_write_file\", `write ${remotePath}`);\r\n }\r\n }\r\n );\r\n\r\n server.registerTool(\r\n \"ssh_upload\",\r\n {\r\n title: \"Upload a file over SFTP\",\r\n description: [\r\n \"Upload a local file to the configured SSH host over SFTP.\",\r\n \"Refused on a read-only host, and for local paths holding credentials (SSH keys, cloud credentials).\",\r\n \"The remote parent directory must already exist.\",\r\n ].join(\" \"),\r\n inputSchema: {\r\n localPath: z.string().min(1).describe(\"Local file path on the machine running this MCP server. '~' is expanded.\"),\r\n remotePath: z.string().min(1).describe(\"Absolute destination path on the remote host, including the filename.\"),\r\n session: z.string().optional().describe(SESSION_DESCRIPTION),\r\n },\r\n annotations: {\r\n readOnlyHint: false,\r\n destructiveHint: true,\r\n idempotentHint: true,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ localPath, remotePath, session }) => {\r\n const _logger = $logger.child({ context: \"ssh_upload\", remote_path: remotePath });\r\n\r\n try {\r\n const { profile, session: openSession } = resolveProfile(context, { session: session });\r\n\r\n const resolvedLocalPath = expandHomePath(localPath);\r\n requirePermittedLocalPath(resolvedLocalPath, \"read\");\r\n\r\n if (fs.existsSync(resolvedLocalPath) == false) {\r\n return textResult(`local file not found: ${resolvedLocalPath}`);\r\n }\r\n\r\n const transferredBytes = await withHostConnection(profile, openSession, async (connection) => {\r\n return await uploadFile(connection, resolvedLocalPath, remotePath);\r\n });\r\n\r\n _logger.info(\"file uploaded\", { profile: profile.name, bytes: transferredBytes });\r\n return textResult(`Uploaded ${resolvedLocalPath} → ${remotePath} (${formatBytes(transferredBytes)}) on ${profile.name}.`);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_upload\", `upload to ${remotePath}`);\r\n }\r\n }\r\n );\r\n\r\n server.registerTool(\r\n \"ssh_download\",\r\n {\r\n title: \"Download a file over SFTP\",\r\n description: [\r\n \"Download a file from the configured SSH host to the machine running this MCP server.\",\r\n \"Use this for binaries or files too large to read into the conversation; missing local directories are created.\",\r\n \"The local destination is overwritten, and paths holding credentials are refused.\",\r\n ].join(\" \"),\r\n inputSchema: {\r\n remotePath: z.string().min(1).describe(\"Absolute source path on the remote host.\"),\r\n localPath: z.string().min(1).describe(\"Local destination path, including the filename. '~' is expanded. Overwritten if it exists.\"),\r\n session: z.string().optional().describe(SESSION_DESCRIPTION),\r\n },\r\n annotations: {\r\n readOnlyHint: false,\r\n destructiveHint: true,\r\n idempotentHint: true,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ remotePath, localPath, session }) => {\r\n const _logger = $logger.child({ context: \"ssh_download\", remote_path: remotePath });\r\n\r\n try {\r\n const { profile, session: openSession } = resolveProfile(context, { session: session });\r\n\r\n const resolvedLocalPath = expandHomePath(localPath);\r\n requirePermittedLocalPath(resolvedLocalPath, \"write\");\r\n\r\n fs.mkdirSync(path.dirname(resolvedLocalPath), { recursive: true });\r\n\r\n const transferredBytes = await withHostConnection(profile, openSession, async (connection) => {\r\n return await downloadFile(connection, remotePath, resolvedLocalPath);\r\n });\r\n\r\n _logger.info(\"file downloaded\", { profile: profile.name, bytes: transferredBytes });\r\n return textResult(`Downloaded ${remotePath} → ${resolvedLocalPath} (${formatBytes(transferredBytes)}) from ${profile.name}.`);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_download\", `download ${remotePath}`);\r\n }\r\n }\r\n );\r\n}\r\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\n\r\nimport { $logger } from \"@/_libs\";\r\n\r\nimport { formatHostProfile } from \"./format\";\r\nimport { textResult, toToolError } from \"./tool-context\";\r\nimport type { ToolContext } from \"./tool-context\";\r\n\r\nexport function registerHostTools(server: McpServer, context: ToolContext): void {\r\n server.registerTool(\r\n \"ssh_list_hosts\",\r\n {\r\n title: \"Show the SSH host\",\r\n description: [\r\n \"Show the SSH host this server connects to, with its guard policy.\",\r\n \"Call this first: it is the only reachable host, and its policy decides what the other tools will accept.\",\r\n \"Credentials are never returned — only which auth method the host uses.\",\r\n ].join(\" \"),\r\n inputSchema: {},\r\n annotations: {\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: false,\r\n },\r\n },\r\n async () => {\r\n const _logger = $logger.child({ context: \"ssh_list_hosts\" });\r\n\r\n try {\r\n const listing = formatHostProfile(context.profile);\r\n _logger.debug(\"host profile returned\", { profile: context.profile?.name });\r\n return textResult(listing);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_list_hosts\", \"show the host\");\r\n }\r\n }\r\n );\r\n}\r\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { z } from \"zod\";\r\n\r\nimport { $logger } from \"@/_libs\";\r\n\r\nimport { formatSessionList } from \"./format\";\r\nimport { errorResult, resolveProfile, textResult, toToolError } from \"./tool-context\";\r\nimport type { ToolContext } from \"./tool-context\";\r\n\r\nexport function registerSessionTools(server: McpServer, context: ToolContext): void {\r\n server.registerTool(\r\n \"ssh_connect\",\r\n {\r\n title: \"Open an SSH session\",\r\n description: [\r\n \"Open a reusable SSH session to the configured host and return its session id.\",\r\n \"Prefer this over one-off calls when running several commands: it pays the handshake once and remembers the working directory, so a 'cd' in one ssh_exec still applies to the next.\",\r\n \"Close it with ssh_disconnect when done; idle sessions are reaped automatically, and a session whose connection drops reports that rather than silently reconnecting.\",\r\n ].join(\" \"),\r\n inputSchema: {\r\n cwd: z.string().optional().describe(\"Absolute directory to start in, with no shell metacharacters. Defaults to SSH_CWD, else the login shell's directory.\"),\r\n },\r\n annotations: {\r\n readOnlyHint: false,\r\n destructiveHint: false,\r\n idempotentHint: false,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ cwd }) => {\r\n let _logger = $logger.child({ context: \"ssh_connect\" });\r\n\r\n // Tracked outside the try so a probe that *throws* still tears the session\r\n // down — leaking it would burn one of the 32 slots until the sweeper ran.\r\n let openedSessionId: string | null = null;\r\n\r\n try {\r\n const { profile } = resolveProfile(context, {});\r\n const session = await context.sessions.open(profile);\r\n openedSessionId = session.sessionId;\r\n _logger = _logger.child({ session_id: session.sessionId });\r\n\r\n if (cwd != null && cwd.trim() !== \"\") {\r\n session.cwd = cwd.trim();\r\n }\r\n\r\n // One `pwd` settles the real starting directory (and proves `cwd` exists),\r\n // so the session reports a concrete path instead of \"login default\".\r\n const probe = await session.exec(\"pwd\");\r\n\r\n // `exitCode === null` means the channel closed with no exit status — the\r\n // probe never proved anything, so it counts as a failure rather than a pass.\r\n const probeSucceeded = probe.exitCode === 0 && probe.timedOut == false;\r\n if (probeSucceeded == false) {\r\n context.sessions.close(session.sessionId);\r\n openedSessionId = null;\r\n _logger.warn(\"session probe failed, closing the session\", {\r\n exit_code: probe.exitCode,\r\n timed_out: probe.timedOut,\r\n stderr: probe.stderr,\r\n });\r\n return errorResult(`ssh_connect failed: could not confirm the starting directory.\\n${probe.stderr.trim()}`);\r\n }\r\n\r\n const lines: string[] = [\r\n `Opened session ${session.sessionId} → ${profile.username}@${profile.host}:${profile.port}`,\r\n `cwd: ${session.cwd}`,\r\n ];\r\n\r\n if (profile.policy.readonly == true) {\r\n lines.push(\"This host is READ-ONLY: writes, redirections and uploads will be rejected.\");\r\n }\r\n\r\n lines.push(`Run commands with ssh_exec({ session: \"${session.sessionId}\", command: \"...\" }).`);\r\n\r\n _logger.info(\"session opened via tool\", { cwd: session.cwd });\r\n return textResult(lines.join(\"\\n\"));\r\n }\r\n catch (ex) {\r\n if (openedSessionId != null) {\r\n context.sessions.close(openedSessionId);\r\n }\r\n\r\n return toToolError(ex, \"ssh_connect\", \"connect to the configured host\");\r\n }\r\n }\r\n );\r\n\r\n server.registerTool(\r\n \"ssh_disconnect\",\r\n {\r\n title: \"Close an SSH session\",\r\n description: \"Close an open SSH session and release its connection.\",\r\n inputSchema: {\r\n session: z.string().describe(\"Session id returned by ssh_connect.\"),\r\n },\r\n annotations: {\r\n readOnlyHint: false,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ session }) => {\r\n const _logger = $logger.child({ context: \"ssh_disconnect\", session_id: session });\r\n\r\n try {\r\n const closed = context.sessions.close(session);\r\n if (closed == false) {\r\n return errorResult(`no open session with id '${session}'. Call ssh_list_sessions to see what is open.`);\r\n }\r\n\r\n _logger.info(\"session closed via tool\");\r\n return textResult(`Closed session ${session}.`);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_disconnect\", `close ${session}`);\r\n }\r\n }\r\n );\r\n\r\n server.registerTool(\r\n \"ssh_list_sessions\",\r\n {\r\n title: \"List open SSH sessions\",\r\n description: \"List every open SSH session with its host, working directory and idle time.\",\r\n inputSchema: {},\r\n annotations: {\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: false,\r\n },\r\n },\r\n async () => {\r\n try {\r\n const sessions = context.sessions.list();\r\n const rendered = formatSessionList(sessions);\r\n return textResult(rendered);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_list_sessions\", \"list sessions\");\r\n }\r\n }\r\n );\r\n}\r\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\n\r\nimport { registerExecTools } from \"./exec-tools\";\r\nimport { registerFileTools } from \"./file-tools\";\r\nimport { registerHostTools } from \"./host-tools\";\r\nimport { registerSessionTools } from \"./session-tools\";\r\nimport type { ToolContext } from \"./tool-context\";\r\n\r\nexport { registerHostTools } from \"./host-tools\";\r\nexport { registerSessionTools } from \"./session-tools\";\r\nexport { registerExecTools } from \"./exec-tools\";\r\nexport { registerFileTools } from \"./file-tools\";\r\n\r\nexport {\r\n textResult,\r\n errorResult,\r\n describeError,\r\n toToolError,\r\n resolveProfile,\r\n withHostConnection,\r\n} from \"./tool-context\";\r\nexport type { ToolContext } from \"./tool-context\";\r\n\r\nexport {\r\n formatHostProfile,\r\n formatExecResult,\r\n formatGuardRejection,\r\n formatSessionList,\r\n formatDirectoryListing,\r\n} from \"./format\";\r\n\r\n/** Registers every tool this server exposes, in the order the model should discover them. */\r\nexport function registerAllTools(server: McpServer, context: ToolContext): void {\r\n registerHostTools(server, context);\r\n registerSessionTools(server, context);\r\n registerExecTools(server, context);\r\n registerFileTools(server, context);\r\n}\r\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\n\r\nimport { SERVER_NAME, SERVER_VERSION } from \"@/_defs\";\r\nimport { $logger } from \"@/_libs\";\r\nimport type { SshHostProfile } from \"@/_types\";\r\nimport { loadHostProfile } from \"@/modules/config\";\r\nimport { SshSessionManager } from \"@/modules/ssh\";\r\nimport { registerAllTools } from \"@/modules/tools\";\r\nimport type { ToolContext } from \"@/modules/tools\";\r\n\r\nexport interface SshMcpServerInstance {\r\n server: McpServer;\r\n sessions: SshSessionManager;\r\n /** The host read from the `SSH_*` variables at startup, or null when none is set. */\r\n readonly profile: SshHostProfile | null;\r\n /** Stops the sweeper and closes every open session. Call before the process exits. */\r\n shutdown(): void;\r\n}\r\n\r\n/** Handed to the client on connect, so the model knows the workflow before its first call. */\r\nconst SERVER_INSTRUCTIONS = [\r\n \"This server runs shell commands and transfers files on one remote host over SSH.\",\r\n \"The host is fixed in this server's own environment — there is no host argument, and no other machine is reachable through it.\",\r\n \"\",\r\n \"ssh_list_hosts shows which host this is and what it permits. Call it before assuming anything about access.\",\r\n \"\",\r\n \"Workflow:\",\r\n \"1. One command: ssh_exec with just 'command'.\",\r\n \"2. Several commands in a row: ssh_connect once, then ssh_exec with 'session' (the working directory persists across calls), then ssh_disconnect.\",\r\n \"3. Files: ssh_list_dir, ssh_read_file, ssh_write_file, ssh_upload, ssh_download — prefer these over shell equivalents; no quoting to get wrong.\",\r\n \"\",\r\n \"What is permitted: ordinary administration, without asking — package installs, service restarts, sudo, interpreters, config edits, deployments.\",\r\n \"Refused always, and only these: operations that destroy the machine or cut this connection — wiping the filesystem root or a system directory, deleting a file the host cannot boot without, formatting a disk, writing raw bytes to a block device, rebooting or powering off, flushing the firewall, removing every cron job.\",\r\n \"The host may add its own limits (read-only, an allow-list, deny patterns, path restrictions). ssh_list_hosts shows which apply.\",\r\n \"\",\r\n \"Commands run with no terminal and with stdin closed, so anything that waits for input fails immediately instead of hanging: use non-interactive flags (apt-get -y, sudo -n), 'top -bn1' rather than 'top', and ssh_write_file rather than an editor.\",\r\n \"A job that may outlive its timeout should be started detached (nohup … &, tmux new -d) and polled through its log.\",\r\n \"\",\r\n \"A guard rejection is a policy decision, not a transient failure — do not retry the same command; report it and ask before working around it.\",\r\n \"A non-zero exit code is returned as normal output; read it and stderr to judge what happened.\",\r\n \"You still carry the judgement the guards do not: this is a real host, and an irreversible action deserves a check with the operator first.\",\r\n].join(\"\\n\");\r\n\r\n/**\r\n * Builds the MCP server: reads the host from the environment, wires the session manager,\r\n * registers tools.\r\n *\r\n * Transport is deliberately left to the caller — `cli.ts` attaches stdio, tests can attach\r\n * an in-memory pair.\r\n *\r\n * @throws {Error} If the `SSH_*` variables are malformed (a bad policy value must not\r\n * silently degrade into \"no guard\").\r\n */\r\nexport function createSshMcpServer(): SshMcpServerInstance {\r\n const _logger = $logger.child({ context: \"createSshMcpServer\" });\r\n\r\n const profile = loadHostProfile();\r\n\r\n const sessions = new SshSessionManager();\r\n sessions.startIdleSweeper();\r\n\r\n const server = new McpServer(\r\n {\r\n name: SERVER_NAME,\r\n version: SERVER_VERSION,\r\n },\r\n {\r\n instructions: SERVER_INSTRUCTIONS,\r\n }\r\n );\r\n\r\n const context: ToolContext = {\r\n profile: profile,\r\n sessions: sessions,\r\n };\r\n\r\n registerAllTools(server, context);\r\n\r\n _logger.info(\"mcp server built\", { profile: profile?.name ?? null });\r\n\r\n return {\r\n server: server,\r\n sessions: sessions,\r\n profile: profile,\r\n shutdown(): void {\r\n sessions.stopIdleSweeper();\r\n sessions.closeAll();\r\n },\r\n };\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/_defs/defaults.ts","../src/_defs/command-rules.ts","../src/_defs/protected-paths.ts","../src/_defs/env-vars.ts","../src/_libs/logger.ts","../src/_libs/text.ts","../src/_libs/fs-path.ts","../src/modules/config/host-profile.ts","../src/modules/guard/command-guard.ts","../src/modules/guard/path-guard.ts","../src/modules/guard/guard-error.ts","../src/modules/ssh/ssh-connection.ts","../src/modules/ssh/session-manager.ts","../src/modules/ssh/sftp-operations.ts","../src/modules/tools/format.ts","../src/modules/tools/tool-context.ts","../src/modules/tools/exec-tools.ts","../src/modules/tools/file-tools.ts","../src/modules/tools/host-tools.ts","../src/modules/tools/session-tools.ts","../src/modules/tools/index.ts","../src/server.ts"],"names":["path","fs","StringDecoder","z"],"mappings":"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","file":"index.js","sourcesContent":["/** Env var overriding the stderr log level (\"debug\" | \"info\" | \"warn\" | \"error\" | \"silent\"). */\r\nexport const LOG_LEVEL_ENV = \"SSH_MCP_LOG_LEVEL\";\r\n\r\nexport const DEFAULT_SSH_PORT = 22;\r\n\r\nexport const DEFAULT_EXEC_TIMEOUT_MS = 60_000;\r\n\r\nexport const DEFAULT_CONNECT_TIMEOUT_MS = 20_000;\r\n\r\n/** stdout / stderr cutoff — roughly 25k tokens of context at worst. */\r\nexport const DEFAULT_MAX_OUTPUT_CHARACTERS = 100_000;\r\n\r\nexport const DEFAULT_MAX_READ_FILE_BYTES = 200_000;\r\n\r\n/** ssh2 keepalive so idle sessions survive NAT / firewall timeouts. */\r\nexport const KEEPALIVE_INTERVAL_MS = 15_000;\r\n\r\n/** Sessions untouched for this long are closed by the sweeper. */\r\nexport const SESSION_IDLE_TIMEOUT_MS = 15 * 60 * 1000;\r\n\r\nexport const SESSION_SWEEP_INTERVAL_MS = 60_000;\r\n\r\n/** Shutdown waits this long for the sessions and the transport to close, then exits anyway — a dead peer must not keep the process alive. */\r\nexport const SHUTDOWN_DEADLINE_MS = 5_000;\r\n\r\n/** Emitted after the user command so the session can track `cd` between execs. */\r\nexport const CWD_MARKER = \"__AKMS_SSH_CWD__\";\r\n\r\n/** `ssh_list_dir` stops here — a directory with 500k entries would otherwise return megabytes. */\r\nexport const MAX_DIRECTORY_ENTRIES = 1_000;\r\n\r\nexport const SERVER_NAME = \"akms-mcp-ssh\";\r\n\r\n/** What `npx -y …` resolves — the `args` the `config` command writes into every entry. */\r\nexport const PACKAGE_NAME = \"@akms/mcp-ssh\";\r\n\r\nexport const SERVER_VERSION = \"0.2.1\";\r\n","/** One deny rule: a regex tested against a command segment plus the message shown on a hit. */\r\nexport interface CommandRule {\r\n pattern: RegExp;\r\n reason: string;\r\n}\r\n\r\n/**\r\n * A rule keyed on the resolved binary plus the shape of its operands.\r\n *\r\n * Regex-only rules could not express \"`rm -rf` where *any* operand is a critical path\" —\r\n * an anchored pattern reads operands positionally, so `rm -rf /tmp/x /` slipped through\r\n * because the root argument was not the first one.\r\n */\r\nexport interface ArgumentRule {\r\n binaries: string[];\r\n /** Rule fires only when at least one flag matches this, when set. */\r\n requiredFlagPattern?: RegExp;\r\n /** Tested against every non-flag operand. */\r\n argumentPattern: RegExp;\r\n reason: string;\r\n}\r\n\r\n/**\r\n * Directories whose recursive deletion or permission rewrite destroys the machine.\r\n * Anchored as a whole-operand match, so `/var/log/myapp` stays allowed while\r\n * `/var`, `/var/`, `/var/*` do not.\r\n */\r\nexport const CRITICAL_PATH_PATTERN = new RegExp(\r\n \"^(?:\"\r\n + \"/|//|/\\\\*|/\\\\.|/\\\\.\\\\.|/\\\\*/|//\\\\*|\"\r\n + \"~|~/|~/\\\\*|\\\\$HOME|\\\\$HOME/|\\\\$HOME/\\\\*|\\\\$\\\\{HOME\\\\}/?\\\\*?|\"\r\n + \"/(?:bin|boot|dev|etc|home|lib|lib32|lib64|libx32|media|mnt|opt|proc|root|run|sbin|srv|sys|usr|var|data)(?:/\\\\*)?/?\"\r\n + \")$\",\r\n \"i\"\r\n);\r\n\r\n/** Individual files whose loss alone bricks the host or locks everyone out. */\r\nexport const CRITICAL_FILE_PATTERN = new RegExp(\r\n \"^/(?:\"\r\n + \"etc/(?:passwd|shadow|group|sudoers|fstab|hostname|hosts|resolv\\\\.conf)\"\r\n + \"|boot/(?:vmlinuz|initrd|grub).*\"\r\n + \"|etc/ssh/sshd_config\"\r\n + \")$\",\r\n \"i\"\r\n);\r\n\r\n/**\r\n * Binaries that only wrap another command — the guard steps past them (and their flags,\r\n * `FOO=bar` assignments, and numeric operands like `timeout 30`) to the binary that runs.\r\n */\r\nexport const COMMAND_WRAPPER_BINARIES = [\r\n \"sudo\",\r\n \"doas\",\r\n \"env\",\r\n \"eval\",\r\n \"nohup\",\r\n \"time\",\r\n \"nice\",\r\n \"ionice\",\r\n \"timeout\",\r\n \"stdbuf\",\r\n \"command\",\r\n \"exec\",\r\n \"builtin\",\r\n \"watch\",\r\n \"xargs\",\r\n \"setsid\",\r\n \"unbuffer\",\r\n \"busybox\",\r\n \"toybox\",\r\n];\r\n\r\n/**\r\n * `timeout 30 cmd`, `nice -n 10 cmd`, `watch 2 cmd` — these wrappers take a bare numeric\r\n * operand before the real command, so a duration must not be mistaken for the binary.\r\n */\r\nexport const NUMERIC_OPERAND_PATTERN = /^\\d+(?:\\.\\d+)?[smhd]?$/i;\r\n\r\n/**\r\n * Wrapper flags that consume the following token as their value.\r\n *\r\n * Without this, `env -u FOO rm -rf /` resolves its binary to `FOO`: the flag is skipped,\r\n * its value is not, and every binary-keyed rule then looks at the wrong word.\r\n */\r\nexport const WRAPPER_VALUE_FLAGS: Record<string, string[]> = {\r\n env: [\"-u\", \"--unset\", \"-C\", \"--chdir\", \"-S\", \"--split-string\"],\r\n sudo: [\"-u\", \"--user\", \"-g\", \"--group\", \"-p\", \"--prompt\", \"-C\", \"--close-from\", \"-h\", \"--host\", \"-D\", \"--chdir\", \"-R\", \"--chroot\"],\r\n doas: [\"-u\", \"-C\"],\r\n timeout: [\"-s\", \"--signal\", \"-k\", \"--kill-after\"],\r\n nice: [\"-n\", \"--adjustment\"],\r\n ionice: [\"-c\", \"--class\", \"-n\", \"--classdata\", \"-p\", \"--pid\"],\r\n stdbuf: [\"-i\", \"--input\", \"-o\", \"--output\", \"-e\", \"--error\"],\r\n xargs: [\"-a\", \"--arg-file\", \"-d\", \"--delimiter\", \"-E\", \"-I\", \"--replace\", \"-L\", \"-n\", \"--max-args\", \"-P\", \"--max-procs\", \"-s\", \"--max-chars\"],\r\n watch: [\"-n\", \"--interval\"],\r\n setsid: [],\r\n nohup: [],\r\n \"systemd-run\": [\"-u\", \"--unit\", \"-p\", \"--property\", \"--uid\", \"--gid\", \"--setenv\"],\r\n runuser: [\"-u\", \"--user\", \"-g\", \"--group\", \"-s\", \"--shell\"],\r\n su: [\"-s\", \"--shell\", \"-g\", \"--group\"],\r\n};\r\n\r\n/** Rejected when `allowSudo` is false. Default profiles allow these. */\r\nexport const PRIVILEGE_ESCALATION_BINARIES = [\r\n \"sudo\",\r\n \"su\",\r\n \"doas\",\r\n \"pkexec\",\r\n \"runuser\",\r\n \"chroot\",\r\n \"nsenter\",\r\n \"systemd-run\",\r\n];\r\n\r\n/**\r\n * Binaries that take a command *string* (`-c \"…\"`) and run it in a fresh shell.\r\n * The payload is pulled out and screened as its own command — otherwise\r\n * `bash -c \"rm -rf /\"` would only ever be judged as \"the binary is bash\".\r\n */\r\nexport const SHELL_PAYLOAD_BINARIES = [\r\n \"sh\",\r\n \"bash\",\r\n \"zsh\",\r\n \"ksh\",\r\n \"dash\",\r\n \"ash\",\r\n \"mksh\",\r\n \"csh\",\r\n \"tcsh\",\r\n \"fish\",\r\n \"su\",\r\n \"runuser\",\r\n \"chroot\",\r\n \"nsenter\",\r\n \"systemd-run\",\r\n];\r\n\r\n/**\r\n * Always rejected, on every profile, not configurable.\r\n *\r\n * Scope is deliberately narrow: **irreversible destruction of the machine, or cutting the\r\n * connection you are working through**. Everything short of that — installing packages,\r\n * restarting services, deleting an application's files, piping an install script into a\r\n * shell — is ordinary administration and is allowed by default. A profile that wants more\r\n * restraint opts in with `readonly`, `allowCommands` or `denyPatterns`.\r\n */\r\nexport const DANGEROUS_COMMAND_RULES: CommandRule[] = [\r\n {\r\n pattern: /^mkfs(\\.\\w+)?\\b/i,\r\n reason: \"formats a filesystem\",\r\n },\r\n {\r\n pattern: />\\s*\\/dev\\/(sd|nvme|hd|vd|xvd|mapper)/i,\r\n reason: \"redirects output onto a block device\",\r\n },\r\n {\r\n pattern: /^(shutdown|reboot|halt|poweroff)\\b/i,\r\n reason: \"powers off or reboots the host, ending this session\",\r\n },\r\n {\r\n pattern: /^init\\s+[06]\\b/i,\r\n reason: \"changes runlevel to halt or reboot\",\r\n },\r\n {\r\n pattern: /^systemctl\\s+(poweroff|reboot|halt|emergency|rescue)\\b/i,\r\n reason: \"powers off, reboots or degrades the host\",\r\n },\r\n {\r\n // Anchored on `/` as the whole search root: `find /tmp -delete` is ordinary\r\n // cleanup, `find / -delete` empties the machine.\r\n pattern: /^find\\s+\\/\\s+.*(-delete|-exec(dir)?\\s+(\\/\\S+\\/)?rm)/i,\r\n reason: \"deletes files found from the filesystem root\",\r\n },\r\n {\r\n pattern: /^crontab\\s+(-\\S+\\s+)*-r\\b/i,\r\n reason: \"removes every cron job for the user\",\r\n },\r\n {\r\n pattern: /^(iptables|ip6tables|nft)\\s+(-F|--flush|flush)\\b/i,\r\n reason: \"flushes firewall rules and can lock this session out\",\r\n },\r\n {\r\n pattern: /--no-preserve-root/i,\r\n reason: \"explicitly disables the root-deletion safeguard\",\r\n },\r\n];\r\n\r\n/** Danger rules keyed on the binary plus its operands. */\r\nexport const DANGEROUS_ARGUMENT_RULES: ArgumentRule[] = [\r\n {\r\n binaries: [\"rm\"],\r\n requiredFlagPattern: /^--?(?:[a-z]*[rRf][a-z]*|recursive|force)$/,\r\n argumentPattern: CRITICAL_PATH_PATTERN,\r\n reason: \"deletes the filesystem root, a system directory or an entire home directory\",\r\n },\r\n {\r\n binaries: [\"rm\", \"unlink\", \"shred\", \"srm\", \"wipe\"],\r\n argumentPattern: CRITICAL_FILE_PATTERN,\r\n reason: \"deletes a file the host cannot boot or authenticate without\",\r\n },\r\n {\r\n binaries: [\"shred\", \"srm\", \"wipe\"],\r\n argumentPattern: CRITICAL_PATH_PATTERN,\r\n reason: \"irreversibly destroys a system path\",\r\n },\r\n {\r\n binaries: [\"chmod\", \"chown\", \"chgrp\"],\r\n requiredFlagPattern: /^--?(R|recursive)$/,\r\n argumentPattern: CRITICAL_PATH_PATTERN,\r\n reason: \"recursively rewrites ownership or permissions on a system path\",\r\n },\r\n {\r\n binaries: [\"dd\"],\r\n argumentPattern: /^of=(\\/dev\\/(?!null|zero|stdout|stderr|tty)|\\/etc\\/|\\/boot\\/|\\/bin\\/|\\/sbin\\/|\\/usr\\/|\\/lib)/i,\r\n reason: \"writes raw bytes over a device or system file\",\r\n },\r\n {\r\n binaries: [\"mkfs\", \"fdisk\", \"parted\", \"sgdisk\", \"wipefs\"],\r\n argumentPattern: /^\\/dev\\//i,\r\n reason: \"repartitions or formats a block device\",\r\n },\r\n];\r\n\r\n/** Danger rules tested against the whole command, before it is split on `;`, `&&`, `|`. */\r\nexport const DANGEROUS_PIPELINE_RULES: CommandRule[] = [\r\n {\r\n pattern: /\\{\\s*:\\s*\\|\\s*:\\s*&\\s*\\}/,\r\n reason: \"fork bomb\",\r\n },\r\n];\r\n\r\n/**\r\n * Blocked when the profile is read-only: binaries whose whole purpose is to change the\r\n * host's files, packages, services or accounts.\r\n *\r\n * Interpreters, `awk`, database clients and archive listing are deliberately **absent**.\r\n * A read-only profile exists to make a host safe to *inspect*, and log analysis routinely\r\n * needs `python3 -c`, `awk`, `psql`. Blocking them made the mode unusable for its own\r\n * purpose while stopping nobody who wanted around it — a shell payload is still screened\r\n * (see SHELL_PAYLOAD_BINARIES), so `bash -c \"rm x\"` is caught by the `rm` rule.\r\n */\r\nexport const WRITE_COMMAND_BINARIES = [\r\n // filesystem mutation\r\n \"rm\", \"rmdir\", \"mv\", \"cp\", \"dd\", \"mkdir\", \"touch\", \"ln\", \"chmod\", \"chown\", \"chgrp\",\r\n \"truncate\", \"shred\", \"srm\", \"wipe\", \"install\", \"tee\", \"patch\", \"rename\", \"chattr\", \"setfacl\",\r\n // storage / mounts\r\n \"mkfs\", \"fdisk\", \"parted\", \"sgdisk\", \"wipefs\", \"mount\", \"umount\", \"swapon\", \"swapoff\",\r\n // accounts and scheduling\r\n \"useradd\", \"usermod\", \"userdel\", \"groupadd\", \"groupmod\", \"groupdel\",\r\n \"passwd\", \"chpasswd\", \"visudo\", \"crontab\", \"at\", \"batch\",\r\n // process and power control\r\n \"kill\", \"killall\", \"pkill\", \"reboot\", \"shutdown\", \"halt\", \"poweroff\", \"init\", \"service\",\r\n // package managers\r\n \"apt\", \"apt-get\", \"aptitude\", \"dpkg\", \"yum\", \"dnf\", \"rpm\", \"zypper\", \"pacman\", \"apk\",\r\n \"snap\", \"brew\", \"npm\", \"pnpm\", \"yarn\", \"npx\", \"pip\", \"pip3\", \"gem\", \"cargo\", \"composer\",\r\n \"bundle\", \"make\", \"cmake\",\r\n // editors — interactive and write-oriented\r\n \"vi\", \"vim\", \"nvim\", \"nano\", \"emacs\", \"ex\", \"ed\",\r\n // transfer to/from other hosts, and archive extraction\r\n \"scp\", \"rsync\", \"sftp\", \"ftp\", \"ssh\", \"ssh-keygen\", \"ssh-copy-id\", \"wget\",\r\n \"unzip\", \"gunzip\", \"bunzip2\", \"unxz\", \"cpio\",\r\n // firewall\r\n \"iptables\", \"ip6tables\", \"ufw\", \"firewall-cmd\", \"nft\",\r\n];\r\n\r\n/**\r\n * Blocked when the profile is read-only: tools that read *or* write depending on the\r\n * subcommand or flag, so only the mutating form is matched.\r\n */\r\nexport const WRITE_USAGE_RULES: CommandRule[] = [\r\n {\r\n pattern: /^sed\\b.*(?:(\\s|^)-[a-zA-Z]*i([a-zA-Z]|\\s|$|\\.)|--in-place)/,\r\n reason: \"sed edits files in place\",\r\n },\r\n {\r\n pattern: /^sort\\b.*(\\s|^)(-o|--output)\\b/i,\r\n reason: \"sort writes its result to a file\",\r\n },\r\n {\r\n pattern: /^find\\b.*(-delete|-exec|-execdir|-ok|-okdir|-fls|-fprint)/i,\r\n reason: \"find action deletes files or runs an arbitrary command\",\r\n },\r\n {\r\n pattern: /^git\\s+(push|reset|checkout|switch|restore|clean|rebase|merge|commit|add|rm|mv|stash|apply|revert|cherry-pick|pull|remote|tag|gc|prune|init|clone)\\b/i,\r\n reason: \"git subcommand mutates the working tree or the remote\",\r\n },\r\n {\r\n pattern: /^systemctl\\s+(start|stop|restart|reload|enable|disable|mask|unmask|kill|isolate|set-property|edit|daemon-reload|daemon-reexec)\\b/i,\r\n reason: \"systemctl subcommand changes service state\",\r\n },\r\n {\r\n pattern: /^docker\\s+(run|rm|rmi|stop|start|restart|kill|exec|create|build|push|pull|cp|commit|prune|load|import|tag|update|rename|volume|network|swarm)\\b/i,\r\n reason: \"docker subcommand mutates containers, images or volumes\",\r\n },\r\n {\r\n pattern: /^(kubectl|oc)\\s+(apply|delete|create|edit|scale|patch|replace|rollout|drain|cordon|uncordon|exec|cp|set|annotate|label|taint)\\b/i,\r\n reason: \"kubectl subcommand mutates cluster state\",\r\n },\r\n {\r\n pattern: /^tar\\b.*(--extract|--create|--delete|(\\s|^)-{1,2}[a-zA-Z]*[xc][a-zA-Z]*(\\s|$))/i,\r\n reason: \"tar extract/create writes files\",\r\n },\r\n {\r\n pattern: /^curl\\b.*(\\s|^)(-o|-O|--output|--remote-name|--upload-file|-T)\\b/i,\r\n reason: \"curl writes a file or uploads one\",\r\n },\r\n {\r\n pattern: /^(zip|gzip|bzip2|xz)\\b/i,\r\n reason: \"compression tool rewrites files on disk\",\r\n },\r\n];\r\n\r\n/**\r\n * Harmless redirection forms stripped before checking for output redirection —\r\n * `2>&1`, `>/dev/null`, `&>/dev/null` are diagnostics plumbing, not writes.\r\n */\r\nexport const BENIGN_REDIRECT_PATTERN = /(\\d?>>?\\s*&\\s*\\d|&>>?\\s*\\/dev\\/null|\\d?>>?\\s*\\/dev\\/null)/g;\r\n\r\n/** Any remaining `>` after the benign forms are stripped means a real file write. */\r\nexport const OUTPUT_REDIRECT_PATTERN = />/;\r\n\r\n/** Leading `2>`, `>>`, `<`, `2>&1` tokens — stripped so the binary scan finds the command. */\r\nexport const REDIRECTION_TOKEN_PATTERN = /^(?:\\d*>>?|<|&>>?)/;\r\n\r\n/**\r\n * A redirection operator with no target attached, so the *next* token is its filename\r\n * rather than a command. `2>&1` is excluded: it names its target inline.\r\n */\r\nexport const BARE_REDIRECTION_OPERATOR_PATTERN = /^(?:\\d*>>?|<|&>>?)$/;\r\n\r\n/**\r\n * Shell word splitting: redirection operators first so `tee</dev/null` yields\r\n * `tee`, `<`, `/dev/null` instead of one word whose basename reads as \"null\".\r\n */\r\nexport const SHELL_TOKEN_PATTERN = /\\d*>>?&?\\d*|&>>?|<&?\\d*|(?:[^\\s'\"\\\\<>&|]|\\\\.|'[^']*'|\"[^\"]*\")+/g;\r\n\r\n/** Shell grouping and keyword tokens that precede a command rather than being one. */\r\nexport const SHELL_KEYWORD_TOKENS = [\r\n \"if\", \"then\", \"else\", \"elif\", \"fi\",\r\n \"for\", \"while\", \"until\", \"do\", \"done\",\r\n \"case\", \"esac\", \"in\", \"select\",\r\n \"function\", \"{\", \"}\", \"(\", \")\", \"!\", \"[[\", \"]]\", \"[\", \"]\",\r\n];\r\n\r\n/**\r\n * Shell metacharacters forbidden in a `cwd` argument.\r\n *\r\n * `cwd` is interpolated into the generated `cd` prefix, so anything that can end a word\r\n * and start a new command is remote code execution that never passes through the command\r\n * guard. Directory names legitimately containing these are vanishingly rare; the trade is\r\n * obviously worth it.\r\n */\r\nexport const UNSAFE_PATH_CHARACTER_PATTERN = /[`$;|&<>(){}!*?\\[\\]\\\\\"'\\n\\r\\t]/;\r\n","import type { CommandRule } from \"./command-rules\";\r\n\r\n/**\r\n * Local paths the file tools may never read or write, whatever the profile allows.\r\n *\r\n * Deliberately narrow — two categories only:\r\n *\r\n * 1. **Credentials.** Uploading these exfiltrates the operator's keys to a remote host.\r\n * 2. **Files that define the guards themselves** — the MCP client configuration, which is\r\n * where this server's `SSH_*` variables live, plus autostart locations. Downloading\r\n * onto one of these replaces the policy that governs the next run, so the guard would\r\n * be self-defeating without it.\r\n *\r\n * Ordinary operational files are intentionally *not* here. `.env` uploads, `.bashrc`\r\n * backups and cron fetches are normal deployment work, and blocking them broke real tasks\r\n * to stop an attack that a `python3 -c` one-liner performs anyway.\r\n *\r\n * Matched against the **real** path (symlinks resolved) with `\\` normalized to `/`.\r\n */\r\nexport const PROTECTED_LOCAL_PATH_RULES: CommandRule[] = [\r\n {\r\n pattern: /(^|\\/)\\.ssh(\\/|$)/i,\r\n reason: \"SSH key directory\",\r\n },\r\n {\r\n pattern: /(^|\\/)authorized_keys$/i,\r\n reason: \"SSH authorized_keys\",\r\n },\r\n {\r\n pattern: /(^|\\/)id_(rsa|dsa|ecdsa|ed25519)/i,\r\n reason: \"private key file\",\r\n },\r\n {\r\n pattern: /\\.(pem|ppk|p12|pfx|key)$/i,\r\n reason: \"private key or certificate file\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.gnupg(\\/|$)/i,\r\n reason: \"GPG keyring\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.aws(\\/|$)/i,\r\n reason: \"AWS credentials\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.kube(\\/|$)/i,\r\n reason: \"kubeconfig\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.docker(\\/|$)/i,\r\n reason: \"docker credentials\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.npmrc$/i,\r\n reason: \"npm credentials\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.netrc$/i,\r\n reason: \"netrc credentials\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.claude(\\.json)?(\\/|$)/i,\r\n reason: \"MCP client configuration\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.mcp\\.json$/i,\r\n reason: \"MCP server configuration\",\r\n },\r\n {\r\n pattern: /(^|\\/)claude_desktop_config\\.json$/i,\r\n reason: \"MCP client configuration\",\r\n },\r\n {\r\n pattern: /(^|\\/)start ?menu\\/programs\\/startup(\\/|$)/i,\r\n reason: \"Windows startup folder\",\r\n },\r\n {\r\n pattern: /(^|\\/)\\.config\\/autostart(\\/|$)/i,\r\n reason: \"desktop autostart directory\",\r\n },\r\n];\r\n","/**\r\n * Environment variables that define the host, one MCP server entry per host.\r\n *\r\n * This is the only way to configure the server. Each variable is a plain value, so an\r\n * MCP client's `env` block reads like a connection form — no JSON inside JSON, no\r\n * escaped quotes, and a typo shows up on the line it is on.\r\n */\r\nexport const SINGLE_HOST_ENV = {\r\n /** Presence of this variable is what registers a host at all. */\r\n HOST: \"SSH_HOST\",\r\n PORT: \"SSH_PORT\",\r\n USER: \"SSH_USER\",\r\n /** Path to a private key file. */\r\n KEY: \"SSH_KEY\",\r\n PASSPHRASE: \"SSH_PASSPHRASE\",\r\n PASSWORD: \"SSH_PASSWORD\",\r\n /** Pinned host key, `SHA256:…`. */\r\n HOST_KEY: \"SSH_HOST_KEY\",\r\n /** Alias shown to the model; defaults to the hostname. */\r\n NAME: \"SSH_NAME\",\r\n DESCRIPTION: \"SSH_DESCRIPTION\",\r\n /** Directory new sessions start in. */\r\n CWD: \"SSH_CWD\",\r\n /**\r\n * Command whose stdin/stdout carry the SSH stream, like OpenSSH's `ProxyCommand`.\r\n * `%h` / `%p` / `%r` expand to the first hop's host, port and user.\r\n */\r\n PROXY_COMMAND: \"SSH_PROXY_COMMAND\",\r\n /** Presence of this variable routes the connection through a jump host. */\r\n JUMP_HOST: \"SSH_JUMP_HOST\",\r\n JUMP_PORT: \"SSH_JUMP_PORT\",\r\n /** Defaults to `SSH_USER`. */\r\n JUMP_USER: \"SSH_JUMP_USER\",\r\n /** Path to the jump host's private key file; defaults to `SSH_KEY`. */\r\n JUMP_KEY: \"SSH_JUMP_KEY\",\r\n JUMP_PASSPHRASE: \"SSH_JUMP_PASSPHRASE\",\r\n JUMP_PASSWORD: \"SSH_JUMP_PASSWORD\",\r\n /** Pinned jump host key, `SHA256:…`. */\r\n JUMP_HOST_KEY: \"SSH_JUMP_HOST_KEY\",\r\n READONLY: \"SSH_READONLY\",\r\n ALLOW_SUDO: \"SSH_ALLOW_SUDO\",\r\n /** Comma-separated binary names. */\r\n ALLOW_COMMANDS: \"SSH_ALLOW_COMMANDS\",\r\n /** Comma-separated regex sources. */\r\n DENY_PATTERNS: \"SSH_DENY_PATTERNS\",\r\n /** Comma-separated absolute path prefixes. */\r\n ALLOWED_PATHS: \"SSH_ALLOWED_PATHS\",\r\n EXEC_TIMEOUT_MS: \"SSH_EXEC_TIMEOUT_MS\",\r\n CONNECT_TIMEOUT_MS: \"SSH_CONNECT_TIMEOUT_MS\",\r\n MAX_OUTPUT_CHARACTERS: \"SSH_MAX_OUTPUT\",\r\n MAX_READ_FILE_BYTES: \"SSH_MAX_READ_BYTES\",\r\n} as const;\r\n\r\n/** Accepted spellings for a true boolean env value. */\r\nexport const TRUTHY_ENV_VALUES = [\"true\", \"1\", \"yes\", \"on\"];\r\n\r\n/** Accepted spellings for a false boolean env value. */\r\nexport const FALSY_ENV_VALUES = [\"false\", \"0\", \"no\", \"off\"];\r\n","import { LOG_LEVEL_ENV } from \"@/_defs\";\r\n\r\nexport type LogLevel = \"silent\" | \"debug\" | \"info\" | \"warn\" | \"error\";\r\n\r\nexport interface Logger {\r\n /** Returns a logger that appends `meta` to every record it writes. */\r\n child(meta: Record<string, unknown>): Logger;\r\n debug(message: string, meta?: Record<string, unknown>): void;\r\n info(message: string, meta?: Record<string, unknown>): void;\r\n warn(message: string, meta?: Record<string, unknown>): void;\r\n error(error: unknown, message: string, meta?: Record<string, unknown>): void;\r\n}\r\n\r\nconst LEVEL_WEIGHTS: Record<LogLevel, number> = {\r\n silent: 0,\r\n debug: 1,\r\n info: 2,\r\n warn: 3,\r\n error: 4,\r\n};\r\n\r\n/** Meta keys whose values never reach the log output. */\r\nconst SECRET_META_KEYS = [\"password\", \"passphrase\", \"privatekey\", \"privatekeypath\", \"secret\", \"token\"];\r\n\r\n/**\r\n * Set once a write to stderr failed; every later line is dropped. When the MCP client dies, nothing\r\n * reads stderr any more and the next line fails with EPIPE. Logging that failure fails again, and the\r\n * loop starves the event loop — the server stays behind at 100% CPU and never reaches its own\r\n * process.exit (measured 2026-10-13 in @akms/mcp-db under Claude Code's `cmd /c` launch).\r\n */\r\nlet stderrBroken = false;\r\nprocess.stderr.on(\"error\", () => {\r\n stderrBroken = true;\r\n});\r\n\r\nfunction resolveThreshold(): LogLevel {\r\n const configured = process.env[LOG_LEVEL_ENV];\r\n if (configured == null) {\r\n return \"info\";\r\n }\r\n\r\n // Own-property check: a plain `LEVEL_WEIGHTS[x] == null` test walks the prototype, so\r\n // SSH_MCP_LOG_LEVEL=constructor would pass validation and disable all filtering.\r\n const normalized = configured.toLowerCase() as LogLevel;\r\n if (Object.hasOwn(LEVEL_WEIGHTS, normalized) == false) {\r\n return \"info\";\r\n }\r\n\r\n return normalized;\r\n}\r\n\r\nfunction maskMeta(meta: Record<string, unknown>): Record<string, unknown> {\r\n const masked: Record<string, unknown> = {};\r\n for (const [key, value] of Object.entries(meta)) {\r\n if (SECRET_META_KEYS.includes(key.toLowerCase()) == true) {\r\n masked[key] = \"***\";\r\n continue;\r\n }\r\n\r\n masked[key] = value;\r\n }\r\n\r\n return masked;\r\n}\r\n\r\n/**\r\n * Writes to stderr only. stdout carries the MCP JSON-RPC stream over stdio transport,\r\n * so a single stray `console.log` corrupts the protocol and kills the session.\r\n */\r\nfunction write(level: LogLevel, message: string, meta: Record<string, unknown>): void {\r\n if (stderrBroken == true) {\r\n return;\r\n }\r\n\r\n const threshold = resolveThreshold();\r\n if (threshold === \"silent\") {\r\n return;\r\n }\r\n\r\n if (LEVEL_WEIGHTS[level] < LEVEL_WEIGHTS[threshold]) {\r\n return;\r\n }\r\n\r\n const timestamp = new Date().toISOString();\r\n const masked = maskMeta(meta);\r\n\r\n let line = `${timestamp} [${level}] ${message}`;\r\n if (Object.keys(masked).length > 0) {\r\n line += ` ${JSON.stringify(masked)}`;\r\n }\r\n\r\n // On POSIX a pipe write is synchronous and can throw EPIPE right here instead of emitting 'error'\r\n try {\r\n process.stderr.write(`${line}\\n`);\r\n }\r\n catch {\r\n stderrBroken = true;\r\n }\r\n}\r\n\r\nfunction createLogger(boundMeta: Record<string, unknown>): Logger {\r\n return {\r\n child(meta: Record<string, unknown>): Logger {\r\n return createLogger({ ...boundMeta, ...meta });\r\n },\r\n debug(message: string, meta?: Record<string, unknown>): void {\r\n write(\"debug\", message, { ...boundMeta, ...meta });\r\n },\r\n info(message: string, meta?: Record<string, unknown>): void {\r\n write(\"info\", message, { ...boundMeta, ...meta });\r\n },\r\n warn(message: string, meta?: Record<string, unknown>): void {\r\n write(\"warn\", message, { ...boundMeta, ...meta });\r\n },\r\n error(error: unknown, message: string, meta?: Record<string, unknown>): void {\r\n const errorMeta: Record<string, unknown> = { ...boundMeta, ...meta };\r\n if (error instanceof Error) {\r\n errorMeta.error_name = error.name;\r\n errorMeta.error_message = error.message;\r\n errorMeta.stack = error.stack;\r\n }\r\n else if (error != null) {\r\n errorMeta.error_message = String(error);\r\n }\r\n\r\n write(\"error\", message, errorMeta);\r\n },\r\n };\r\n}\r\n\r\nexport const $logger = createLogger({});\r\n","/** Output cut to `maxCharacters`, with a note appended when anything was dropped. */\r\nexport interface TruncatedText {\r\n text: string;\r\n truncated: boolean;\r\n}\r\n\r\n/**\r\n * Cuts `text` to `maxCharacters`, appending a note saying how much was dropped.\r\n *\r\n * @returns The possibly-cut text and whether anything was removed.\r\n */\r\nexport function truncateOutput(text: string, maxCharacters: number): TruncatedText {\r\n if (text.length <= maxCharacters) {\r\n return { text: text, truncated: false };\r\n }\r\n\r\n // Never cut between the halves of a surrogate pair: the orphaned half renders as a\r\n // replacement character, so an emoji at the boundary would come back corrupted.\r\n let cutAt = maxCharacters;\r\n const lastKeptCode = text.charCodeAt(cutAt - 1);\r\n if (lastKeptCode >= 0xd800 && lastKeptCode <= 0xdbff) {\r\n cutAt -= 1;\r\n }\r\n\r\n const droppedCharacters = text.length - cutAt;\r\n const kept = text.slice(0, cutAt);\r\n return {\r\n text: `${kept}\\n... [truncated ${droppedCharacters} characters of ${text.length}]`,\r\n truncated: true,\r\n };\r\n}\r\n\r\n/**\r\n * Wraps a value in single quotes for POSIX shells, escaping embedded quotes as `'\\''`.\r\n * Used for interpolating paths into the `cd` prefix — never for user commands, which\r\n * are passed through verbatim so the remote shell parses them as written.\r\n */\r\nexport function quoteShellArgument(value: string): string {\r\n const escaped = value.replace(/'/g, `'\\\\''`);\r\n return `'${escaped}'`;\r\n}\r\n\r\n/** Renders a byte count as `B` / `KB` / `MB` for display in tool output. */\r\nexport function formatBytes(bytes: number): string {\r\n if (bytes < 1024) {\r\n return `${bytes} B`;\r\n }\r\n\r\n if (bytes < 1024 * 1024) {\r\n const kilobytes = bytes / 1024;\r\n return `${kilobytes.toFixed(1)} KB`;\r\n }\r\n\r\n const megabytes = bytes / (1024 * 1024);\r\n return `${megabytes.toFixed(1)} MB`;\r\n}\r\n","import fs from \"node:fs\";\r\nimport os from \"node:os\";\r\nimport path from \"node:path\";\r\n\r\n/**\r\n * Absolute path of an executable on `PATH`, or null. On Windows only `PATHEXT` suffixes are\r\n * tried — npm drops an extensionless POSIX shell script next to every `.cmd` shim, and that\r\n * file exists but cannot be launched.\r\n */\r\nexport function findExecutable(name: string): string | null {\r\n const searchPath = process.env.PATH ?? \"\";\r\n const directories = searchPath.split(path.delimiter).filter((directory) => directory !== \"\");\r\n\r\n let suffixes = [\"\"];\r\n if (process.platform === \"win32\") {\r\n const pathExt = process.env.PATHEXT ?? \".COM;.EXE;.BAT;.CMD\";\r\n suffixes = pathExt.split(\";\").filter((suffix) => suffix !== \"\");\r\n }\r\n\r\n for (const directory of directories) {\r\n for (const suffix of suffixes) {\r\n const candidate = path.join(directory, `${name}${suffix}`);\r\n if (fs.existsSync(candidate) == true && fs.statSync(candidate).isFile() == true) {\r\n return candidate;\r\n }\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\n/** Expands a leading `~` to the local home directory and returns an absolute path. */\r\nexport function expandHomePath(inputPath: string): string {\r\n let expanded = inputPath;\r\n if (inputPath === \"~\") {\r\n expanded = os.homedir();\r\n }\r\n else if (inputPath.startsWith(\"~/\") == true || inputPath.startsWith(\"~\\\\\") == true) {\r\n expanded = path.join(os.homedir(), inputPath.slice(2));\r\n }\r\n\r\n return path.resolve(expanded);\r\n}\r\n\r\n/** True for POSIX absolute remote paths — the only form the path guard can reason about. */\r\nexport function isAbsoluteRemotePath(remotePath: string): boolean {\r\n return remotePath.startsWith(\"/\");\r\n}\r\n\r\n/** Collapses `.`, `..` and duplicate slashes using POSIX rules, whatever the local OS is. */\r\nexport function normalizeRemotePath(remotePath: string): string {\r\n return path.posix.normalize(remotePath);\r\n}\r\n\r\n/** True when `candidate` is `allowedPrefix` itself or sits underneath it. */\r\nexport function isRemotePathWithin(candidate: string, allowedPrefix: string): boolean {\r\n const normalizedCandidate = normalizeRemotePath(candidate);\r\n const normalizedPrefix = normalizeRemotePath(allowedPrefix).replace(/\\/+$/, \"\");\r\n\r\n if (normalizedPrefix === \"\" || normalizedPrefix === \"/\") {\r\n return true;\r\n }\r\n\r\n if (normalizedCandidate === normalizedPrefix) {\r\n return true;\r\n }\r\n\r\n return normalizedCandidate.startsWith(`${normalizedPrefix}/`);\r\n}\r\n","import fs from \"node:fs\";\r\n\r\nimport {\r\n DEFAULT_CONNECT_TIMEOUT_MS,\r\n DEFAULT_EXEC_TIMEOUT_MS,\r\n DEFAULT_MAX_OUTPUT_CHARACTERS,\r\n DEFAULT_MAX_READ_FILE_BYTES,\r\n DEFAULT_SSH_PORT,\r\n FALSY_ENV_VALUES,\r\n SINGLE_HOST_ENV,\r\n TRUTHY_ENV_VALUES,\r\n} from \"@/_defs\";\r\nimport { $logger, expandHomePath } from \"@/_libs\";\r\nimport type { SshHostProfile, SshHostSummary, SshPolicy } from \"@/_types\";\r\n\r\n/** Trimmed value, or undefined when the variable is unset or blank. */\r\nfunction readEnv(env: NodeJS.ProcessEnv, name: string): string | undefined {\r\n const value = env[name];\r\n if (value == null) {\r\n return undefined;\r\n }\r\n\r\n const trimmed = value.trim();\r\n if (trimmed === \"\") {\r\n return undefined;\r\n }\r\n\r\n return trimmed;\r\n}\r\n\r\n/**\r\n * @throws {Error} If the value is set but is neither a truthy nor a falsy spelling —\r\n * `SSH_READONLY=ture` must fail loudly, not resolve to \"not read-only\".\r\n */\r\nfunction readBooleanEnv(env: NodeJS.ProcessEnv, name: string): boolean | undefined {\r\n const value = readEnv(env, name);\r\n if (value == null) {\r\n return undefined;\r\n }\r\n\r\n const normalized = value.toLowerCase();\r\n if (TRUTHY_ENV_VALUES.includes(normalized) == true) {\r\n return true;\r\n }\r\n\r\n if (FALSY_ENV_VALUES.includes(normalized) == false) {\r\n throw new Error(`${name} must be one of ${[...TRUTHY_ENV_VALUES, ...FALSY_ENV_VALUES].join(\", \")} (got \"${value}\")`);\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * @throws {Error} If the value is set but is not a positive integer.\r\n */\r\nfunction readIntegerEnv(env: NodeJS.ProcessEnv, name: string): number | undefined {\r\n const value = readEnv(env, name);\r\n if (value == null) {\r\n return undefined;\r\n }\r\n\r\n const parsed = Number(value);\r\n if (Number.isInteger(parsed) == false || parsed <= 0) {\r\n throw new Error(`${name} must be a positive integer (got \"${value}\")`);\r\n }\r\n\r\n return parsed;\r\n}\r\n\r\n/** Comma-separated list, blanks dropped. */\r\nfunction readListEnv(env: NodeJS.ProcessEnv, name: string): string[] | undefined {\r\n const value = readEnv(env, name);\r\n if (value == null) {\r\n return undefined;\r\n }\r\n\r\n const items = value.split(\",\").map((item) => item.trim()).filter((item) => item !== \"\");\r\n if (items.length === 0) {\r\n return undefined;\r\n }\r\n\r\n return items;\r\n}\r\n\r\n/**\r\n * Compiles `SSH_DENY_PATTERNS` at startup.\r\n *\r\n * Lazily compiling them at first use meant a typo'd regex logged one stderr line and then\r\n * simply wasn't in force — the operator's own deny rule, silently absent, on a server that\r\n * reported \"host profile loaded\". Every other malformed value aborts startup; so does this.\r\n *\r\n * @throws {Error} If a pattern is not a valid regular expression.\r\n */\r\nfunction compileDenyPatterns(sources: string[]): RegExp[] {\r\n return sources.map((source) => {\r\n try {\r\n return new RegExp(source, \"i\");\r\n }\r\n catch (ex) {\r\n const _logger = $logger.child({ context: \"compileDenyPatterns\" });\r\n _logger.error(ex, \"denyPatterns entry is not a valid regular expression\");\r\n\r\n let detail = String(ex);\r\n if (ex instanceof Error) {\r\n detail = ex.message;\r\n }\r\n\r\n throw new Error(`${SINGLE_HOST_ENV.DENY_PATTERNS} has an invalid entry /${source}/: ${detail}`);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Builds the host profile from the `SSH_*` variables, or null when `SSH_HOST` is unset.\r\n *\r\n * One MCP server entry fronts one host, so every setting is a plain variable — no JSON\r\n * inside JSON, no escaped quotes, and a typo shows up on the line it is on. A malformed\r\n * value aborts startup rather than being ignored, because silently dropping `SSH_READONLY`\r\n * would leave a host the operator believed was read-only fully writable.\r\n *\r\n * Flow:\r\n * 1) Guard: no `SSH_HOST` means nothing is registered — the server still starts and says so\r\n * 2) Resolve the policy over the built-in permissive stance; each variable is opt-in restriction\r\n * 3) Assemble the connection fields\r\n * 4) Expand the private key path and check it exists\r\n * 5) Route: proxy command and / or jump host\r\n *\r\n * @param env - Where the `SSH_*` variables are read from. Defaults to the process\r\n * environment; the `config` command passes the entry it is about to write.\r\n * @throws {Error} If `SSH_HOST` is set without `SSH_USER`, a `SSH_JUMP_*` variable is set\r\n * without `SSH_JUMP_HOST`, or a typed value is malformed.\r\n */\r\nexport function loadHostProfile(env: NodeJS.ProcessEnv = process.env): SshHostProfile | null {\r\n // (1) Guard — a missing host is not an error: the server reports \"not configured\"\r\n // through the tools, which is a far clearer signal to the model than a dead server.\r\n const host = readEnv(env, SINGLE_HOST_ENV.HOST);\r\n if (host == null) {\r\n const _startupLogger = $logger.child({ context: \"loadHostProfile\" });\r\n _startupLogger.warn(\"no host is registered; set the connection variables and restart\", {\r\n env_vars: [SINGLE_HOST_ENV.HOST, SINGLE_HOST_ENV.USER],\r\n });\r\n return null;\r\n }\r\n\r\n const username = readEnv(env, SINGLE_HOST_ENV.USER);\r\n if (username == null) {\r\n throw new Error(`${SINGLE_HOST_ENV.HOST} is set, so ${SINGLE_HOST_ENV.USER} is required`);\r\n }\r\n\r\n const name = readEnv(env, SINGLE_HOST_ENV.NAME) ?? host;\r\n const _logger = $logger.child({ context: \"loadHostProfile\", profile: name });\r\n\r\n // (2) Policy — the built-in stance is permissive: an unconfigured host allows\r\n // everything except the catastrophe rules, because a guard nobody can work through\r\n // gets turned off wholesale rather than tuned.\r\n const allowCommands = readListEnv(env, SINGLE_HOST_ENV.ALLOW_COMMANDS) ?? [];\r\n\r\n // `ssh_connect` probes with `pwd` to settle the session's starting directory; without\r\n // this an allow-list profile could never open a session at all.\r\n if (allowCommands.length > 0 && allowCommands.includes(\"pwd\") == false) {\r\n allowCommands.push(\"pwd\");\r\n }\r\n\r\n const policy: SshPolicy = {\r\n readonly: readBooleanEnv(env, SINGLE_HOST_ENV.READONLY) ?? false,\r\n allowSudo: readBooleanEnv(env, SINGLE_HOST_ENV.ALLOW_SUDO) ?? true,\r\n execTimeoutMs: readIntegerEnv(env, SINGLE_HOST_ENV.EXEC_TIMEOUT_MS) ?? DEFAULT_EXEC_TIMEOUT_MS,\r\n connectTimeoutMs: readIntegerEnv(env, SINGLE_HOST_ENV.CONNECT_TIMEOUT_MS) ?? DEFAULT_CONNECT_TIMEOUT_MS,\r\n maxOutputCharacters: readIntegerEnv(env, SINGLE_HOST_ENV.MAX_OUTPUT_CHARACTERS) ?? DEFAULT_MAX_OUTPUT_CHARACTERS,\r\n maxReadFileBytes: readIntegerEnv(env, SINGLE_HOST_ENV.MAX_READ_FILE_BYTES) ?? DEFAULT_MAX_READ_FILE_BYTES,\r\n allowCommands: allowCommands,\r\n denyPatterns: compileDenyPatterns(readListEnv(env, SINGLE_HOST_ENV.DENY_PATTERNS) ?? []),\r\n allowedPaths: readListEnv(env, SINGLE_HOST_ENV.ALLOWED_PATHS) ?? [],\r\n };\r\n\r\n // (3) Connection\r\n const profile: SshHostProfile = {\r\n name: name,\r\n host: host,\r\n port: readIntegerEnv(env, SINGLE_HOST_ENV.PORT) ?? DEFAULT_SSH_PORT,\r\n username: username,\r\n description: readEnv(env, SINGLE_HOST_ENV.DESCRIPTION),\r\n passphrase: readEnv(env, SINGLE_HOST_ENV.PASSPHRASE),\r\n password: readEnv(env, SINGLE_HOST_ENV.PASSWORD),\r\n hostKeyFingerprint: readEnv(env, SINGLE_HOST_ENV.HOST_KEY),\r\n defaultCwd: readEnv(env, SINGLE_HOST_ENV.CWD),\r\n policy: policy,\r\n };\r\n\r\n // (4) Key path — checked at load, not at first use: a typo would otherwise surface as\r\n // a connection failure minutes later, with the path withheld from the message.\r\n const keyPath = readEnv(env, SINGLE_HOST_ENV.KEY);\r\n if (keyPath != null) {\r\n const expandedKeyPath = expandHomePath(keyPath);\r\n profile.privateKeyPath = expandedKeyPath;\r\n\r\n if (fs.existsSync(expandedKeyPath) == false) {\r\n _logger.warn(`${SINGLE_HOST_ENV.KEY} does not exist; connections will fail`, {\r\n private_key_path: expandedKeyPath,\r\n });\r\n }\r\n }\r\n\r\n // (5) Route — the proxy command carries the first hop (the jump host when both are\r\n // set), as an OpenSSH `ProxyCommand` on the jump host's own config entry would.\r\n profile.proxyCommand = readEnv(env, SINGLE_HOST_ENV.PROXY_COMMAND);\r\n\r\n const jumpHost = readEnv(env, SINGLE_HOST_ENV.JUMP_HOST);\r\n if (jumpHost == null) {\r\n // A jump setting without a jump host is a half-finished entry; ignoring it would\r\n // quietly connect straight to the target the operator meant to put behind a hop.\r\n const strayJumpVariables = [\r\n SINGLE_HOST_ENV.JUMP_PORT,\r\n SINGLE_HOST_ENV.JUMP_USER,\r\n SINGLE_HOST_ENV.JUMP_KEY,\r\n SINGLE_HOST_ENV.JUMP_PASSPHRASE,\r\n SINGLE_HOST_ENV.JUMP_PASSWORD,\r\n SINGLE_HOST_ENV.JUMP_HOST_KEY,\r\n ].filter((variable) => readEnv(env, variable) != null);\r\n\r\n if (strayJumpVariables.length > 0) {\r\n throw new Error(`${strayJumpVariables.join(\", \")} is set, so ${SINGLE_HOST_ENV.JUMP_HOST} is required`);\r\n }\r\n }\r\n else {\r\n profile.jump = {\r\n host: jumpHost,\r\n port: readIntegerEnv(env, SINGLE_HOST_ENV.JUMP_PORT) ?? DEFAULT_SSH_PORT,\r\n username: readEnv(env, SINGLE_HOST_ENV.JUMP_USER) ?? username,\r\n password: readEnv(env, SINGLE_HOST_ENV.JUMP_PASSWORD),\r\n hostKeyFingerprint: readEnv(env, SINGLE_HOST_ENV.JUMP_HOST_KEY),\r\n };\r\n\r\n // The target's key (and its passphrase) is the default, as `ssh -J` authenticates\r\n // both hops with the same identity unless told otherwise.\r\n const jumpKeyPath = readEnv(env, SINGLE_HOST_ENV.JUMP_KEY);\r\n if (jumpKeyPath != null) {\r\n profile.jump.privateKeyPath = expandHomePath(jumpKeyPath);\r\n profile.jump.passphrase = readEnv(env, SINGLE_HOST_ENV.JUMP_PASSPHRASE);\r\n\r\n if (fs.existsSync(profile.jump.privateKeyPath) == false) {\r\n _logger.warn(`${SINGLE_HOST_ENV.JUMP_KEY} does not exist; connections will fail`, {\r\n private_key_path: profile.jump.privateKeyPath,\r\n });\r\n }\r\n }\r\n else {\r\n profile.jump.privateKeyPath = profile.privateKeyPath;\r\n profile.jump.passphrase = readEnv(env, SINGLE_HOST_ENV.JUMP_PASSPHRASE) ?? profile.passphrase;\r\n }\r\n }\r\n\r\n _logger.info(\"host profile loaded\", {\r\n host: host,\r\n port: profile.port,\r\n username: username,\r\n readonly: policy.readonly,\r\n jump_host: profile.jump?.host,\r\n via_proxy_command: profile.proxyCommand != null,\r\n });\r\n\r\n return profile;\r\n}\r\n\r\n/** Strips every secret so the profile can be described back to the model. */\r\nexport function toHostSummary(profile: SshHostProfile): SshHostSummary {\r\n let authMethod: SshHostSummary[\"authMethod\"] = \"agent\";\r\n if (profile.privateKeyPath != null) {\r\n authMethod = \"privateKey\";\r\n }\r\n else if (profile.password != null) {\r\n authMethod = \"password\";\r\n }\r\n\r\n const summary: SshHostSummary = {\r\n name: profile.name,\r\n host: profile.host,\r\n port: profile.port,\r\n username: profile.username,\r\n description: profile.description,\r\n authMethod: authMethod,\r\n viaProxyCommand: profile.proxyCommand != null,\r\n readonly: profile.policy.readonly,\r\n allowSudo: profile.policy.allowSudo,\r\n allowedPaths: profile.policy.allowedPaths,\r\n allowCommands: profile.policy.allowCommands,\r\n };\r\n\r\n if (profile.jump != null) {\r\n summary.jump = {\r\n host: profile.jump.host,\r\n port: profile.jump.port,\r\n username: profile.jump.username,\r\n };\r\n }\r\n\r\n return summary;\r\n}\r\n","import {\r\n BARE_REDIRECTION_OPERATOR_PATTERN,\r\n BENIGN_REDIRECT_PATTERN,\r\n COMMAND_WRAPPER_BINARIES,\r\n DANGEROUS_ARGUMENT_RULES,\r\n DANGEROUS_COMMAND_RULES,\r\n DANGEROUS_PIPELINE_RULES,\r\n NUMERIC_OPERAND_PATTERN,\r\n OUTPUT_REDIRECT_PATTERN,\r\n PRIVILEGE_ESCALATION_BINARIES,\r\n REDIRECTION_TOKEN_PATTERN,\r\n SHELL_KEYWORD_TOKENS,\r\n SHELL_PAYLOAD_BINARIES,\r\n SHELL_TOKEN_PATTERN,\r\n WRAPPER_VALUE_FLAGS,\r\n WRITE_COMMAND_BINARIES,\r\n WRITE_USAGE_RULES,\r\n} from \"@/_defs\";\r\nimport type { GuardVerdict, SshPolicy } from \"@/_types\";\r\n\r\n/** Membership lists as sets — these are hit once per token, per segment, per command. */\r\nconst WRAPPER_BINARY_SET = new Set(COMMAND_WRAPPER_BINARIES);\r\nconst PRIVILEGE_BINARY_SET = new Set(PRIVILEGE_ESCALATION_BINARIES);\r\nconst SHELL_PAYLOAD_BINARY_SET = new Set(SHELL_PAYLOAD_BINARIES);\r\nconst WRITE_BINARY_SET = new Set(WRITE_COMMAND_BINARIES);\r\nconst SHELL_KEYWORD_SET = new Set(SHELL_KEYWORD_TOKENS);\r\n\r\n/** `bash -c \"sh -c '…'\"` nests; three levels is far past anything legitimate. */\r\nconst MAX_INSPECTION_DEPTH = 3;\r\n\r\nconst ASSIGNMENT_TOKEN_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=/;\r\n\r\n/** `-c` alone, or bundled into a short-flag cluster like `-lc` / `-exc`. */\r\nconst SHELL_COMMAND_FLAG_PATTERN = /^-[a-z]*c$/i;\r\n\r\n/**\r\n * Rewrites shell constructs that change how the text tokenizes but not what it runs,\r\n * so the rules below see the same command the remote shell will.\r\n *\r\n * - `\\` + newline is a line continuation. POSIX removes it entirely — folding it to a\r\n * space instead would split `r\\⏎m` into two words and hide `rm` from every rule, so it\r\n * is removed, and the *canonical* text is what gets executed (see `canonicalizeCommand`).\r\n * - `${IFS}` / `$IFS` expand to whitespace, the standard way to write a command with no\r\n * literal spaces in it.\r\n */\r\nexport function normalizeCommandText(command: string): string {\r\n return command\r\n .replace(/\\\\\\r?\\n/g, \"\")\r\n .replace(/\\$\\{IFS\\}/g, \" \")\r\n .replace(/\\$IFS/g, \" \");\r\n}\r\n\r\n/**\r\n * The exact text that must be sent to the remote shell.\r\n *\r\n * Validating one string and executing another is a bypass by construction: the guard saw\r\n * `r m -rf /` while the host ran `rm -rf /`. Callers run this, then send its result.\r\n */\r\nexport function canonicalizeCommand(command: string): string {\r\n return normalizeCommandText(command);\r\n}\r\n\r\n/**\r\n * Splits a command line on `;`, `&&`, `||`, `|`, `&` and newlines, leaving quoted text intact.\r\n * `2>&1` keeps its `&` — a `&` directly after `>` is redirection, not a background operator.\r\n */\r\nexport function splitCommandSegments(command: string): string[] {\r\n const segments: string[] = [];\r\n let current = \"\";\r\n let openQuote: string | null = null;\r\n\r\n for (let index = 0; index < command.length; index++) {\r\n const character = command[index];\r\n const nextCharacter = command[index + 1];\r\n\r\n if (openQuote != null) {\r\n current += character;\r\n // Single quotes take no escapes in POSIX; double quotes do.\r\n if (character === \"\\\\\" && openQuote === `\"` && nextCharacter != null) {\r\n current += nextCharacter;\r\n index++;\r\n continue;\r\n }\r\n if (character === openQuote) {\r\n openQuote = null;\r\n }\r\n continue;\r\n }\r\n\r\n if (character === \"'\" || character === `\"`) {\r\n openQuote = character;\r\n current += character;\r\n continue;\r\n }\r\n\r\n if (character === \"\\\\\") {\r\n current += character;\r\n if (nextCharacter != null) {\r\n current += nextCharacter;\r\n index++;\r\n }\r\n continue;\r\n }\r\n\r\n if (character === \"&\" && current.trimEnd().endsWith(\">\") == true) {\r\n current += character;\r\n continue;\r\n }\r\n\r\n const isDoubleOperator = (character === \"&\" && nextCharacter === \"&\") || (character === \"|\" && nextCharacter === \"|\");\r\n if (isDoubleOperator == true) {\r\n segments.push(current);\r\n current = \"\";\r\n index++;\r\n continue;\r\n }\r\n\r\n if (character === \";\" || character === \"\\n\" || character === \"|\" || character === \"&\") {\r\n segments.push(current);\r\n current = \"\";\r\n continue;\r\n }\r\n\r\n current += character;\r\n }\r\n\r\n segments.push(current);\r\n\r\n return segments\r\n .map((segment) => segment.trim())\r\n .filter((segment) => segment !== \"\");\r\n}\r\n\r\n/**\r\n * Drops quoted spans so `echo \"a > b\"` isn't read as a redirection.\r\n *\r\n * Quote state is checked *before* the backslash branch: POSIX single quotes take no\r\n * escapes, so treating `'x\\'` as an escaped quote left the span open and swallowed the\r\n * rest of the line — including a real `> /etc/…` redirection.\r\n */\r\nexport function stripQuotedText(segment: string): string {\r\n let result = \"\";\r\n let openQuote: string | null = null;\r\n\r\n for (let index = 0; index < segment.length; index++) {\r\n const character = segment[index];\r\n\r\n if (openQuote === \"'\") {\r\n if (character === \"'\") {\r\n openQuote = null;\r\n }\r\n continue;\r\n }\r\n\r\n if (openQuote === `\"`) {\r\n if (character === \"\\\\\") {\r\n index++;\r\n continue;\r\n }\r\n if (character === `\"`) {\r\n openQuote = null;\r\n }\r\n continue;\r\n }\r\n\r\n if (character === \"\\\\\") {\r\n index++;\r\n continue;\r\n }\r\n\r\n if (character === \"'\" || character === `\"`) {\r\n openQuote = character;\r\n continue;\r\n }\r\n\r\n result += character;\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/** Splits a segment into shell words, keeping quoted spans as one word. */\r\nfunction tokenizeSegment(segment: string): string[] {\r\n const tokens = segment.match(SHELL_TOKEN_PATTERN);\r\n if (tokens == null) {\r\n return [];\r\n }\r\n\r\n return tokens;\r\n}\r\n\r\n/** Removes quoting so an operand can be compared against a path pattern. */\r\nfunction unquoteToken(token: string): string {\r\n if (token.includes(\"'\") == false && token.includes(`\"`) == false && token.includes(\"\\\\\") == false) {\r\n return token;\r\n }\r\n\r\n return token\r\n // ANSI-C quoting: `$'rm'` is the word `rm`, and dropping only the quotes would\r\n // leave `$rm` — a binary name no rule matches.\r\n .replace(/\\$'([^']*)'/g, \"$1\")\r\n .replace(/'([^']*)'/g, \"$1\")\r\n .replace(/\"([^\"]*)\"/g, \"$1\")\r\n .replace(/\\\\(.)/g, \"$1\");\r\n}\r\n\r\n/**\r\n * Collapses `.`, `..` and duplicate slashes in an operand before it is matched.\r\n *\r\n * `rm -rf //*`, `/./*` and `/bin/../bin` all name the same targets as the plain forms,\r\n * and a literal-spelling comparison would miss every one of them. Trailing glob characters\r\n * are preserved because the patterns match on them.\r\n */\r\nfunction normalizeOperand(operand: string): string {\r\n if (operand.startsWith(\"/\") == false) {\r\n return operand;\r\n }\r\n\r\n let globSuffix = \"\";\r\n if (operand.endsWith(\"*\") == true) {\r\n globSuffix = \"*\";\r\n }\r\n\r\n const withoutGlob = operand.slice(0, operand.length - globSuffix.length);\r\n const collapsed = withoutGlob.replace(/\\/{2,}/g, \"/\").replace(/\\/\\.(?=\\/|$)/g, \"/\").replace(/\\/{2,}/g, \"/\");\r\n\r\n if (collapsed === \"\") {\r\n return `/${globSuffix}`;\r\n }\r\n\r\n return `${collapsed}${globSuffix}`;\r\n}\r\n\r\n/**\r\n * Commands nested inside the text: `$(…)`, backticks, and process substitution\r\n * `<(…)` / `>(…)`. Each runs as a command in its own right.\r\n */\r\nexport function extractSubstitutions(command: string): string[] {\r\n const found: string[] = [];\r\n\r\n const nestedPattern = /(?:\\$|<|>)\\(([^()]*(?:\\([^()]*\\)[^()]*)*)\\)/g;\r\n let nestedMatch = nestedPattern.exec(command);\r\n while (nestedMatch != null) {\r\n found.push(nestedMatch[1]);\r\n nestedMatch = nestedPattern.exec(command);\r\n }\r\n\r\n const backtickPattern = /`([^`]*)`/g;\r\n let backtickMatch = backtickPattern.exec(command);\r\n while (backtickMatch != null) {\r\n found.push(backtickMatch[1]);\r\n backtickMatch = backtickPattern.exec(command);\r\n }\r\n\r\n return found.filter((substitution) => substitution.trim() !== \"\");\r\n}\r\n\r\nexport interface SegmentAnalysis {\r\n /** Wrapper binaries preceding the real command, in order — e.g. `[\"sudo\", \"env\"]`. */\r\n wrappers: string[];\r\n /** Basename of the binary that actually runs; empty when the segment has no command. */\r\n binary: string;\r\n /** Flags that follow the binary, unquoted. */\r\n flags: string[];\r\n /** Non-flag operands that follow the binary, unquoted. */\r\n operands: string[];\r\n /**\r\n * The segment reduced to `<basename> <args…>`, with assignment, redirection, wrapper\r\n * and shell-keyword prefixes removed. Rules are `^`-anchored, so they must see the\r\n * command at position zero — otherwise `nohup rm -rf /` and `/sbin/reboot` slide past.\r\n */\r\n normalizedText: string;\r\n /** Tokens of the segment, computed once and reused. */\r\n tokens: string[];\r\n}\r\n\r\n/**\r\n * Walks past `FOO=bar` assignments, redirection tokens, shell keywords, wrapper binaries,\r\n * their flags and their numeric operands (`timeout 30`) to the command that executes.\r\n */\r\nexport function analyzeSegment(segment: string): SegmentAnalysis {\r\n const tokens = tokenizeSegment(segment);\r\n const wrappers: string[] = [];\r\n const flags: string[] = [];\r\n const operands: string[] = [];\r\n\r\n let binary = \"\";\r\n let binaryIndex = -1;\r\n let expectRedirectionTarget = false;\r\n let expectFlagValue = false;\r\n\r\n for (let index = 0; index < tokens.length; index++) {\r\n const token = tokens[index];\r\n\r\n // The filename after a bare `>` / `<` is a redirection target, not a command.\r\n if (expectRedirectionTarget == true) {\r\n expectRedirectionTarget = false;\r\n continue;\r\n }\r\n\r\n // The value of a wrapper flag such as `env -u FOO` / `sudo -u deploy`.\r\n if (expectFlagValue == true) {\r\n expectFlagValue = false;\r\n continue;\r\n }\r\n\r\n if (ASSIGNMENT_TOKEN_PATTERN.test(token) == true) {\r\n continue;\r\n }\r\n\r\n if (REDIRECTION_TOKEN_PATTERN.test(token) == true) {\r\n if (BARE_REDIRECTION_OPERATOR_PATTERN.test(token) == true) {\r\n expectRedirectionTarget = true;\r\n }\r\n continue;\r\n }\r\n\r\n if (token.startsWith(\"-\") == true) {\r\n const currentWrapper = wrappers[wrappers.length - 1];\r\n if (currentWrapper != null) {\r\n const valueFlags = WRAPPER_VALUE_FLAGS[currentWrapper];\r\n if (valueFlags != null && valueFlags.includes(token) == true) {\r\n expectFlagValue = true;\r\n }\r\n }\r\n continue;\r\n }\r\n\r\n if (NUMERIC_OPERAND_PATTERN.test(token) == true && wrappers.length > 0) {\r\n continue;\r\n }\r\n\r\n const unquoted = unquoteToken(token);\r\n const trimmed = unquoted.replace(/^[({!]+|[)}]+$/g, \"\");\r\n if (SHELL_KEYWORD_SET.has(trimmed) == true || trimmed === \"\") {\r\n continue;\r\n }\r\n\r\n const basename = trimmed.split(\"/\").pop() ?? trimmed;\r\n if (basename === \"\") {\r\n continue;\r\n }\r\n\r\n if (WRAPPER_BINARY_SET.has(basename) == true) {\r\n wrappers.push(basename);\r\n continue;\r\n }\r\n\r\n binary = basename;\r\n binaryIndex = index;\r\n break;\r\n }\r\n\r\n if (binaryIndex >= 0) {\r\n for (const token of tokens.slice(binaryIndex + 1)) {\r\n const unquoted = unquoteToken(token);\r\n if (unquoted.startsWith(\"-\") == true) {\r\n flags.push(unquoted);\r\n continue;\r\n }\r\n\r\n operands.push(normalizeOperand(unquoted));\r\n }\r\n }\r\n\r\n // `sudo -i` has wrappers but no following command: the wrapper *is* what runs.\r\n if (binary === \"\" && wrappers.length > 0) {\r\n binary = wrappers[wrappers.length - 1];\r\n }\r\n\r\n let normalizedText = segment;\r\n if (binaryIndex >= 0) {\r\n // The binary is rewritten to its basename so `/sbin/reboot` matches `^reboot`.\r\n normalizedText = [binary, ...tokens.slice(binaryIndex + 1).map(unquoteToken)].join(\" \");\r\n }\r\n\r\n return {\r\n wrappers: wrappers,\r\n binary: binary,\r\n flags: flags,\r\n operands: operands,\r\n normalizedText: normalizedText,\r\n tokens: tokens,\r\n };\r\n}\r\n\r\n/**\r\n * Commands carried as arguments of a shell-style binary, which run in their own right:\r\n * the `-c \"<string>\"` form (including bundled clusters like `-lc`), and everything after\r\n * a `--` argv separator (`runuser -u root -- rm -rf /srv`).\r\n */\r\nfunction extractShellPayloads(analysis: SegmentAnalysis): string[] {\r\n if (SHELL_PAYLOAD_BINARY_SET.has(analysis.binary) == false) {\r\n return [];\r\n }\r\n\r\n const tokens = analysis.tokens;\r\n const payloads: string[] = [];\r\n\r\n for (let index = 0; index < tokens.length - 1; index++) {\r\n if (SHELL_COMMAND_FLAG_PATTERN.test(tokens[index]) == true || tokens[index] === \"--command\") {\r\n payloads.push(unquoteToken(tokens[index + 1]));\r\n }\r\n }\r\n\r\n const separatorIndex = tokens.indexOf(\"--\");\r\n if (separatorIndex >= 0 && separatorIndex < tokens.length - 1) {\r\n payloads.push(tokens.slice(separatorIndex + 1).map(unquoteToken).join(\" \"));\r\n }\r\n\r\n return payloads;\r\n}\r\n\r\n/**\r\n * Decides whether a command may run against a host.\r\n *\r\n * Ordered cheapest-to-strictest so the reported reason is the most fundamental one:\r\n * pipeline-wide disasters → command substitutions → then, per segment, profile deny\r\n * patterns → privilege escalation → always-dangerous rules → read-only rules →\r\n * allow-list → nested shell payloads.\r\n *\r\n * **Scope.** This is a mistake-stopper, not a sandbox. It reads the command as text, so\r\n * anything that hides the real operation — an interpreter one-liner, an encoded payload,\r\n * a tool named something else — runs unexamined, and no amount of pattern-chasing closes\r\n * that. The layer that actually contains an agent is the remote account's own permissions.\r\n */\r\nexport function inspectCommand(command: string, policy: SshPolicy, depth = 0): GuardVerdict {\r\n const normalizedCommand = normalizeCommandText(command);\r\n const trimmedCommand = normalizedCommand.trim();\r\n if (trimmedCommand === \"\") {\r\n return { allowed: false, reason: \"command is empty\" };\r\n }\r\n\r\n if (depth > MAX_INSPECTION_DEPTH) {\r\n return {\r\n allowed: false,\r\n reason: \"command nests shells or substitutions too deeply to screen reliably\",\r\n offendingText: trimmedCommand,\r\n };\r\n }\r\n\r\n for (const rule of DANGEROUS_PIPELINE_RULES) {\r\n if (rule.pattern.test(trimmedCommand) == true) {\r\n return {\r\n allowed: false,\r\n reason: `blocked as dangerous: ${rule.reason}`,\r\n offendingText: trimmedCommand,\r\n };\r\n }\r\n }\r\n\r\n for (const substitution of extractSubstitutions(trimmedCommand)) {\r\n const verdict = inspectCommand(substitution, policy, depth + 1);\r\n if (verdict.allowed == false) {\r\n return {\r\n allowed: false,\r\n reason: `${verdict.reason} (inside a command substitution)`,\r\n offendingText: substitution,\r\n };\r\n }\r\n }\r\n\r\n for (const segment of splitCommandSegments(trimmedCommand)) {\r\n const verdict = inspectSegment(segment, policy, depth);\r\n if (verdict.allowed == false) {\r\n return verdict;\r\n }\r\n }\r\n\r\n return { allowed: true };\r\n}\r\n\r\nfunction inspectSegment(segment: string, policy: SshPolicy, depth: number): GuardVerdict {\r\n const analysis = analyzeSegment(segment);\r\n\r\n // Per segment, not per command: an operator writing /^rm/ expects it to catch\r\n // `ls && rm -rf x`, and testing only the whole string silently would not.\r\n for (const pattern of policy.denyPatterns) {\r\n if (pattern.test(segment) == true || pattern.test(analysis.normalizedText) == true) {\r\n return {\r\n allowed: false,\r\n reason: `blocked by the profile's denyPatterns rule /${pattern.source}/`,\r\n offendingText: segment,\r\n };\r\n }\r\n }\r\n\r\n if (policy.allowSudo == false) {\r\n const escalation = [...analysis.wrappers, analysis.binary]\r\n .find((name) => PRIVILEGE_BINARY_SET.has(name) == true);\r\n if (escalation != null) {\r\n return {\r\n allowed: false,\r\n reason: `privilege escalation via '${escalation}' is disabled for this profile (set allowSudo: true to permit it)`,\r\n offendingText: segment,\r\n };\r\n }\r\n }\r\n\r\n // The raw segment is only re-tested when normalization actually changed something —\r\n // it carries redirections (`> /dev/sda`) that the normalized form drops.\r\n const shouldTestRawSegment = analysis.normalizedText !== segment;\r\n\r\n for (const rule of DANGEROUS_COMMAND_RULES) {\r\n const matched = rule.pattern.test(analysis.normalizedText) == true\r\n || (shouldTestRawSegment == true && rule.pattern.test(segment) == true);\r\n if (matched == true) {\r\n return {\r\n allowed: false,\r\n reason: `blocked as dangerous: ${rule.reason}`,\r\n offendingText: segment,\r\n };\r\n }\r\n }\r\n\r\n for (const rule of DANGEROUS_ARGUMENT_RULES) {\r\n if (rule.binaries.includes(analysis.binary) == false) {\r\n continue;\r\n }\r\n\r\n if (rule.requiredFlagPattern != null) {\r\n const hasRequiredFlag = analysis.flags.some((flag) => rule.requiredFlagPattern?.test(flag) == true);\r\n if (hasRequiredFlag == false) {\r\n continue;\r\n }\r\n }\r\n\r\n const offendingOperand = analysis.operands.find((operand) => rule.argumentPattern.test(operand) == true);\r\n if (offendingOperand != null) {\r\n return {\r\n allowed: false,\r\n reason: `blocked as dangerous: ${rule.reason}`,\r\n offendingText: `${analysis.binary} … ${offendingOperand}`,\r\n };\r\n }\r\n }\r\n\r\n if (policy.readonly == true) {\r\n const readonlyVerdict = inspectReadOnly(segment, analysis, shouldTestRawSegment);\r\n if (readonlyVerdict.allowed == false) {\r\n return readonlyVerdict;\r\n }\r\n }\r\n\r\n if (policy.allowCommands.length > 0) {\r\n // An empty binary means the segment is only a redirection (`> /etc/cron.d/x`),\r\n // which writes a file without naming a command — it must not skip the allow-list.\r\n if (analysis.binary === \"\") {\r\n return {\r\n allowed: false,\r\n reason: `this profile only permits ${policy.allowCommands.join(\", \")}, and this segment runs no listed command`,\r\n offendingText: segment,\r\n };\r\n }\r\n\r\n if (policy.allowCommands.includes(analysis.binary) == false) {\r\n return {\r\n allowed: false,\r\n reason: `'${analysis.binary}' is not in this profile's allowCommands list (${policy.allowCommands.join(\", \")})`,\r\n offendingText: segment,\r\n };\r\n }\r\n }\r\n\r\n for (const payload of extractShellPayloads(analysis)) {\r\n const verdict = inspectCommand(payload, policy, depth + 1);\r\n if (verdict.allowed == false) {\r\n return {\r\n allowed: false,\r\n reason: `${verdict.reason} (inside a '${analysis.binary} -c' payload)`,\r\n offendingText: payload,\r\n };\r\n }\r\n }\r\n\r\n return { allowed: true };\r\n}\r\n\r\nfunction inspectReadOnly(segment: string, analysis: SegmentAnalysis, shouldTestRawSegment: boolean): GuardVerdict {\r\n if (WRITE_BINARY_SET.has(analysis.binary) == true) {\r\n return {\r\n allowed: false,\r\n reason: `profile is read-only and '${analysis.binary}' can modify the host`,\r\n offendingText: segment,\r\n };\r\n }\r\n\r\n for (const rule of WRITE_USAGE_RULES) {\r\n const matched = rule.pattern.test(analysis.normalizedText) == true\r\n || (shouldTestRawSegment == true && rule.pattern.test(segment) == true);\r\n if (matched == true) {\r\n return {\r\n allowed: false,\r\n reason: `profile is read-only: ${rule.reason}`,\r\n offendingText: segment,\r\n };\r\n }\r\n }\r\n\r\n const withoutQuotes = stripQuotedText(segment);\r\n const withoutBenignRedirects = withoutQuotes.replace(BENIGN_REDIRECT_PATTERN, \"\");\r\n if (OUTPUT_REDIRECT_PATTERN.test(withoutBenignRedirects) == true) {\r\n return {\r\n allowed: false,\r\n reason: \"profile is read-only and the command redirects output into a file\",\r\n offendingText: segment,\r\n };\r\n }\r\n\r\n return { allowed: true };\r\n}\r\n","import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\n\r\nimport { PROTECTED_LOCAL_PATH_RULES, UNSAFE_PATH_CHARACTER_PATTERN } from \"@/_defs\";\r\nimport { $logger, isAbsoluteRemotePath, isRemotePathWithin, normalizeRemotePath } from \"@/_libs\";\r\nimport type { GuardVerdict, SshPolicy } from \"@/_types\";\r\n\r\nexport type RemotePathAccess = \"read\" | \"write\";\r\n\r\n/**\r\n * Decides whether a file tool may touch a remote path.\r\n *\r\n * With `allowedPaths` set, only absolute paths are accepted: a relative path resolves\r\n * against the SFTP session's own working directory, which the guard cannot see, so it\r\n * could escape the allow-list without any way to detect it here.\r\n *\r\n * Symlinks on the remote side are not resolved — the guard has no view of that\r\n * filesystem. `allowedPaths` therefore bounds *names*, not inodes.\r\n */\r\nexport function inspectRemotePath(\r\n remotePath: string,\r\n policy: SshPolicy,\r\n access: RemotePathAccess\r\n): GuardVerdict {\r\n const trimmedPath = remotePath.trim();\r\n if (trimmedPath === \"\") {\r\n return { allowed: false, reason: \"path is empty\" };\r\n }\r\n\r\n if (access === \"write\" && policy.readonly == true) {\r\n return {\r\n allowed: false,\r\n reason: \"profile is read-only, remote writes are not permitted\",\r\n offendingText: trimmedPath,\r\n };\r\n }\r\n\r\n if (policy.allowedPaths.length === 0) {\r\n return { allowed: true };\r\n }\r\n\r\n if (isAbsoluteRemotePath(trimmedPath) == false) {\r\n return {\r\n allowed: false,\r\n reason: `this profile restricts file access to ${policy.allowedPaths.join(\", \")}, so the path must be absolute`,\r\n offendingText: trimmedPath,\r\n };\r\n }\r\n\r\n const normalizedPath = normalizeRemotePath(trimmedPath);\r\n const matched = policy.allowedPaths.some((allowedPath) => isRemotePathWithin(normalizedPath, allowedPath) == true);\r\n if (matched == false) {\r\n return {\r\n allowed: false,\r\n reason: `path is outside this profile's allowedPaths (${policy.allowedPaths.join(\", \")})`,\r\n offendingText: normalizedPath,\r\n };\r\n }\r\n\r\n return { allowed: true };\r\n}\r\n\r\n/**\r\n * Resolves symlinks so the guard compares the file that will actually be touched.\r\n *\r\n * A lexical `path.resolve` is not enough: `~/notes.txt` symlinked to `~/.ssh/id_ed25519`\r\n * matches no rule by name, and both `fastPut` and `fastGet` follow the link. For a path\r\n * that does not exist yet (the normal download case) the parent directory is resolved\r\n * instead, which catches a symlinked *directory*.\r\n */\r\nfunction toRealPath(inputPath: string): string {\r\n const _logger = $logger.child({ context: \"toRealPath\" });\r\n\r\n try {\r\n return fs.realpathSync.native(inputPath);\r\n }\r\n catch (ex) {\r\n // Expected for a download destination that does not exist yet, so this is not an\r\n // error — but it must not pass unrecorded, since the fallback guards less.\r\n _logger.debug(\"path does not resolve, falling back to its parent directory\", {\r\n reason: ex instanceof Error ? ex.name : \"unknown\",\r\n });\r\n }\r\n\r\n try {\r\n const parentRealPath = fs.realpathSync.native(path.dirname(inputPath));\r\n return path.join(parentRealPath, path.basename(inputPath));\r\n }\r\n catch (ex) {\r\n _logger.debug(\"parent directory does not resolve either, comparing lexically\", {\r\n reason: ex instanceof Error ? ex.name : \"unknown\",\r\n });\r\n return path.resolve(inputPath);\r\n }\r\n}\r\n\r\n/** Path form the protected-path rules are written against. */\r\nfunction toComparablePath(inputPath: string): string {\r\n return toRealPath(inputPath).replace(/\\\\/g, \"/\");\r\n}\r\n\r\n/**\r\n * Decides whether a file tool may touch a path on the machine running this server.\r\n *\r\n * Separate from the remote guard because the threat is inverted: here the danger is\r\n * reading the operator's own credentials out (upload) or overwriting the files that\r\n * define the guards themselves (download).\r\n *\r\n * @param resolvedLocalPath Absolute path, already `~`-expanded.\r\n * @param extraProtectedPaths Runtime-known paths to protect on top of the built-in rules,\r\n * for a host embedding this server as a library.\r\n */\r\nexport function inspectLocalPath(\r\n resolvedLocalPath: string,\r\n access: RemotePathAccess,\r\n extraProtectedPaths: string[] = []\r\n): GuardVerdict {\r\n const trimmedPath = resolvedLocalPath.trim();\r\n if (trimmedPath === \"\") {\r\n return { allowed: false, reason: \"local path is empty\" };\r\n }\r\n\r\n const comparablePath = toComparablePath(trimmedPath);\r\n\r\n for (const rule of PROTECTED_LOCAL_PATH_RULES) {\r\n if (rule.pattern.test(comparablePath) == true) {\r\n let verb = \"read from\";\r\n if (access === \"write\") {\r\n verb = \"written to\";\r\n }\r\n\r\n return {\r\n allowed: false,\r\n reason: `local path is protected and cannot be ${verb}: ${rule.reason}`,\r\n offendingText: trimmedPath,\r\n };\r\n }\r\n }\r\n\r\n for (const protectedPath of extraProtectedPaths) {\r\n if (protectedPath === \"\") {\r\n continue;\r\n }\r\n\r\n if (comparablePath.toLowerCase() === toComparablePath(protectedPath).toLowerCase()) {\r\n return {\r\n allowed: false,\r\n reason: \"local path is this server's own configuration file\",\r\n offendingText: trimmedPath,\r\n };\r\n }\r\n }\r\n\r\n return { allowed: true };\r\n}\r\n\r\n/**\r\n * Decides whether a `cwd` argument is safe to interpolate into the generated `cd` prefix.\r\n *\r\n * `cwd` never reaches the command guard — it is not a command — yet it lands in the same\r\n * remote shell script. Without this check, `cwd: '/tmp\"; rm -rf /; #'` executes with every\r\n * command guard bypassed.\r\n */\r\nexport function inspectWorkingDirectory(cwd: string): GuardVerdict {\r\n const trimmedCwd = cwd.trim();\r\n if (trimmedCwd === \"\") {\r\n return { allowed: true };\r\n }\r\n\r\n const unsafeMatch = UNSAFE_PATH_CHARACTER_PATTERN.exec(trimmedCwd);\r\n if (unsafeMatch != null) {\r\n const displayCharacter = unsafeMatch[0]\r\n .replace(/\\n/, \"\\\\n\")\r\n .replace(/\\r/, \"\\\\r\")\r\n .replace(/\\t/, \"\\\\t\");\r\n return {\r\n allowed: false,\r\n reason: `cwd contains the shell metacharacter '${displayCharacter}', which is not allowed in a directory argument`,\r\n offendingText: trimmedCwd,\r\n };\r\n }\r\n\r\n return { allowed: true };\r\n}\r\n\r\n/** Warns once when a profile's own settings make its guards inert. */\r\nexport function warnOnInertPolicy(profileName: string, policy: SshPolicy): void {\r\n if (policy.readonly == true || policy.allowCommands.length > 0 || policy.denyPatterns.length > 0) {\r\n return;\r\n }\r\n\r\n const _logger = $logger.child({ context: \"warnOnInertPolicy\", profile: profileName });\r\n _logger.debug(\"profile runs with catastrophe-only guards; remote account permissions are the real boundary\");\r\n}\r\n","import type { GuardVerdict } from \"@/_types\";\r\n\r\n/**\r\n * Thrown when a guard refuses an operation.\r\n *\r\n * A distinct class because callers genuinely branch on it: the tool layer renders a\r\n * rejection with `formatGuardRejection` and marks it a policy decision, while any other\r\n * error is an operational failure worth retrying. It carries the verdict so that\r\n * rendering needs no string parsing.\r\n */\r\nexport class GuardRejectionError extends Error {\r\n constructor(public readonly verdict: GuardVerdict) {\r\n super(verdict.reason ?? \"rejected by the host profile's guard policy\");\r\n this.name = \"GuardRejectionError\";\r\n }\r\n}\r\n","import { spawn } from \"node:child_process\";\r\nimport { createHash } from \"node:crypto\";\r\nimport fs from \"node:fs\";\r\nimport { Duplex } from \"node:stream\";\r\nimport { StringDecoder } from \"node:string_decoder\";\r\n\r\nimport type ssh2 from \"ssh2\";\r\nimport type { ConnectConfig, SFTPWrapper } from \"ssh2\";\r\n\r\nimport { CWD_MARKER, KEEPALIVE_INTERVAL_MS } from \"@/_defs\";\r\nimport { $logger, quoteShellArgument, truncateOutput } from \"@/_libs\";\r\nimport type { SshExecResult, SshHostProfile, SshJumpProfile } from \"@/_types\";\r\nimport { GuardRejectionError, canonicalizeCommand, inspectCommand, inspectWorkingDirectory } from \"@/modules/guard\";\r\n\r\n/** Stops runaway output from filling the heap before truncation can happen. */\r\nconst OUTPUT_HARD_CAP_MULTIPLIER = 4;\r\n\r\n/** Key file contents by absolute path — immutable for the process lifetime. */\r\nconst PRIVATE_KEY_CACHE = new Map<string, Buffer>();\r\n\r\nexport interface SshExecOptions {\r\n command: string;\r\n /** Directory to `cd` into first; the login shell's default is used when absent. */\r\n cwd?: string;\r\n timeoutMs: number;\r\n maxOutputCharacters: number;\r\n /** Appends a `$PWD` marker so a session can follow `cd` between commands. */\r\n trackCwd: boolean;\r\n}\r\n\r\n/** Windows OpenSSH agent pipe — ssh2 accepts it wherever a socket path is expected. */\r\nconst WINDOWS_AGENT_PIPE = \"\\\\\\\\.\\\\pipe\\\\openssh-ssh-agent\";\r\n\r\nfunction resolveAgentPath(): string | undefined {\r\n const authSocket = process.env.SSH_AUTH_SOCK;\r\n if (authSocket != null && authSocket !== \"\") {\r\n return authSocket;\r\n }\r\n\r\n if (process.platform === \"win32\") {\r\n return WINDOWS_AGENT_PIPE;\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\n/**\r\n * Reads a private key, caching by path.\r\n *\r\n * Without the cache every one-off tool call re-read the same file synchronously, stalling\r\n * the event loop for every other in-flight call.\r\n *\r\n * @param label Names the hop in the error, e.g. the profile name or \"<name> jump host\".\r\n * @throws {Error} If the file cannot be read. The path is deliberately kept out of the\r\n * message — it reaches the model verbatim, and the rest of the codebase\r\n * (logger masking, `toHostSummary`) treats it as a secret.\r\n */\r\nfunction readPrivateKey(label: string, keyPath: string): Buffer {\r\n const cached = PRIVATE_KEY_CACHE.get(keyPath);\r\n if (cached != null) {\r\n return cached;\r\n }\r\n\r\n try {\r\n const contents = fs.readFileSync(keyPath);\r\n PRIVATE_KEY_CACHE.set(keyPath, contents);\r\n return contents;\r\n }\r\n catch (ex) {\r\n const _logger = $logger.child({ context: \"readPrivateKey\", profile: label });\r\n _logger.error(ex, \"cannot read the private key file\");\r\n throw new Error(`cannot read the private key configured for '${label}' (see the server log for the path)`);\r\n }\r\n}\r\n\r\n/** Credentials a hop authenticates with — the same three fields on the target and the jump host. */\r\ninterface HopCredentials {\r\n privateKeyPath?: string;\r\n passphrase?: string;\r\n password?: string;\r\n}\r\n\r\n/** Applies the auth order — private key, password, then the SSH agent — onto a connect config. */\r\nfunction applyAuth(connectConfig: ConnectConfig, credentials: HopCredentials, label: string): void {\r\n if (credentials.privateKeyPath != null) {\r\n connectConfig.privateKey = readPrivateKey(label, credentials.privateKeyPath);\r\n }\r\n\r\n if (credentials.passphrase != null) {\r\n connectConfig.passphrase = credentials.passphrase;\r\n }\r\n\r\n if (credentials.password != null) {\r\n connectConfig.password = credentials.password;\r\n }\r\n\r\n // No key and no password — fall back to the running SSH agent, which is how most\r\n // interactive setups already authenticate.\r\n if (connectConfig.privateKey == null && connectConfig.password == null) {\r\n connectConfig.agent = resolveAgentPath();\r\n }\r\n}\r\n\r\n/** OpenSSH-style fingerprint — the same string `ssh-keyscan` and `ssh` itself print. */\r\nexport function toHostKeyFingerprint(hostKey: Buffer): string {\r\n const digest = createHash(\"sha256\").update(hostKey).digest(\"base64\").replace(/=+$/, \"\");\r\n return `SHA256:${digest}`;\r\n}\r\n\r\n/**\r\n * Builds the host key check for one hop.\r\n *\r\n * ssh2 accepts *any* host key when no verifier is supplied, so without this a\r\n * man-in-the-middle — or a rebuilt server presenting a new key — is indistinguishable\r\n * from the real host. Pinning stays opt-in (`pinnedFingerprint`) to match the permissive\r\n * defaults, but the fingerprint is always logged on first connect so it can be copied\r\n * into the profile.\r\n */\r\nfunction buildHostVerifier(\r\n hop: { label: string; host: string; pinnedFingerprint?: string },\r\n onHostKeySeen: (fingerprint: string) => void\r\n): (hostKey: Buffer) => boolean {\r\n return (hostKey: Buffer) => {\r\n const fingerprint = toHostKeyFingerprint(hostKey);\r\n onHostKeySeen(fingerprint);\r\n\r\n const _logger = $logger.child({ context: \"hostVerifier\", profile: hop.label, host: hop.host });\r\n\r\n if (hop.pinnedFingerprint == null) {\r\n _logger.info(\"host key accepted unpinned; pin the fingerprint in the profile to detect a changed key\", {\r\n host_key_fingerprint: fingerprint,\r\n });\r\n return true;\r\n }\r\n\r\n if (hop.pinnedFingerprint === fingerprint) {\r\n _logger.debug(\"host key matches the pinned fingerprint\");\r\n return true;\r\n }\r\n\r\n _logger.error(null, \"host key does not match the pinned fingerprint\", {\r\n expected: hop.pinnedFingerprint,\r\n actual: fingerprint,\r\n });\r\n return false;\r\n };\r\n}\r\n\r\n/** Profile → ssh2 connect options for the target hop. */\r\nfunction buildConnectConfig(\r\n profile: SshHostProfile,\r\n onHostKeySeen: (fingerprint: string) => void\r\n): ConnectConfig {\r\n const connectConfig: ConnectConfig = {\r\n host: profile.host,\r\n port: profile.port,\r\n username: profile.username,\r\n readyTimeout: profile.policy.connectTimeoutMs,\r\n keepaliveInterval: KEEPALIVE_INTERVAL_MS,\r\n hostVerifier: buildHostVerifier(\r\n { label: profile.name, host: profile.host, pinnedFingerprint: profile.hostKeyFingerprint },\r\n onHostKeySeen\r\n ),\r\n };\r\n\r\n applyAuth(connectConfig, profile, profile.name);\r\n\r\n return connectConfig;\r\n}\r\n\r\n/**\r\n * The stream the target handshake runs over when it is not a plain TCP socket, plus the\r\n * resource behind it — a jump client or a proxy process — that must go when the target\r\n * connection does.\r\n */\r\ninterface Transport {\r\n sock: Duplex;\r\n close(): void;\r\n}\r\n\r\n/** OpenSSH `ProxyCommand` tokens: `%h` host, `%p` port, `%r` user, `%%` a literal `%`. */\r\nexport function expandProxyCommand(template: string, hop: { host: string; port: number; username: string }): string {\r\n return template.replace(/%[hpr%]/g, (token) => {\r\n if (token === \"%h\") {\r\n return hop.host;\r\n }\r\n\r\n if (token === \"%p\") {\r\n return String(hop.port);\r\n }\r\n\r\n if (token === \"%r\") {\r\n return hop.username;\r\n }\r\n\r\n return \"%\";\r\n });\r\n}\r\n\r\n/**\r\n * Starts the proxy command and exposes its stdin/stdout as the SSH stream.\r\n *\r\n * Runs through the shell, as OpenSSH does, so the operator can quote arguments the way\r\n * they would in `~/.ssh/config`. The process is killed when the transport closes: on\r\n * Windows the shell is `cmd.exe` and the real command is its child, so the whole tree\r\n * has to go or `cloudflared` would outlive the connection it was carrying.\r\n */\r\nfunction openProxyCommand(profile: SshHostProfile, hop: { host: string; port: number; username: string }): Transport {\r\n const command = expandProxyCommand(profile.proxyCommand ?? \"\", hop);\r\n const _logger = $logger.child({ context: \"openProxyCommand\", profile: profile.name });\r\n _logger.debug(\"starting the proxy command\", { command: command });\r\n\r\n const child = spawn(command, { shell: true, stdio: [\"pipe\", \"pipe\", \"pipe\"], windowsHide: true });\r\n\r\n // The command's own diagnostics — a cloudflared login prompt, a refused tunnel — are\r\n // the only clue when the handshake never starts, so they go to the log at warn.\r\n const stderrDecoder = new StringDecoder(\"utf8\");\r\n child.stderr.on(\"data\", (chunk: Buffer) => {\r\n const text = stderrDecoder.write(chunk).trim();\r\n if (text !== \"\") {\r\n _logger.warn(\"proxy command stderr\", { text: text });\r\n }\r\n });\r\n\r\n child.on(\"exit\", (code, signal) => {\r\n _logger.debug(\"proxy command exited\", { exit_code: code, signal: signal });\r\n });\r\n\r\n const sock = Duplex.from({ readable: child.stdout, writable: child.stdin });\r\n\r\n // Permanent listener: killing the process tears its pipes down, and the stream reports\r\n // that as an error. ssh2 only listens once it has been handed the stream, so a kill\r\n // before the handshake (an unreadable jump key, a disconnect mid-setup) would\r\n // otherwise surface as an unhandled 'error' and take the whole server down.\r\n sock.on(\"error\", (error: Error) => {\r\n _logger.debug(\"proxy command stream error\", { error_message: error.message });\r\n });\r\n\r\n // A spawn failure surfaces on the child, not on the stream; forward it so ssh2\r\n // reports it as a socket error instead of waiting for the ready timeout.\r\n child.on(\"error\", (error: Error) => {\r\n sock.destroy(error);\r\n });\r\n\r\n // Latch: close() is reached from both the jump client's and the target client's\r\n // 'close' handlers when the two are stacked, and one kill is enough.\r\n let closed = false;\r\n\r\n return {\r\n sock: sock,\r\n close: () => {\r\n if (closed == true) {\r\n return;\r\n }\r\n closed = true;\r\n\r\n if (child.exitCode != null || child.signalCode != null) {\r\n return;\r\n }\r\n\r\n if (process.platform === \"win32\" && child.pid != null) {\r\n const killer = spawn(\"taskkill\", [\"/pid\", String(child.pid), \"/T\", \"/F\"], { stdio: \"ignore\", windowsHide: true });\r\n // Without a listener a failure to even start taskkill is an unhandled\r\n // 'error' event, which takes the whole server down with it.\r\n killer.on(\"error\", (error: Error) => {\r\n _logger.error(error, \"failed to kill the proxy command's process tree\");\r\n });\r\n return;\r\n }\r\n\r\n child.kill();\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Connects to the jump host and opens a forwarded TCP channel to the target, which the\r\n * target handshake then runs over — what `ssh -J` does.\r\n *\r\n * @param proxy Carries the jump hop itself when a proxy command is also configured.\r\n * @throws {Error} If the jump host cannot be reached or refuses the forward. The message\r\n * names the jump host, so a failure there is not mistaken for the target.\r\n */\r\nfunction openJumpTransport(\r\n profile: SshHostProfile,\r\n jump: SshJumpProfile,\r\n proxy: Transport | null,\r\n onHostKeySeen: (fingerprint: string) => void,\r\n Client: typeof ssh2.Client\r\n): Promise<Transport> {\r\n const label = `${profile.name} jump host`;\r\n const endpoint = `${jump.username}@${jump.host}:${jump.port}`;\r\n const _logger = $logger.child({ context: \"openJumpTransport\", profile: profile.name, jump_host: jump.host });\r\n\r\n const jumpConfig: ConnectConfig = {\r\n host: jump.host,\r\n port: jump.port,\r\n username: jump.username,\r\n readyTimeout: profile.policy.connectTimeoutMs,\r\n keepaliveInterval: KEEPALIVE_INTERVAL_MS,\r\n hostVerifier: buildHostVerifier(\r\n { label: label, host: jump.host, pinnedFingerprint: jump.hostKeyFingerprint },\r\n onHostKeySeen\r\n ),\r\n };\r\n\r\n applyAuth(jumpConfig, jump, label);\r\n\r\n if (proxy != null) {\r\n jumpConfig.sock = proxy.sock;\r\n }\r\n\r\n return new Promise<Transport>((resolve, reject) => {\r\n const jumpClient = new Client();\r\n let settled = false;\r\n\r\n jumpClient.on(\"error\", (error: Error) => {\r\n if (settled == false) {\r\n settled = true;\r\n jumpClient.destroy();\r\n reject(new Error(`jump host ${endpoint}: ${error.message}`));\r\n return;\r\n }\r\n\r\n _logger.error(error, \"jump host connection error after ready\");\r\n });\r\n\r\n jumpClient.on(\"close\", () => {\r\n _logger.debug(\"jump host connection closed\");\r\n proxy?.close();\r\n\r\n if (settled == false) {\r\n settled = true;\r\n reject(new Error(`jump host ${endpoint}: connection closed before the handshake completed`));\r\n }\r\n });\r\n\r\n jumpClient.once(\"ready\", () => {\r\n if (settled == true) {\r\n return;\r\n }\r\n\r\n jumpClient.forwardOut(\"127.0.0.1\", 0, profile.host, profile.port, (error, stream) => {\r\n if (settled == true) {\r\n return;\r\n }\r\n settled = true;\r\n\r\n if (error != null) {\r\n jumpClient.end();\r\n reject(new Error(`jump host ${endpoint} refused the forward to ${profile.host}:${profile.port}: ${error.message}`));\r\n return;\r\n }\r\n\r\n _logger.info(\"jump host connected, target channel forwarded\");\r\n resolve({\r\n sock: stream,\r\n close: () => {\r\n jumpClient.end();\r\n },\r\n });\r\n });\r\n });\r\n\r\n jumpClient.connect(jumpConfig);\r\n });\r\n}\r\n\r\n/**\r\n * Opens whatever carries the target handshake: nothing for a direct TCP connection, a\r\n * proxy process, a jump channel, or a jump channel whose own hop runs over the proxy.\r\n */\r\nasync function openTransport(\r\n profile: SshHostProfile,\r\n onJumpHostKeySeen: (fingerprint: string) => void,\r\n Client: typeof ssh2.Client\r\n): Promise<Transport | null> {\r\n let proxy: Transport | null = null;\r\n if (profile.proxyCommand != null) {\r\n proxy = openProxyCommand(profile, profile.jump ?? profile);\r\n }\r\n\r\n if (profile.jump == null) {\r\n return proxy;\r\n }\r\n\r\n // The proxy process is already running by now; a jump hop that cannot even be set\r\n // up (an unreadable jump key throws before any socket is touched) must not leave it\r\n // behind as an orphan.\r\n try {\r\n const jumpTransport = await openJumpTransport(profile, profile.jump, proxy, onJumpHostKeySeen, Client);\r\n return jumpTransport;\r\n }\r\n catch (ex) {\r\n proxy?.close();\r\n throw ex;\r\n }\r\n}\r\n\r\n/**\r\n * Wraps the user command so the remote shell reports where it ended up.\r\n *\r\n * `$?` is captured before the marker is printed and replayed via `exit`, so the exit\r\n * status the caller sees is the command's, not `printf`'s.\r\n *\r\n * `cwd` appears exactly once, inside `quoteShellArgument`. It must never be interpolated\r\n * anywhere else — an earlier version echoed it into a double-quoted diagnostic, which let\r\n * `cwd: '/x\"; rm -rf /; #'` run arbitrary commands with every guard bypassed. The failure\r\n * message is therefore a fixed string.\r\n */\r\nexport function buildRemoteCommand(command: string, cwd: string | undefined, trackCwd: boolean): string {\r\n const lines: string[] = [];\r\n\r\n if (cwd != null && cwd !== \"\") {\r\n const quotedCwd = quoteShellArgument(cwd);\r\n lines.push(`cd ${quotedCwd} 2>/dev/null || { echo 'akms-mcp-ssh: cannot enter the requested working directory' >&2; exit 1; }`);\r\n }\r\n\r\n lines.push(command);\r\n\r\n if (trackCwd == true) {\r\n lines.push(\"__akms_exit=$?\");\r\n lines.push(`printf '\\\\n${CWD_MARKER}%s' \"$PWD\"`);\r\n lines.push(\"exit $__akms_exit\");\r\n }\r\n\r\n return lines.join(\"\\n\");\r\n}\r\n\r\n/** Splits the trailing `$PWD` marker off stdout. */\r\nexport function extractCwdMarker(stdout: string): { output: string; cwd?: string } {\r\n const markerIndex = stdout.lastIndexOf(CWD_MARKER);\r\n if (markerIndex < 0) {\r\n return { output: stdout };\r\n }\r\n\r\n // First line only: `$PWD` is a single line, so anything past a newline is not the\r\n // directory and must not end up in the `cd` prefix of the next command.\r\n const markerPayload = stdout.slice(markerIndex + CWD_MARKER.length);\r\n const cwd = (markerPayload.split(\"\\n\")[0] ?? \"\").trim();\r\n\r\n let output = stdout.slice(0, markerIndex);\r\n if (output.endsWith(\"\\n\") == true) {\r\n output = output.slice(0, -1);\r\n }\r\n\r\n if (cwd === \"\") {\r\n return { output: output };\r\n }\r\n\r\n return { output: output, cwd: cwd };\r\n}\r\n\r\n/**\r\n * One ssh2 client bound to a profile, plus the exec / SFTP operations run over it.\r\n *\r\n * This class is the chokepoint: it holds the profile, so it applies the command guard\r\n * itself rather than trusting each caller to remember. Enforcement in the tool handlers\r\n * was one forgotten line away from shipping an unscreened command — and one such caller\r\n * (the `ssh_connect` probe) already existed.\r\n */\r\nexport class SshConnection {\r\n private client: ssh2.Client | null = null;\r\n private pendingClient: ssh2.Client | null = null;\r\n private connecting: Promise<void> | null = null;\r\n private disposed = false;\r\n /** Set when the peer closed a previously established connection. */\r\n private droppedAfterReady = false;\r\n private observedFingerprint: string | null = null;\r\n private observedJumpFingerprint: string | null = null;\r\n\r\n constructor(public readonly profile: SshHostProfile) {\r\n }\r\n\r\n get isConnected(): boolean {\r\n return this.client != null;\r\n }\r\n\r\n /** Host key the server presented, once a handshake has been attempted. */\r\n get hostKeyFingerprint(): string | null {\r\n return this.observedFingerprint;\r\n }\r\n\r\n /** Host key the jump host presented; null without a jump host or before its handshake. */\r\n get jumpHostKeyFingerprint(): string | null {\r\n return this.observedJumpFingerprint;\r\n }\r\n\r\n /** True when a connection was established and then lost, so shell state is gone. */\r\n get wasDropped(): boolean {\r\n return this.droppedAfterReady;\r\n }\r\n\r\n private assertNotDisposed(): void {\r\n if (this.disposed == true) {\r\n throw new Error(`ssh connection for '${this.profile.name}' has been closed; open a new session`);\r\n }\r\n }\r\n\r\n /**\r\n * Opens the connection, or joins the in-flight attempt when one is already running.\r\n *\r\n * With a proxy command or a jump host the carrier is opened first and handed to ssh2\r\n * as the socket; it is torn down with the target connection, whichever side closes.\r\n *\r\n * @throws {Error} If the connection was closed, authentication fails, the host is\r\n * unreachable, the handshake times out, or the carrier cannot be opened.\r\n */\r\n async connect(onProgress?: (stage: \"transport\" | \"host\") => void): Promise<void> {\r\n this.assertNotDisposed();\r\n\r\n if (this.client != null) {\r\n return;\r\n }\r\n\r\n if (this.connecting != null) {\r\n await this.connecting;\r\n return;\r\n }\r\n\r\n const _logger = $logger.child({\r\n context: \"SshConnection.connect\",\r\n profile: this.profile.name,\r\n host: this.profile.host,\r\n port: this.profile.port,\r\n username: this.profile.username,\r\n });\r\n\r\n const connectConfig = buildConnectConfig(this.profile, (fingerprint) => {\r\n this.observedFingerprint = fingerprint;\r\n });\r\n\r\n this.connecting = (async () => {\r\n // Loading ssh2 starts its crypto runtime. Idle MCP connections and local\r\n // configuration work do not need it; share this load with the handshake.\r\n const { default: { Client } } = await import(\"ssh2\");\r\n this.assertNotDisposed();\r\n if (this.profile.jump != null || this.profile.proxyCommand != null) {\r\n onProgress?.(\"transport\");\r\n }\r\n const transport = await openTransport(this.profile, (fingerprint) => {\r\n this.observedJumpFingerprint = fingerprint;\r\n }, Client);\r\n\r\n // disconnect() may have run while the carrier was being opened.\r\n if (this.disposed == true) {\r\n transport?.close();\r\n throw new Error(`ssh connection for '${this.profile.name}' was closed during the handshake`);\r\n }\r\n\r\n if (transport != null) {\r\n connectConfig.sock = transport.sock;\r\n }\r\n\r\n onProgress?.(\"host\");\r\n await new Promise<void>((resolve, reject) => {\r\n const client = new Client();\r\n this.pendingClient = client;\r\n let settled = false;\r\n\r\n // One permanent 'error' listener, installed before connect() and never\r\n // removed. Registering it with `once` left the client listener-less after the\r\n // first error, and ssh2 emits 'error' again while the socket tears down —\r\n // an unhandled 'error' event takes down the whole server process.\r\n client.on(\"error\", (error: Error) => {\r\n if (settled == false) {\r\n settled = true;\r\n this.pendingClient = null;\r\n client.destroy();\r\n reject(error);\r\n return;\r\n }\r\n\r\n // Only a live connection's error is news. A failed handshake is\r\n // already reported through reject(); ssh2 keeps emitting while the\r\n // socket tears down (\"Connection lost before handshake\", an abort\r\n // from the destroyed proxy stream) and those are just the aftermath.\r\n if (this.client !== client) {\r\n _logger.debug(\"ssh connection error during teardown\", { error_message: error.message });\r\n return;\r\n }\r\n\r\n _logger.error(error, \"ssh connection error after ready\");\r\n });\r\n\r\n client.on(\"close\", () => {\r\n // Whichever side closed, the carrier behind this connection is done with.\r\n transport?.close();\r\n\r\n if (this.client === client) {\r\n _logger.debug(\"ssh connection closed by the peer\");\r\n this.client = null;\r\n this.droppedAfterReady = true;\r\n return;\r\n }\r\n\r\n // A carrier that ends before the handshake (a proxy command that exited at\r\n // once) closes without a usable 'error'; settle here or connect() hangs.\r\n if (settled == false) {\r\n settled = true;\r\n this.pendingClient = null;\r\n reject(new Error(\"connection closed before the handshake completed\"));\r\n }\r\n });\r\n\r\n client.once(\"ready\", () => {\r\n if (settled == true) {\r\n return;\r\n }\r\n settled = true;\r\n this.pendingClient = null;\r\n\r\n // disconnect() may have run while the handshake was in flight; adopting\r\n // the client now would leak a live connection nothing can ever close.\r\n if (this.disposed == true) {\r\n client.destroy();\r\n reject(new Error(`ssh connection for '${this.profile.name}' was closed during the handshake`));\r\n return;\r\n }\r\n\r\n this.client = client;\r\n resolve();\r\n });\r\n\r\n client.connect(connectConfig);\r\n });\r\n })();\r\n\r\n try {\r\n await this.connecting;\r\n _logger.info(\"ssh connection established\");\r\n }\r\n catch (ex) {\r\n _logger.error(ex, \"ssh connection failed\");\r\n\r\n let detail = String(ex);\r\n if (ex instanceof Error) {\r\n detail = ex.message;\r\n }\r\n\r\n throw new Error(`ssh connect failed for '${this.profile.name}' (${this.profile.username}@${this.profile.host}:${this.profile.port}): ${detail}`);\r\n }\r\n finally {\r\n this.connecting = null;\r\n }\r\n }\r\n\r\n /**\r\n * Screens a command against this profile's policy, then runs it on its own channel.\r\n *\r\n * The canonical form is what gets screened *and* what gets sent — validating one\r\n * string while executing another is a bypass by construction.\r\n *\r\n * @throws {GuardRejectionError} If the policy refuses the command or the cwd.\r\n * @throws {Error} If the connection is closed or the channel cannot be opened.\r\n */\r\n async exec(options: SshExecOptions): Promise<SshExecResult> {\r\n const canonicalCommand = canonicalizeCommand(options.command);\r\n\r\n const verdict = inspectCommand(canonicalCommand, this.profile.policy);\r\n if (verdict.allowed == false) {\r\n throw new GuardRejectionError(verdict);\r\n }\r\n\r\n if (options.cwd != null) {\r\n const cwdVerdict = inspectWorkingDirectory(options.cwd);\r\n if (cwdVerdict.allowed == false) {\r\n throw new GuardRejectionError(cwdVerdict);\r\n }\r\n }\r\n\r\n await this.connect();\r\n\r\n const client = this.client;\r\n if (client == null) {\r\n throw new Error(`ssh connection for '${this.profile.name}' is not open`);\r\n }\r\n\r\n const _logger = $logger.child({\r\n context: \"SshConnection.exec\",\r\n profile: this.profile.name,\r\n cwd: options.cwd,\r\n });\r\n\r\n const remoteCommand = buildRemoteCommand(canonicalCommand, options.cwd, options.trackCwd);\r\n const hardCap = options.maxOutputCharacters * OUTPUT_HARD_CAP_MULTIPLIER;\r\n const startedAt = Date.now();\r\n\r\n const raw = await new Promise<{\r\n stdout: string;\r\n stderr: string;\r\n exitCode: number | null;\r\n signal?: string;\r\n timedOut: boolean;\r\n }>((resolve, reject) => {\r\n client.exec(remoteCommand, (error, channel) => {\r\n if (error != null) {\r\n reject(error);\r\n return;\r\n }\r\n\r\n let stdout = \"\";\r\n let stderr = \"\";\r\n let exitCode: number | null = null;\r\n let signal: string | undefined = undefined;\r\n let timedOut = false;\r\n let settled = false;\r\n // Latch: channel.close() is a round trip, so packets keep arriving after\r\n // the cap trips. Without it each one re-logged and re-grew the string.\r\n let capExceeded = false;\r\n\r\n // One decoder per stream: a multi-byte character split across two SSH\r\n // packets decodes to U+FFFD if each chunk is converted on its own.\r\n const stdoutDecoder = new StringDecoder(\"utf8\");\r\n const stderrDecoder = new StringDecoder(\"utf8\");\r\n\r\n const timeoutTimer = setTimeout(() => {\r\n timedOut = true;\r\n _logger.warn(\"command timed out, closing the channel\", { timeout_ms: options.timeoutMs });\r\n channel.close();\r\n }, options.timeoutMs);\r\n timeoutTimer.unref();\r\n\r\n // Only 'close' settles the promise. 'end' fires on CHANNEL_EOF, which the\r\n // remote may send before the separate exit-status request arrives — settling\r\n // there reports a successful command as `exit code: none (killed)`.\r\n const finish = (): void => {\r\n if (settled == true) {\r\n return;\r\n }\r\n settled = true;\r\n clearTimeout(timeoutTimer);\r\n\r\n stdout += stdoutDecoder.end();\r\n stderr += stderrDecoder.end();\r\n\r\n resolve({ stdout: stdout, stderr: stderr, exitCode: exitCode, signal: signal, timedOut: timedOut });\r\n };\r\n\r\n channel.on(\"data\", (chunk: Buffer) => {\r\n if (capExceeded == true) {\r\n return;\r\n }\r\n\r\n stdout += stdoutDecoder.write(chunk);\r\n if (stdout.length > hardCap) {\r\n capExceeded = true;\r\n _logger.warn(\"stdout exceeded the hard cap, closing the channel\", { hard_cap: hardCap });\r\n channel.close();\r\n }\r\n });\r\n\r\n channel.stderr.on(\"data\", (chunk: Buffer) => {\r\n if (capExceeded == true) {\r\n return;\r\n }\r\n\r\n stderr += stderrDecoder.write(chunk);\r\n if (stderr.length > hardCap) {\r\n capExceeded = true;\r\n channel.close();\r\n }\r\n });\r\n\r\n channel.on(\"exit\", (code: number | null, exitSignal?: string) => {\r\n exitCode = code;\r\n signal = exitSignal;\r\n });\r\n\r\n channel.on(\"close\", finish);\r\n channel.on(\"error\", (channelError: Error) => {\r\n clearTimeout(timeoutTimer);\r\n if (settled == true) {\r\n return;\r\n }\r\n settled = true;\r\n reject(channelError);\r\n });\r\n\r\n // Send EOF on the remote stdin straight away. Nothing will ever be written\r\n // to it, and a command that reads — a `sudo` password prompt, an apt\r\n // confirmation, `ssh-keygen` — would otherwise sit there until the timeout\r\n // fires. With stdin closed it fails in milliseconds with a usable message.\r\n //\r\n // `eof()`, not `end()`: end() also closes the channel, which loses the\r\n // exit-status request the server sends afterwards.\r\n channel.eof();\r\n });\r\n });\r\n\r\n // Only parse the marker when this call asked for it — otherwise output that merely\r\n // contains the marker text would be silently cut at that point.\r\n let output = raw.stdout;\r\n let cwd: string | undefined = undefined;\r\n if (options.trackCwd == true) {\r\n const parsed = extractCwdMarker(raw.stdout);\r\n output = parsed.output;\r\n cwd = parsed.cwd;\r\n }\r\n\r\n const truncatedStdout = truncateOutput(output, options.maxOutputCharacters);\r\n const truncatedStderr = truncateOutput(raw.stderr, options.maxOutputCharacters);\r\n\r\n const result: SshExecResult = {\r\n stdout: truncatedStdout.text,\r\n stderr: truncatedStderr.text,\r\n exitCode: raw.exitCode,\r\n timedOut: raw.timedOut,\r\n truncated: truncatedStdout.truncated == true || truncatedStderr.truncated == true,\r\n durationMs: Date.now() - startedAt,\r\n };\r\n\r\n if (raw.signal != null) {\r\n result.signal = raw.signal;\r\n }\r\n\r\n if (cwd != null) {\r\n result.cwd = cwd;\r\n }\r\n\r\n _logger.debug(\"command finished\", {\r\n exit_code: result.exitCode,\r\n duration_ms: result.durationMs,\r\n timed_out: result.timedOut,\r\n });\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Opens an SFTP channel, hands it to `handler`, and closes it afterwards.\r\n *\r\n * A channel per operation costs one round trip but keeps a broken transfer from\r\n * poisoning later ones — SFTP channels do not recover from a mid-stream error.\r\n *\r\n * The channel's own `error` event is wired into the returned promise: ssh2 strips its\r\n * setup listeners before handing the wrapper over, so an unhandled SFTP protocol error\r\n * would otherwise crash the process rather than fail this one call.\r\n *\r\n * @throws {Error} If the SFTP subsystem cannot be started or the channel errors.\r\n */\r\n async withSftp<T>(handler: (sftp: SFTPWrapper) => Promise<T>): Promise<T> {\r\n await this.connect();\r\n\r\n const client = this.client;\r\n if (client == null) {\r\n throw new Error(`ssh connection for '${this.profile.name}' is not open`);\r\n }\r\n\r\n const sftp = await new Promise<SFTPWrapper>((resolve, reject) => {\r\n client.sftp((error, sftpWrapper) => {\r\n if (error != null) {\r\n reject(error);\r\n return;\r\n }\r\n\r\n resolve(sftpWrapper);\r\n });\r\n });\r\n\r\n return await new Promise<T>((resolve, reject) => {\r\n let settled = false;\r\n\r\n const finish = (settle: () => void): void => {\r\n if (settled == true) {\r\n return;\r\n }\r\n settled = true;\r\n\r\n try {\r\n sftp.end();\r\n }\r\n catch (ex) {\r\n const _logger = $logger.child({ context: \"SshConnection.withSftp\", profile: this.profile.name });\r\n _logger.error(ex, \"failed to close the sftp channel\");\r\n }\r\n\r\n settle();\r\n };\r\n\r\n sftp.on(\"error\", (error: Error) => {\r\n finish(() => {\r\n reject(error);\r\n });\r\n });\r\n\r\n handler(sftp).then(\r\n (value) => {\r\n finish(() => {\r\n resolve(value);\r\n });\r\n },\r\n (error: unknown) => {\r\n finish(() => {\r\n reject(error);\r\n });\r\n }\r\n );\r\n });\r\n }\r\n\r\n /** Closes the connection for good; a disposed connection will not re-dial. */\r\n disconnect(): void {\r\n this.disposed = true;\r\n\r\n // A handshake still in flight would otherwise complete and install its client on\r\n // an object nothing holds a reference to any more.\r\n if (this.pendingClient != null) {\r\n this.pendingClient.destroy();\r\n this.pendingClient = null;\r\n }\r\n\r\n if (this.client == null) {\r\n return;\r\n }\r\n\r\n this.client.end();\r\n this.client = null;\r\n }\r\n}\r\n","import { setInterval, clearInterval } from \"node:timers\";\r\n\r\nimport { SESSION_IDLE_TIMEOUT_MS, SESSION_SWEEP_INTERVAL_MS } from \"@/_defs\";\r\nimport { $logger } from \"@/_libs\";\r\nimport type { SshExecResult, SshHostProfile, SshSessionInfo } from \"@/_types\";\r\nimport { inspectWorkingDirectory } from \"@/modules/guard\";\r\n\r\nimport { SshConnection } from \"./ssh-connection\";\r\n\r\n/** Ceiling on concurrently open sessions, so a runaway loop can't exhaust file descriptors. */\r\nconst MAX_OPEN_SESSIONS = 32;\r\n\r\n/** A connection plus the shell state (working directory) carried across commands. */\r\nexport class SshSession {\r\n /** Empty until the first command reports `$PWD`; then the real remote path. */\r\n cwd: string;\r\n lastUsedAt: Date;\r\n execCount = 0;\r\n\r\n readonly openedAt = new Date();\r\n\r\n /** Tail of the serialized operation chain; see `runExclusive`. */\r\n private operationChain: Promise<unknown> = Promise.resolve();\r\n private activeOperationCount = 0;\r\n\r\n constructor(\r\n public readonly sessionId: string,\r\n public readonly connection: SshConnection\r\n ) {\r\n this.cwd = connection.profile.defaultCwd ?? \"\";\r\n this.lastUsedAt = new Date();\r\n }\r\n\r\n get profile(): SshHostProfile {\r\n return this.connection.profile;\r\n }\r\n\r\n /** True while an operation is running, so the idle sweeper leaves it alone. */\r\n get isBusy(): boolean {\r\n return this.activeOperationCount > 0;\r\n }\r\n\r\n /** Marks the session as in use. */\r\n touch(): void {\r\n this.lastUsedAt = new Date();\r\n }\r\n\r\n /**\r\n * Runs `operation` with exclusive use of this session.\r\n *\r\n * Serialized because `cwd` is read-modify-write across a network round trip: two\r\n * concurrent calls both read the old directory, and the one that finishes second\r\n * writes it back — silently undoing the other's `cd`. Bracketing with `touch()` also\r\n * keeps the idle sweeper off a session that is mid-transfer.\r\n */\r\n async runExclusive<T>(operation: (connection: SshConnection) => Promise<T>): Promise<T> {\r\n const previous = this.operationChain;\r\n\r\n let releaseChain: () => void = () => {\r\n // Replaced below; assigned here so the type is not nullable.\r\n };\r\n this.operationChain = new Promise<void>((resolve) => {\r\n releaseChain = resolve;\r\n });\r\n\r\n await previous.catch(() => {\r\n // A failed predecessor must not poison the queue for later callers.\r\n });\r\n\r\n this.activeOperationCount += 1;\r\n this.touch();\r\n\r\n try {\r\n return await operation(this.connection);\r\n }\r\n finally {\r\n this.activeOperationCount -= 1;\r\n this.touch();\r\n releaseChain();\r\n }\r\n }\r\n\r\n /**\r\n * Runs a command in this session's working directory and adopts wherever it ended up,\r\n * so a `cd` in one call is still in effect on the next.\r\n *\r\n * Passing `cwd` runs this command elsewhere *and* moves the session there — same\r\n * result as prefixing `cd <cwd> &&`, which is what a shell user would expect.\r\n *\r\n * @throws {Error} If the connection was dropped since the session opened. A silent\r\n * re-dial would hand back a fresh login shell while the model still\r\n * believes it holds the old one — environment, sudo cache and\r\n * background jobs all gone with no signal.\r\n */\r\n async exec(command: string, options?: { cwd?: string; timeoutMs?: number }): Promise<SshExecResult> {\r\n if (this.connection.wasDropped == true) {\r\n throw new Error(`session ${this.sessionId} lost its connection to ${this.profile.host}; its shell state is gone — open a new session with ssh_connect`);\r\n }\r\n\r\n const policy = this.profile.policy;\r\n const targetCwd = options?.cwd ?? this.cwd;\r\n\r\n return await this.runExclusive(async (connection) => {\r\n const result = await connection.exec({\r\n command: command,\r\n cwd: targetCwd,\r\n timeoutMs: options?.timeoutMs ?? policy.execTimeoutMs,\r\n maxOutputCharacters: policy.maxOutputCharacters,\r\n trackCwd: true,\r\n });\r\n\r\n if (result.cwd != null && result.cwd !== \"\") {\r\n // The reported `$PWD` becomes the `cd` argument of the next command, so it\r\n // goes through the same metacharacter check as a caller-supplied cwd.\r\n const verdict = inspectWorkingDirectory(result.cwd);\r\n if (verdict.allowed == true) {\r\n this.cwd = result.cwd;\r\n }\r\n else {\r\n const _logger = $logger.child({ context: \"SshSession.exec\", session_id: this.sessionId });\r\n _logger.warn(\"remote working directory contains unsafe characters, keeping the previous cwd\", {\r\n reported_cwd: result.cwd,\r\n });\r\n }\r\n }\r\n\r\n this.execCount += 1;\r\n return result;\r\n });\r\n }\r\n\r\n toInfo(): SshSessionInfo {\r\n let displayCwd = this.cwd;\r\n if (displayCwd === \"\") {\r\n displayCwd = \"(login default)\";\r\n }\r\n\r\n return {\r\n sessionId: this.sessionId,\r\n profileName: this.profile.name,\r\n host: this.profile.host,\r\n username: this.profile.username,\r\n cwd: displayCwd,\r\n openedAt: this.openedAt.toISOString(),\r\n lastUsedAt: this.lastUsedAt.toISOString(),\r\n execCount: this.execCount,\r\n };\r\n }\r\n}\r\n\r\n/** Owns every open session: creation, lookup, idle expiry and shutdown. */\r\nexport class SshSessionManager {\r\n private readonly sessions = new Map<string, SshSession>();\r\n private sequence = 0;\r\n private pendingOpenCount = 0;\r\n private sweepTimer: NodeJS.Timeout | null = null;\r\n\r\n /**\r\n * Opens a connection and registers it as a session.\r\n *\r\n * `pendingOpenCount` reserves the slot across the `await`: checking `sessions.size`\r\n * alone let N concurrent calls all observe the same pre-connect count and blow past\r\n * the ceiling together.\r\n *\r\n * @throws {Error} If the session ceiling is reached or the connection fails.\r\n */\r\n async open(profile: SshHostProfile): Promise<SshSession> {\r\n if (this.sessions.size + this.pendingOpenCount >= MAX_OPEN_SESSIONS) {\r\n throw new Error(`too many open ssh sessions (${MAX_OPEN_SESSIONS}), close some with ssh_disconnect first`);\r\n }\r\n\r\n this.sequence += 1;\r\n const sessionId = `${profile.name}#${this.sequence}`;\r\n\r\n const connection = new SshConnection(profile);\r\n\r\n this.pendingOpenCount += 1;\r\n try {\r\n await connection.connect();\r\n }\r\n finally {\r\n this.pendingOpenCount -= 1;\r\n }\r\n\r\n const session = new SshSession(sessionId, connection);\r\n this.sessions.set(sessionId, session);\r\n\r\n const _logger = $logger.child({ context: \"SshSessionManager.open\", session_id: sessionId, profile: profile.name });\r\n _logger.info(\"ssh session opened\", { open_sessions: this.sessions.size });\r\n\r\n return session;\r\n }\r\n\r\n /**\r\n * Looks up a session, failing with the list of live ids so the caller can self-correct.\r\n *\r\n * @throws {Error} If no session matches `sessionId`.\r\n */\r\n require(sessionId: string): SshSession {\r\n const session = this.sessions.get(sessionId);\r\n if (session == null) {\r\n const openIds = Array.from(this.sessions.keys());\r\n\r\n let openSummary = \"none\";\r\n if (openIds.length > 0) {\r\n openSummary = openIds.join(\", \");\r\n }\r\n\r\n throw new Error(`unknown ssh session '${sessionId}' (open sessions: ${openSummary})`);\r\n }\r\n\r\n return session;\r\n }\r\n\r\n close(sessionId: string): boolean {\r\n const session = this.sessions.get(sessionId);\r\n if (session == null) {\r\n return false;\r\n }\r\n\r\n session.connection.disconnect();\r\n this.sessions.delete(sessionId);\r\n\r\n const _logger = $logger.child({ context: \"SshSessionManager.close\", session_id: sessionId });\r\n _logger.info(\"ssh session closed\", { open_sessions: this.sessions.size });\r\n\r\n return true;\r\n }\r\n\r\n closeAll(): void {\r\n for (const sessionId of Array.from(this.sessions.keys())) {\r\n this.close(sessionId);\r\n }\r\n }\r\n\r\n list(): SshSessionInfo[] {\r\n return Array.from(this.sessions.values()).map((session) => session.toInfo());\r\n }\r\n\r\n /** Starts the idle sweeper; the timer is unref'd so it never holds the process open. */\r\n startIdleSweeper(): void {\r\n if (this.sweepTimer != null) {\r\n return;\r\n }\r\n\r\n this.sweepTimer = setInterval(() => {\r\n const expiredAt = Date.now() - SESSION_IDLE_TIMEOUT_MS;\r\n for (const session of Array.from(this.sessions.values())) {\r\n // A long exec or a large transfer keeps a session busy well past the idle\r\n // window; reaping it there would abort the operation mid-flight.\r\n if (session.isBusy == true) {\r\n continue;\r\n }\r\n\r\n if (session.lastUsedAt.getTime() > expiredAt) {\r\n continue;\r\n }\r\n\r\n const _logger = $logger.child({\r\n context: \"SshSessionManager.sweep\",\r\n session_id: session.sessionId,\r\n idle_ms: Date.now() - session.lastUsedAt.getTime(),\r\n });\r\n _logger.info(\"closing idle ssh session\");\r\n this.close(session.sessionId);\r\n }\r\n }, SESSION_SWEEP_INTERVAL_MS);\r\n\r\n this.sweepTimer.unref();\r\n }\r\n\r\n stopIdleSweeper(): void {\r\n if (this.sweepTimer == null) {\r\n return;\r\n }\r\n\r\n clearInterval(this.sweepTimer);\r\n this.sweepTimer = null;\r\n }\r\n}\r\n","import fs from \"node:fs\";\r\nimport { StringDecoder } from \"node:string_decoder\";\r\n\r\nimport type { FileEntryWithStats, SFTPWrapper, Stats } from \"ssh2\";\r\n\r\nimport { MAX_DIRECTORY_ENTRIES } from \"@/_defs\";\r\nimport { $logger } from \"@/_libs\";\r\nimport type { SshRemoteEntry } from \"@/_types\";\r\nimport { GuardRejectionError, inspectRemotePath } from \"@/modules/guard\";\r\nimport type { RemotePathAccess } from \"@/modules/guard\";\r\n\r\nimport type { SshConnection } from \"./ssh-connection\";\r\n\r\n/**\r\n * Screens a remote path against the connection's own policy.\r\n *\r\n * Enforced here rather than in the tool handlers for the same reason `exec` screens its\r\n * own command: this is the layer every path must pass through, so a new tool cannot\r\n * forget the check.\r\n *\r\n * @throws {GuardRejectionError} If the policy refuses the path.\r\n */\r\nfunction requirePermittedPath(connection: SshConnection, remotePath: string, access: RemotePathAccess): void {\r\n const verdict = inspectRemotePath(remotePath, connection.profile.policy, access);\r\n if (verdict.allowed == false) {\r\n throw new GuardRejectionError(verdict);\r\n }\r\n}\r\n\r\nfunction toEntryType(attrs: Stats): SshRemoteEntry[\"type\"] {\r\n // SFTP attributes are flag-gated; without PERMISSIONS every predicate answers false.\r\n if (typeof attrs.mode !== \"number\") {\r\n return \"other\";\r\n }\r\n\r\n if (attrs.isDirectory() == true) {\r\n return \"directory\";\r\n }\r\n\r\n if (attrs.isSymbolicLink() == true) {\r\n return \"symlink\";\r\n }\r\n\r\n if (attrs.isFile() == true) {\r\n return \"file\";\r\n }\r\n\r\n return \"other\";\r\n}\r\n\r\n/**\r\n * ssh2 `Stats` → the listing shape returned to the model.\r\n *\r\n * Every field is optional on the wire: a server may answer readdir without SIZE or\r\n * ACMODTIME. Reading them unguarded turned one such entry into `new Date(NaN)` and a\r\n * thrown RangeError that failed the whole directory listing.\r\n */\r\nfunction toRemoteEntry(entry: FileEntryWithStats): SshRemoteEntry {\r\n const attrs = entry.attrs;\r\n\r\n let mode = \"?\";\r\n if (typeof attrs.mode === \"number\") {\r\n mode = `0${(attrs.mode & 0o7777).toString(8)}`;\r\n }\r\n\r\n let sizeBytes = 0;\r\n if (typeof attrs.size === \"number\" && Number.isFinite(attrs.size) == true) {\r\n sizeBytes = attrs.size;\r\n }\r\n\r\n let modifiedAt = \"unknown\";\r\n if (typeof attrs.mtime === \"number\" && Number.isFinite(attrs.mtime) == true) {\r\n modifiedAt = new Date(attrs.mtime * 1000).toISOString();\r\n }\r\n\r\n return {\r\n name: entry.filename,\r\n type: toEntryType(attrs),\r\n sizeBytes: sizeBytes,\r\n mode: mode,\r\n modifiedAt: modifiedAt,\r\n };\r\n}\r\n\r\nexport interface RemoteDirectoryListing {\r\n entries: SshRemoteEntry[];\r\n /** Entries the remote host reported, before the cap was applied. */\r\n totalCount: number;\r\n truncated: boolean;\r\n}\r\n\r\n/**\r\n * Lists a remote directory, directories first then files, each group alphabetical.\r\n *\r\n * Capped at `MAX_DIRECTORY_ENTRIES`: this is the one output path with no byte ceiling,\r\n * and a spool directory with half a million files would otherwise return megabytes.\r\n *\r\n * @throws {Error} If the path does not exist or is not readable.\r\n */\r\nexport async function listRemoteDirectory(\r\n connection: SshConnection,\r\n remotePath: string\r\n): Promise<RemoteDirectoryListing> {\r\n requirePermittedPath(connection, remotePath, \"read\");\r\n\r\n return await connection.withSftp(async (sftp: SFTPWrapper) => {\r\n const entries = await new Promise<FileEntryWithStats[]>((resolve, reject) => {\r\n sftp.readdir(remotePath, (error, list) => {\r\n if (error != null) {\r\n reject(error);\r\n return;\r\n }\r\n\r\n resolve(list);\r\n });\r\n });\r\n\r\n const mapped = entries.map(toRemoteEntry);\r\n mapped.sort((left, right) => {\r\n if (left.type === \"directory\" && right.type !== \"directory\") {\r\n return -1;\r\n }\r\n\r\n if (left.type !== \"directory\" && right.type === \"directory\") {\r\n return 1;\r\n }\r\n\r\n return left.name.localeCompare(right.name);\r\n });\r\n\r\n return {\r\n entries: mapped.slice(0, MAX_DIRECTORY_ENTRIES),\r\n totalCount: mapped.length,\r\n truncated: mapped.length > MAX_DIRECTORY_ENTRIES,\r\n };\r\n });\r\n}\r\n\r\nexport interface RemoteFileContent {\r\n content: string;\r\n /** Size the remote host reported; 0 for virtual files that do not declare one. */\r\n sizeBytes: number;\r\n /** Bytes actually read. */\r\n readBytes: number;\r\n truncated: boolean;\r\n}\r\n\r\n/**\r\n * Reads a remote file as UTF-8, stopping after `maxBytes`.\r\n *\r\n * Oversized files return their leading bytes rather than an error — a 2 GB log is exactly\r\n * the case where the first chunk is what you wanted anyway.\r\n *\r\n * A reported size of 0 does not mean empty: every file under /proc and most of /sys says 0\r\n * while holding real content. Those are read unbounded up to `maxBytes` instead of being\r\n * range-limited, which previously returned exactly one byte and called it the whole file.\r\n *\r\n * @throws {Error} If the path is missing, is a directory, or is not readable.\r\n */\r\nexport async function readRemoteFile(\r\n connection: SshConnection,\r\n remotePath: string,\r\n maxBytes: number\r\n): Promise<RemoteFileContent> {\r\n requirePermittedPath(connection, remotePath, \"read\");\r\n\r\n return await connection.withSftp(async (sftp: SFTPWrapper) => {\r\n const attrs = await new Promise<Stats>((resolve, reject) => {\r\n sftp.stat(remotePath, (error, stats) => {\r\n if (error != null) {\r\n reject(error);\r\n return;\r\n }\r\n\r\n resolve(stats);\r\n });\r\n });\r\n\r\n if (attrs.isDirectory() == true) {\r\n throw new Error(`${remotePath} is a directory, use ssh_list_dir instead`);\r\n }\r\n\r\n let declaredSize = 0;\r\n if (typeof attrs.size === \"number\" && Number.isFinite(attrs.size) == true) {\r\n declaredSize = attrs.size;\r\n }\r\n\r\n const isSizeKnown = declaredSize > 0;\r\n\r\n const streamOptions: { start?: number; end?: number } = {};\r\n if (isSizeKnown == true) {\r\n streamOptions.start = 0;\r\n streamOptions.end = Math.min(declaredSize, maxBytes) - 1;\r\n }\r\n\r\n const chunks: Buffer[] = [];\r\n let collectedBytes = 0;\r\n\r\n await new Promise<void>((resolve, reject) => {\r\n const stream = sftp.createReadStream(remotePath, streamOptions);\r\n\r\n stream.on(\"data\", (chunk: Buffer) => {\r\n chunks.push(chunk);\r\n collectedBytes += chunk.length;\r\n\r\n if (collectedBytes >= maxBytes) {\r\n stream.destroy();\r\n resolve();\r\n }\r\n });\r\n stream.on(\"error\", (error: Error) => {\r\n reject(error);\r\n });\r\n stream.on(\"end\", () => {\r\n resolve();\r\n });\r\n stream.on(\"close\", () => {\r\n resolve();\r\n });\r\n });\r\n\r\n const buffer = Buffer.concat(chunks, Math.min(collectedBytes, maxBytes));\r\n\r\n let truncated = false;\r\n if (isSizeKnown == true) {\r\n truncated = declaredSize > buffer.length;\r\n }\r\n else {\r\n truncated = buffer.length >= maxBytes;\r\n }\r\n\r\n // Decoded through StringDecoder for the same reason the exec path does: the cut\r\n // lands at an arbitrary byte offset, and a plain toString() turns the multi-byte\r\n // character straddling it into U+FFFD. `end()` drops the incomplete tail instead.\r\n const decoder = new StringDecoder(\"utf8\");\r\n const content = decoder.write(buffer) + decoder.end();\r\n\r\n return {\r\n content: content,\r\n sizeBytes: declaredSize,\r\n readBytes: buffer.length,\r\n truncated: truncated,\r\n };\r\n });\r\n}\r\n\r\n/**\r\n * Writes UTF-8 text to a remote path, replacing or appending.\r\n *\r\n * @throws {Error} If the parent directory is missing or the account cannot write there.\r\n */\r\nexport async function writeRemoteFile(\r\n connection: SshConnection,\r\n args: { remotePath: string; content: string; append: boolean }\r\n): Promise<number> {\r\n const { remotePath, content, append } = args;\r\n\r\n requirePermittedPath(connection, remotePath, \"write\");\r\n\r\n const payload = Buffer.from(content, \"utf8\");\r\n\r\n await connection.withSftp(async (sftp: SFTPWrapper) => {\r\n await new Promise<void>((resolve, reject) => {\r\n const callback = (error: Error | null | undefined): void => {\r\n if (error != null) {\r\n reject(error);\r\n return;\r\n }\r\n\r\n resolve();\r\n };\r\n\r\n if (append == true) {\r\n sftp.appendFile(remotePath, payload, callback);\r\n return;\r\n }\r\n\r\n sftp.writeFile(remotePath, payload, callback);\r\n });\r\n });\r\n\r\n const _logger = $logger.child({\r\n context: \"writeRemoteFile\",\r\n profile: connection.profile.name,\r\n remote_path: remotePath,\r\n append: append,\r\n });\r\n _logger.info(\"remote file written\", { bytes: payload.length });\r\n\r\n return payload.length;\r\n}\r\n\r\n/**\r\n * Uploads a local file with SFTP.\r\n *\r\n * @returns Bytes transferred.\r\n * @throws {Error} If the local file is missing or the remote path is not writable.\r\n */\r\nexport async function uploadFile(\r\n connection: SshConnection,\r\n localPath: string,\r\n remotePath: string\r\n): Promise<number> {\r\n requirePermittedPath(connection, remotePath, \"write\");\r\n\r\n const localStats = fs.statSync(localPath);\r\n if (localStats.isFile() == false) {\r\n throw new Error(`local path is not a file: ${localPath}`);\r\n }\r\n\r\n await connection.withSftp(async (sftp: SFTPWrapper) => {\r\n await new Promise<void>((resolve, reject) => {\r\n sftp.fastPut(localPath, remotePath, (error) => {\r\n if (error != null) {\r\n reject(error);\r\n return;\r\n }\r\n\r\n resolve();\r\n });\r\n });\r\n });\r\n\r\n const _logger = $logger.child({\r\n context: \"uploadFile\",\r\n profile: connection.profile.name,\r\n local_path: localPath,\r\n remote_path: remotePath,\r\n });\r\n _logger.info(\"file uploaded\", { bytes: localStats.size });\r\n\r\n return localStats.size;\r\n}\r\n\r\n/**\r\n * Downloads a remote file with SFTP.\r\n *\r\n * @returns Bytes written locally.\r\n * @throws {Error} If the remote file is missing or the local path is not writable.\r\n */\r\nexport async function downloadFile(\r\n connection: SshConnection,\r\n remotePath: string,\r\n localPath: string\r\n): Promise<number> {\r\n requirePermittedPath(connection, remotePath, \"read\");\r\n\r\n await connection.withSftp(async (sftp: SFTPWrapper) => {\r\n await new Promise<void>((resolve, reject) => {\r\n sftp.fastGet(remotePath, localPath, (error) => {\r\n if (error != null) {\r\n reject(error);\r\n return;\r\n }\r\n\r\n resolve();\r\n });\r\n });\r\n });\r\n\r\n const localStats = fs.statSync(localPath);\r\n\r\n const _logger = $logger.child({\r\n context: \"downloadFile\",\r\n profile: connection.profile.name,\r\n remote_path: remotePath,\r\n local_path: localPath,\r\n });\r\n _logger.info(\"file downloaded\", { bytes: localStats.size });\r\n\r\n return localStats.size;\r\n}\r\n","import { SINGLE_HOST_ENV } from \"@/_defs\";\r\nimport { formatBytes } from \"@/_libs\";\r\nimport type {\r\n GuardVerdict,\r\n SshExecResult,\r\n SshHostProfile,\r\n SshRemoteEntry,\r\n SshSessionInfo,\r\n} from \"@/_types\";\r\nimport { toHostSummary } from \"@/modules/config\";\r\nimport type { RemoteDirectoryListing } from \"@/modules/ssh\";\r\n\r\nconst ENTRY_TYPE_MARKERS: Record<SshRemoteEntry[\"type\"], string> = {\r\n file: \"-\",\r\n directory: \"d\",\r\n symlink: \"l\",\r\n other: \"?\",\r\n};\r\n\r\n/**\r\n * Renders the configured host for `ssh_list_hosts`, or setup instructions when none is\r\n * registered. Credential fields never appear — only which auth method is in use.\r\n */\r\nexport function formatHostProfile(profile: SshHostProfile | null): string {\r\n if (profile == null) {\r\n return [\r\n \"No SSH host is registered.\",\r\n \"\",\r\n \"Put the connection in this server's MCP entry — one server entry per host:\",\r\n \"\",\r\n ` \"env\": {`,\r\n ` \"${SINGLE_HOST_ENV.HOST}\": \"10.0.0.5\",`,\r\n ` \"${SINGLE_HOST_ENV.USER}\": \"deploy\",`,\r\n ` \"${SINGLE_HOST_ENV.KEY}\": \"~/.ssh/id_ed25519\"`,\r\n \" }\",\r\n \"\",\r\n `${SINGLE_HOST_ENV.HOST} and ${SINGLE_HOST_ENV.USER} are the whole minimum.`,\r\n \"Restart this server after changing it — environment variables are fixed for the life of the process.\",\r\n ].join(\"\\n\");\r\n }\r\n\r\n const summary = toHostSummary(profile);\r\n\r\n let accessLabel = \"writable\";\r\n if (summary.readonly == true) {\r\n accessLabel = \"READ-ONLY\";\r\n }\r\n\r\n let sudoLabel = \"sudo=disabled\";\r\n if (summary.allowSudo == true) {\r\n sudoLabel = \"sudo=enabled\";\r\n }\r\n\r\n const lines: string[] = [];\r\n lines.push(`${summary.name} → ${summary.username}@${summary.host}:${summary.port} auth=${summary.authMethod} ${accessLabel} ${sudoLabel}`);\r\n\r\n if (summary.description != null) {\r\n lines.push(` ${summary.description}`);\r\n }\r\n\r\n const routeParts: string[] = [];\r\n if (summary.viaProxyCommand == true) {\r\n routeParts.push(\"proxy command\");\r\n }\r\n if (summary.jump != null) {\r\n routeParts.push(`jump host ${summary.jump.username}@${summary.jump.host}:${summary.jump.port}`);\r\n }\r\n if (routeParts.length > 0) {\r\n lines.push(` via ${routeParts.join(\" → \")}`);\r\n }\r\n\r\n if (summary.allowedPaths.length > 0) {\r\n // Scope stated explicitly: allowedPaths binds the SFTP tools only, and reading\r\n // it as a filesystem-wide restriction would be a dangerous misunderstanding —\r\n // ssh_exec can still `cat` anything the remote account can read.\r\n lines.push(` file tools limited to: ${summary.allowedPaths.join(\", \")} (does not restrict ssh_exec)`);\r\n }\r\n\r\n if (summary.allowCommands.length > 0) {\r\n lines.push(` commands limited to: ${summary.allowCommands.join(\", \")}`);\r\n }\r\n\r\n return lines.join(\"\\n\");\r\n}\r\n\r\n/**\r\n * Renders a command result: a status line (exit code, duration, cwd), any timeout or\r\n * truncation notice, then the stdout and stderr sections.\r\n *\r\n * @param timeoutMs The limit that applied, quoted in the timeout notice.\r\n */\r\nexport function formatExecResult(result: SshExecResult, timeoutMs: number): string {\r\n const headerParts: string[] = [];\r\n\r\n if (result.exitCode != null) {\r\n headerParts.push(`exit code: ${result.exitCode}`);\r\n }\r\n else {\r\n headerParts.push(\"exit code: none (killed)\");\r\n }\r\n\r\n if (result.signal != null) {\r\n headerParts.push(`signal: ${result.signal}`);\r\n }\r\n\r\n headerParts.push(`${result.durationMs}ms`);\r\n\r\n if (result.cwd != null) {\r\n headerParts.push(`cwd: ${result.cwd}`);\r\n }\r\n\r\n const lines: string[] = [headerParts.join(\" · \")];\r\n\r\n if (result.timedOut == true) {\r\n lines.push(`TIMED OUT after ${timeoutMs}ms — the channel was closed, but the remote process may still be running.`);\r\n }\r\n\r\n if (result.truncated == true) {\r\n lines.push(\"Output was truncated; narrow the command (grep / head / tail) or raise maxOutputCharacters for this profile.\");\r\n }\r\n\r\n lines.push(\"\");\r\n lines.push(\"--- stdout ---\");\r\n if (result.stdout === \"\") {\r\n lines.push(\"(empty)\");\r\n }\r\n else {\r\n lines.push(result.stdout);\r\n }\r\n\r\n if (result.stderr !== \"\") {\r\n lines.push(\"\");\r\n lines.push(\"--- stderr ---\");\r\n lines.push(result.stderr);\r\n }\r\n\r\n return lines.join(\"\\n\");\r\n}\r\n\r\n/** Rejection message: what was blocked, why, and what would make it legal. */\r\nexport function formatGuardRejection(action: string, verdict: GuardVerdict): string {\r\n const lines: string[] = [`Blocked by the host profile's guard policy: ${verdict.reason}`];\r\n\r\n if (verdict.offendingText != null && verdict.offendingText !== action) {\r\n lines.push(`Offending part: ${verdict.offendingText}`);\r\n }\r\n\r\n lines.push(`Requested: ${action}`);\r\n lines.push(\"Nothing was sent to the remote host. Adjust the request, or relax the profile's policy in the config file.\");\r\n\r\n return lines.join(\"\\n\");\r\n}\r\n\r\n/** Renders open sessions with host, working directory, command count and idle time. */\r\nexport function formatSessionList(sessions: SshSessionInfo[]): string {\r\n if (sessions.length === 0) {\r\n return \"No open SSH sessions. Open one with ssh_connect, or pass 'target' to run a one-off command.\";\r\n }\r\n\r\n const lines: string[] = [`${sessions.length} open SSH session(s):`];\r\n const now = Date.now();\r\n\r\n for (const session of sessions) {\r\n const idleSeconds = Math.round((now - new Date(session.lastUsedAt).getTime()) / 1000);\r\n lines.push(\"\");\r\n lines.push(`${session.sessionId} → ${session.username}@${session.host} profile: ${session.profileName}`);\r\n lines.push(` cwd: ${session.cwd} · ${session.execCount} command(s) · idle ${idleSeconds}s · opened ${session.openedAt}`);\r\n }\r\n\r\n return lines.join(\"\\n\");\r\n}\r\n\r\n/**\r\n * Renders a directory listing as one row per entry (type, mode, size, mtime, name),\r\n * noting the true total when the listing was capped.\r\n */\r\nexport function formatDirectoryListing(remotePath: string, listing: RemoteDirectoryListing): string {\r\n if (listing.totalCount === 0) {\r\n return `${remotePath} is empty.`;\r\n }\r\n\r\n let header = `${remotePath} — ${listing.totalCount} entrie(s):`;\r\n if (listing.truncated == true) {\r\n header = `${remotePath} — ${listing.totalCount} entrie(s), showing the first ${listing.entries.length}:`;\r\n }\r\n\r\n const lines: string[] = [header, \"\"];\r\n\r\n for (const entry of listing.entries) {\r\n const marker = ENTRY_TYPE_MARKERS[entry.type];\r\n const size = formatBytes(entry.sizeBytes).padStart(9);\r\n\r\n let name = entry.name;\r\n if (entry.type === \"directory\") {\r\n name = `${entry.name}/`;\r\n }\r\n\r\n lines.push(`${marker} ${entry.mode} ${size} ${entry.modifiedAt} ${name}`);\r\n }\r\n\r\n if (listing.truncated == true) {\r\n lines.push(\"\");\r\n lines.push(\"Listing was capped; narrow the path or filter with ssh_exec if you need the rest.\");\r\n }\r\n\r\n return lines.join(\"\\n\");\r\n}\r\n","import type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\r\n\r\nimport { SINGLE_HOST_ENV } from \"@/_defs\";\r\nimport { $logger } from \"@/_libs\";\r\nimport type { SshHostProfile } from \"@/_types\";\r\nimport { GuardRejectionError } from \"@/modules/guard\";\r\nimport { SshConnection, SshSession, SshSessionManager } from \"@/modules/ssh\";\r\n\r\nimport { formatGuardRejection } from \"./format\";\r\n\r\n/**\r\n * Ceiling on simultaneous one-off connections, which the session manager never sees.\r\n * Matched to the session cap: an MCP client can dispatch a whole tool batch at once, and\r\n * a limit low enough to reject an ordinary batch is worse than the fan-out it prevents.\r\n */\r\nconst MAX_EPHEMERAL_CONNECTIONS = 32;\r\n\r\nlet activeEphemeralCount = 0;\r\n\r\n/** Everything the tool handlers share: the configured host and the live sessions. */\r\nexport interface ToolContext {\r\n /** The host from the `SSH_*` variables, or null when none is registered. */\r\n profile: SshHostProfile | null;\r\n sessions: SshSessionManager;\r\n}\r\n\r\nexport function textResult(text: string): CallToolResult {\r\n return { content: [{ type: \"text\", text: text }] };\r\n}\r\n\r\n/** Marked `isError` so the model sees the call failed rather than reading it as output. */\r\nexport function errorResult(message: string): CallToolResult {\r\n return { content: [{ type: \"text\", text: message }], isError: true };\r\n}\r\n\r\nexport function describeError(error: unknown): string {\r\n if (error instanceof Error) {\r\n return error.message;\r\n }\r\n\r\n return String(error);\r\n}\r\n\r\n/**\r\n * Renders a failure for the model, separating the two cases it must treat differently:\r\n * a guard rejection is a policy decision not worth retrying, anything else is an\r\n * operational failure that might succeed on a second attempt.\r\n */\r\nexport function toToolError(error: unknown, toolName: string, action: string): CallToolResult {\r\n const _logger = $logger.child({ context: toolName });\r\n\r\n if (error instanceof GuardRejectionError) {\r\n _logger.warn(\"rejected by the guard\", { reason: error.verdict.reason, action: action });\r\n\r\n const rendered = formatGuardRejection(action, error.verdict);\r\n return errorResult(rendered);\r\n }\r\n\r\n _logger.error(error, `${toolName} failed`);\r\n return errorResult(`${toolName} failed: ${describeError(error)}`);\r\n}\r\n\r\n/**\r\n * Resolves the optional `session` argument to a host, without connecting.\r\n *\r\n * A session carries the profile it was opened against, so it is preferred when supplied;\r\n * otherwise the one configured host is used. There is no host argument — this server\r\n * fronts exactly the host in its own env block, which is what keeps the model from being\r\n * able to name one.\r\n *\r\n * @throws {Error} If no host is registered, or the session id is not open.\r\n */\r\nexport function resolveProfile(\r\n context: ToolContext,\r\n args: { session?: string }\r\n): { profile: SshHostProfile; session: SshSession | null } {\r\n const hasSession = args.session != null && args.session.trim() !== \"\";\r\n if (hasSession == true) {\r\n const session = context.sessions.require((args.session as string).trim());\r\n return { profile: session.profile, session: session };\r\n }\r\n\r\n if (context.profile == null) {\r\n throw new Error(`no host is registered — set ${SINGLE_HOST_ENV.HOST} / ${SINGLE_HOST_ENV.USER} in this server's env block, then restart it`);\r\n }\r\n\r\n return { profile: context.profile, session: null };\r\n}\r\n\r\n/**\r\n * Runs `operation` against the resolved host.\r\n *\r\n * A session routes through `runExclusive`, which serializes work and keeps the idle\r\n * sweeper away; without one a one-off connection is opened and closed around the call.\r\n * The connection is created but not dialled — `exec` / `withSftp` connect on demand, so a\r\n * command the guard refuses never costs a handshake.\r\n *\r\n * @throws {Error} If the one-off connection ceiling is reached.\r\n */\r\nexport async function withHostConnection<T>(\r\n profile: SshHostProfile,\r\n session: SshSession | null,\r\n operation: (connection: SshConnection) => Promise<T>\r\n): Promise<T> {\r\n if (session != null) {\r\n return await session.runExclusive(operation);\r\n }\r\n\r\n if (activeEphemeralCount >= MAX_EPHEMERAL_CONNECTIONS) {\r\n throw new Error(`too many concurrent one-off connections (${MAX_EPHEMERAL_CONNECTIONS}); open a session with ssh_connect and reuse it`);\r\n }\r\n\r\n activeEphemeralCount += 1;\r\n const connection = new SshConnection(profile);\r\n\r\n try {\r\n return await operation(connection);\r\n }\r\n finally {\r\n // Nested finally: a throwing disconnect must not leak the slot, or the ceiling\r\n // ratchets down until every call fails.\r\n try {\r\n connection.disconnect();\r\n }\r\n finally {\r\n activeEphemeralCount -= 1;\r\n }\r\n }\r\n}\r\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { z } from \"zod\";\r\n\r\nimport { $logger } from \"@/_libs\";\r\nimport type { SshExecResult } from \"@/_types\";\r\n\r\nimport { formatExecResult } from \"./format\";\r\nimport { resolveProfile, textResult, toToolError, withHostConnection } from \"./tool-context\";\r\nimport type { ToolContext } from \"./tool-context\";\r\n\r\nexport function registerExecTools(server: McpServer, context: ToolContext): void {\r\n server.registerTool(\r\n \"ssh_exec\",\r\n {\r\n title: \"Run a command over SSH\",\r\n description: [\r\n \"Run a shell command on the configured SSH host and return stdout, stderr and the exit code.\",\r\n \"Pass 'session' to reuse an open session, which remembers its working directory; omit it and a one-off connection is opened and closed around the call.\",\r\n \"A non-zero exit code is reported as normal output, not as a tool error — read the exit code and stderr to judge the outcome.\",\r\n \"Almost everything is permitted: package installs, service restarts, sudo, interpreters. Only catastrophic operations are refused outright (wiping the filesystem root, formatting a disk, rebooting, flushing the firewall), and the host may add its own limits.\",\r\n ].join(\" \"),\r\n inputSchema: {\r\n command: z.string().min(1).describe(\"Shell command, exactly as it would be typed in a terminal. Chaining with ';', '&&', '||' and pipes is allowed; each part is screened separately.\"),\r\n session: z.string().optional().describe(\"Session id from ssh_connect. Reuses that connection and keeps the working directory across calls.\"),\r\n cwd: z.string().optional().describe(\"Absolute directory to run in, with no shell metacharacters. With a session, the session also moves there.\"),\r\n timeoutMs: z.number().int().positive().optional().describe(\"Lowers the command timeout for this call; it cannot exceed the profile's own limit. The channel is closed when it elapses; the remote process may survive.\"),\r\n },\r\n annotations: {\r\n readOnlyHint: false,\r\n destructiveHint: true,\r\n idempotentHint: false,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ command, session, cwd, timeoutMs }) => {\r\n let _logger = $logger.child({ context: \"ssh_exec\" });\r\n\r\n try {\r\n const { profile, session: openSession } = resolveProfile(context, { session: session });\r\n _logger = _logger.child({ profile: profile.name, session_id: openSession?.sessionId });\r\n\r\n // Clamped, not defaulted: a per-call override that could exceed the profile\r\n // ceiling would make the ceiling advisory.\r\n const requestedTimeoutMs = timeoutMs ?? profile.policy.execTimeoutMs;\r\n const effectiveTimeoutMs = Math.min(requestedTimeoutMs, profile.policy.execTimeoutMs);\r\n\r\n // An empty string is \"not specified\", not \"the root of nowhere\" — passing it\r\n // through would silently reset a session's tracked directory.\r\n let requestedCwd: string | undefined = undefined;\r\n if (cwd != null && cwd.trim() !== \"\") {\r\n requestedCwd = cwd.trim();\r\n }\r\n\r\n _logger.debug(\"running command\", { command: command, timeout_ms: effectiveTimeoutMs });\r\n\r\n let result: SshExecResult;\r\n if (openSession != null) {\r\n result = await openSession.exec(command, { cwd: requestedCwd, timeoutMs: effectiveTimeoutMs });\r\n }\r\n else {\r\n result = await withHostConnection(profile, null, async (connection) => {\r\n return await connection.exec({\r\n command: command,\r\n cwd: requestedCwd,\r\n timeoutMs: effectiveTimeoutMs,\r\n maxOutputCharacters: profile.policy.maxOutputCharacters,\r\n trackCwd: false,\r\n });\r\n });\r\n }\r\n\r\n _logger.info(\"command executed\", {\r\n exit_code: result.exitCode,\r\n duration_ms: result.durationMs,\r\n timed_out: result.timedOut,\r\n });\r\n\r\n const rendered = formatExecResult(result, effectiveTimeoutMs);\r\n return textResult(rendered);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_exec\", command);\r\n }\r\n }\r\n );\r\n}\r\n","import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\n\r\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { z } from \"zod\";\r\n\r\nimport { $logger, expandHomePath, formatBytes } from \"@/_libs\";\r\nimport { GuardRejectionError, inspectLocalPath } from \"@/modules/guard\";\r\nimport {\r\n downloadFile,\r\n listRemoteDirectory,\r\n readRemoteFile,\r\n uploadFile,\r\n writeRemoteFile,\r\n} from \"@/modules/ssh\";\r\n\r\nimport { formatDirectoryListing } from \"./format\";\r\nimport {\r\n resolveProfile,\r\n textResult,\r\n toToolError,\r\n withHostConnection,\r\n} from \"./tool-context\";\r\nimport type { ToolContext } from \"./tool-context\";\r\n\r\nconst SESSION_DESCRIPTION = \"Session id from ssh_connect, whose connection is reused. Omit it to open a one-off connection.\";\r\n\r\n/**\r\n * Screens a path on the machine running this server.\r\n *\r\n * The remote side is guarded inside the SFTP layer, where the policy lives. The local\r\n * side has no policy — it is the operator's own filesystem — so it is checked here, at\r\n * the only layer that knows the path came from a tool argument.\r\n *\r\n * @throws {GuardRejectionError} If the path is protected.\r\n */\r\nfunction requirePermittedLocalPath(localPath: string, access: \"read\" | \"write\"): void {\r\n const verdict = inspectLocalPath(localPath, access);\r\n if (verdict.allowed == false) {\r\n throw new GuardRejectionError(verdict);\r\n }\r\n}\r\n\r\nexport function registerFileTools(server: McpServer, context: ToolContext): void {\r\n server.registerTool(\r\n \"ssh_list_dir\",\r\n {\r\n title: \"List a remote directory\",\r\n description: [\r\n \"List a directory on the configured SSH host over SFTP, with type, permissions, size and mtime.\",\r\n \"Cheaper and more structured than running 'ls -la' through ssh_exec, and it works on a read-only host.\",\r\n ].join(\" \"),\r\n inputSchema: {\r\n path: z.string().min(1).describe(\"Absolute remote directory path. When SSH_ALLOWED_PATHS is set, only paths inside it are accepted.\"),\r\n session: z.string().optional().describe(SESSION_DESCRIPTION),\r\n },\r\n annotations: {\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ path: remotePath, session }) => {\r\n const _logger = $logger.child({ context: \"ssh_list_dir\", remote_path: remotePath });\r\n\r\n try {\r\n const { profile, session: openSession } = resolveProfile(context, { session: session });\r\n\r\n const listing = await withHostConnection(profile, openSession, async (connection) => {\r\n return await listRemoteDirectory(connection, remotePath);\r\n });\r\n\r\n _logger.info(\"remote directory listed\", {\r\n profile: profile.name,\r\n entry_count: listing.totalCount,\r\n truncated: listing.truncated,\r\n });\r\n const rendered = formatDirectoryListing(remotePath, listing);\r\n return textResult(rendered);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_list_dir\", `list ${remotePath}`);\r\n }\r\n }\r\n );\r\n\r\n server.registerTool(\r\n \"ssh_read_file\",\r\n {\r\n title: \"Read a remote file\",\r\n description: [\r\n \"Read a remote text file over SFTP and return its contents.\",\r\n \"Oversized files come back truncated to their leading bytes rather than failing, so pointing this at a large log is safe.\",\r\n \"Files that report size 0 but hold content (/proc, /sys) are read in full up to the ceiling.\",\r\n ].join(\" \"),\r\n inputSchema: {\r\n path: z.string().min(1).describe(\"Absolute remote file path.\"),\r\n session: z.string().optional().describe(SESSION_DESCRIPTION),\r\n maxBytes: z.number().int().positive().optional().describe(\"Lowers the read ceiling for this call; it cannot exceed SSH_MAX_READ_BYTES.\"),\r\n },\r\n annotations: {\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ path: remotePath, session, maxBytes }) => {\r\n const _logger = $logger.child({ context: \"ssh_read_file\", remote_path: remotePath });\r\n\r\n try {\r\n const { profile, session: openSession } = resolveProfile(context, { session: session });\r\n\r\n // Clamped, not defaulted: an override above the profile ceiling would let one\r\n // call buffer hundreds of megabytes and take the server down.\r\n const requestedLimit = maxBytes ?? profile.policy.maxReadFileBytes;\r\n const readLimit = Math.min(requestedLimit, profile.policy.maxReadFileBytes);\r\n\r\n const file = await withHostConnection(profile, openSession, async (connection) => {\r\n return await readRemoteFile(connection, remotePath, readLimit);\r\n });\r\n\r\n let header = `${remotePath} — ${formatBytes(file.readBytes)} read`;\r\n if (file.sizeBytes > 0) {\r\n header = `${remotePath} — ${formatBytes(file.sizeBytes)}`;\r\n }\r\n\r\n if (file.truncated == true) {\r\n header += `, truncated to the first ${formatBytes(file.readBytes)}`;\r\n }\r\n\r\n _logger.info(\"remote file read\", {\r\n profile: profile.name,\r\n size_bytes: file.sizeBytes,\r\n read_bytes: file.readBytes,\r\n truncated: file.truncated,\r\n });\r\n return textResult(`${header}\\n\\n${file.content}`);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_read_file\", `read ${remotePath}`);\r\n }\r\n }\r\n );\r\n\r\n server.registerTool(\r\n \"ssh_write_file\",\r\n {\r\n title: \"Write a remote file\",\r\n description: [\r\n \"Write or append UTF-8 text to a file on the configured SSH host over SFTP.\",\r\n \"Use this instead of 'echo ... > file' through ssh_exec: no shell quoting to get wrong, and it works with multi-line content.\",\r\n \"Refused on a read-only host. The parent directory must already exist.\",\r\n ].join(\" \"),\r\n inputSchema: {\r\n path: z.string().min(1).describe(\"Absolute remote file path. Existing content is replaced unless 'append' is true.\"),\r\n content: z.string().describe(\"UTF-8 text to write.\"),\r\n session: z.string().optional().describe(SESSION_DESCRIPTION),\r\n append: z.boolean().optional().describe(\"Append instead of replacing. Defaults to false.\"),\r\n },\r\n annotations: {\r\n readOnlyHint: false,\r\n destructiveHint: true,\r\n idempotentHint: false,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ path: remotePath, content, session, append }) => {\r\n const _logger = $logger.child({ context: \"ssh_write_file\", remote_path: remotePath });\r\n\r\n try {\r\n const { profile, session: openSession } = resolveProfile(context, { session: session });\r\n const shouldAppend = append ?? false;\r\n\r\n const writtenBytes = await withHostConnection(profile, openSession, async (connection) => {\r\n return await writeRemoteFile(connection, {\r\n remotePath: remotePath,\r\n content: content,\r\n append: shouldAppend,\r\n });\r\n });\r\n\r\n let verb = \"Wrote\";\r\n if (shouldAppend == true) {\r\n verb = \"Appended\";\r\n }\r\n\r\n _logger.info(\"remote file written\", { profile: profile.name, bytes: writtenBytes });\r\n return textResult(`${verb} ${formatBytes(writtenBytes)} to ${remotePath} on ${profile.name}.`);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_write_file\", `write ${remotePath}`);\r\n }\r\n }\r\n );\r\n\r\n server.registerTool(\r\n \"ssh_upload\",\r\n {\r\n title: \"Upload a file over SFTP\",\r\n description: [\r\n \"Upload a local file to the configured SSH host over SFTP.\",\r\n \"Refused on a read-only host, and for local paths holding credentials (SSH keys, cloud credentials).\",\r\n \"The remote parent directory must already exist.\",\r\n ].join(\" \"),\r\n inputSchema: {\r\n localPath: z.string().min(1).describe(\"Local file path on the machine running this MCP server. '~' is expanded.\"),\r\n remotePath: z.string().min(1).describe(\"Absolute destination path on the remote host, including the filename.\"),\r\n session: z.string().optional().describe(SESSION_DESCRIPTION),\r\n },\r\n annotations: {\r\n readOnlyHint: false,\r\n destructiveHint: true,\r\n idempotentHint: true,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ localPath, remotePath, session }) => {\r\n const _logger = $logger.child({ context: \"ssh_upload\", remote_path: remotePath });\r\n\r\n try {\r\n const { profile, session: openSession } = resolveProfile(context, { session: session });\r\n\r\n const resolvedLocalPath = expandHomePath(localPath);\r\n requirePermittedLocalPath(resolvedLocalPath, \"read\");\r\n\r\n if (fs.existsSync(resolvedLocalPath) == false) {\r\n return textResult(`local file not found: ${resolvedLocalPath}`);\r\n }\r\n\r\n const transferredBytes = await withHostConnection(profile, openSession, async (connection) => {\r\n return await uploadFile(connection, resolvedLocalPath, remotePath);\r\n });\r\n\r\n _logger.info(\"file uploaded\", { profile: profile.name, bytes: transferredBytes });\r\n return textResult(`Uploaded ${resolvedLocalPath} → ${remotePath} (${formatBytes(transferredBytes)}) on ${profile.name}.`);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_upload\", `upload to ${remotePath}`);\r\n }\r\n }\r\n );\r\n\r\n server.registerTool(\r\n \"ssh_download\",\r\n {\r\n title: \"Download a file over SFTP\",\r\n description: [\r\n \"Download a file from the configured SSH host to the machine running this MCP server.\",\r\n \"Use this for binaries or files too large to read into the conversation; missing local directories are created.\",\r\n \"The local destination is overwritten, and paths holding credentials are refused.\",\r\n ].join(\" \"),\r\n inputSchema: {\r\n remotePath: z.string().min(1).describe(\"Absolute source path on the remote host.\"),\r\n localPath: z.string().min(1).describe(\"Local destination path, including the filename. '~' is expanded. Overwritten if it exists.\"),\r\n session: z.string().optional().describe(SESSION_DESCRIPTION),\r\n },\r\n annotations: {\r\n readOnlyHint: false,\r\n destructiveHint: true,\r\n idempotentHint: true,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ remotePath, localPath, session }) => {\r\n const _logger = $logger.child({ context: \"ssh_download\", remote_path: remotePath });\r\n\r\n try {\r\n const { profile, session: openSession } = resolveProfile(context, { session: session });\r\n\r\n const resolvedLocalPath = expandHomePath(localPath);\r\n requirePermittedLocalPath(resolvedLocalPath, \"write\");\r\n\r\n fs.mkdirSync(path.dirname(resolvedLocalPath), { recursive: true });\r\n\r\n const transferredBytes = await withHostConnection(profile, openSession, async (connection) => {\r\n return await downloadFile(connection, remotePath, resolvedLocalPath);\r\n });\r\n\r\n _logger.info(\"file downloaded\", { profile: profile.name, bytes: transferredBytes });\r\n return textResult(`Downloaded ${remotePath} → ${resolvedLocalPath} (${formatBytes(transferredBytes)}) from ${profile.name}.`);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_download\", `download ${remotePath}`);\r\n }\r\n }\r\n );\r\n}\r\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\n\r\nimport { $logger } from \"@/_libs\";\r\n\r\nimport { formatHostProfile } from \"./format\";\r\nimport { textResult, toToolError } from \"./tool-context\";\r\nimport type { ToolContext } from \"./tool-context\";\r\n\r\nexport function registerHostTools(server: McpServer, context: ToolContext): void {\r\n server.registerTool(\r\n \"ssh_list_hosts\",\r\n {\r\n title: \"Show the SSH host\",\r\n description: [\r\n \"Show the SSH host this server connects to, with its guard policy.\",\r\n \"Call this first: it is the only reachable host, and its policy decides what the other tools will accept.\",\r\n \"Credentials are never returned — only which auth method the host uses.\",\r\n ].join(\" \"),\r\n inputSchema: {},\r\n annotations: {\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: false,\r\n },\r\n },\r\n async () => {\r\n const _logger = $logger.child({ context: \"ssh_list_hosts\" });\r\n\r\n try {\r\n const listing = formatHostProfile(context.profile);\r\n _logger.debug(\"host profile returned\", { profile: context.profile?.name });\r\n return textResult(listing);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_list_hosts\", \"show the host\");\r\n }\r\n }\r\n );\r\n}\r\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { z } from \"zod\";\r\n\r\nimport { $logger } from \"@/_libs\";\r\n\r\nimport { formatSessionList } from \"./format\";\r\nimport { errorResult, resolveProfile, textResult, toToolError } from \"./tool-context\";\r\nimport type { ToolContext } from \"./tool-context\";\r\n\r\nexport function registerSessionTools(server: McpServer, context: ToolContext): void {\r\n server.registerTool(\r\n \"ssh_connect\",\r\n {\r\n title: \"Open an SSH session\",\r\n description: [\r\n \"Open a reusable SSH session to the configured host and return its session id.\",\r\n \"Prefer this over one-off calls when running several commands: it pays the handshake once and remembers the working directory, so a 'cd' in one ssh_exec still applies to the next.\",\r\n \"Close it with ssh_disconnect when done; idle sessions are reaped automatically, and a session whose connection drops reports that rather than silently reconnecting.\",\r\n ].join(\" \"),\r\n inputSchema: {\r\n cwd: z.string().optional().describe(\"Absolute directory to start in, with no shell metacharacters. Defaults to SSH_CWD, else the login shell's directory.\"),\r\n },\r\n annotations: {\r\n readOnlyHint: false,\r\n destructiveHint: false,\r\n idempotentHint: false,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ cwd }) => {\r\n let _logger = $logger.child({ context: \"ssh_connect\" });\r\n\r\n // Tracked outside the try so a probe that *throws* still tears the session\r\n // down — leaking it would burn one of the 32 slots until the sweeper ran.\r\n let openedSessionId: string | null = null;\r\n\r\n try {\r\n const { profile } = resolveProfile(context, {});\r\n const session = await context.sessions.open(profile);\r\n openedSessionId = session.sessionId;\r\n _logger = _logger.child({ session_id: session.sessionId });\r\n\r\n if (cwd != null && cwd.trim() !== \"\") {\r\n session.cwd = cwd.trim();\r\n }\r\n\r\n // One `pwd` settles the real starting directory (and proves `cwd` exists),\r\n // so the session reports a concrete path instead of \"login default\".\r\n const probe = await session.exec(\"pwd\");\r\n\r\n // `exitCode === null` means the channel closed with no exit status — the\r\n // probe never proved anything, so it counts as a failure rather than a pass.\r\n const probeSucceeded = probe.exitCode === 0 && probe.timedOut == false;\r\n if (probeSucceeded == false) {\r\n context.sessions.close(session.sessionId);\r\n openedSessionId = null;\r\n _logger.warn(\"session probe failed, closing the session\", {\r\n exit_code: probe.exitCode,\r\n timed_out: probe.timedOut,\r\n stderr: probe.stderr,\r\n });\r\n return errorResult(`ssh_connect failed: could not confirm the starting directory.\\n${probe.stderr.trim()}`);\r\n }\r\n\r\n const lines: string[] = [\r\n `Opened session ${session.sessionId} → ${profile.username}@${profile.host}:${profile.port}`,\r\n `cwd: ${session.cwd}`,\r\n ];\r\n\r\n if (profile.policy.readonly == true) {\r\n lines.push(\"This host is READ-ONLY: writes, redirections and uploads will be rejected.\");\r\n }\r\n\r\n lines.push(`Run commands with ssh_exec({ session: \"${session.sessionId}\", command: \"...\" }).`);\r\n\r\n _logger.info(\"session opened via tool\", { cwd: session.cwd });\r\n return textResult(lines.join(\"\\n\"));\r\n }\r\n catch (ex) {\r\n if (openedSessionId != null) {\r\n context.sessions.close(openedSessionId);\r\n }\r\n\r\n return toToolError(ex, \"ssh_connect\", \"connect to the configured host\");\r\n }\r\n }\r\n );\r\n\r\n server.registerTool(\r\n \"ssh_disconnect\",\r\n {\r\n title: \"Close an SSH session\",\r\n description: \"Close an open SSH session and release its connection.\",\r\n inputSchema: {\r\n session: z.string().describe(\"Session id returned by ssh_connect.\"),\r\n },\r\n annotations: {\r\n readOnlyHint: false,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ session }) => {\r\n const _logger = $logger.child({ context: \"ssh_disconnect\", session_id: session });\r\n\r\n try {\r\n const closed = context.sessions.close(session);\r\n if (closed == false) {\r\n return errorResult(`no open session with id '${session}'. Call ssh_list_sessions to see what is open.`);\r\n }\r\n\r\n _logger.info(\"session closed via tool\");\r\n return textResult(`Closed session ${session}.`);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_disconnect\", `close ${session}`);\r\n }\r\n }\r\n );\r\n\r\n server.registerTool(\r\n \"ssh_list_sessions\",\r\n {\r\n title: \"List open SSH sessions\",\r\n description: \"List every open SSH session with its host, working directory and idle time.\",\r\n inputSchema: {},\r\n annotations: {\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: false,\r\n },\r\n },\r\n async () => {\r\n try {\r\n const sessions = context.sessions.list();\r\n const rendered = formatSessionList(sessions);\r\n return textResult(rendered);\r\n }\r\n catch (ex) {\r\n return toToolError(ex, \"ssh_list_sessions\", \"list sessions\");\r\n }\r\n }\r\n );\r\n}\r\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\n\r\nimport { registerExecTools } from \"./exec-tools\";\r\nimport { registerFileTools } from \"./file-tools\";\r\nimport { registerHostTools } from \"./host-tools\";\r\nimport { registerSessionTools } from \"./session-tools\";\r\nimport type { ToolContext } from \"./tool-context\";\r\n\r\nexport { registerHostTools } from \"./host-tools\";\r\nexport { registerSessionTools } from \"./session-tools\";\r\nexport { registerExecTools } from \"./exec-tools\";\r\nexport { registerFileTools } from \"./file-tools\";\r\n\r\nexport {\r\n textResult,\r\n errorResult,\r\n describeError,\r\n toToolError,\r\n resolveProfile,\r\n withHostConnection,\r\n} from \"./tool-context\";\r\nexport type { ToolContext } from \"./tool-context\";\r\n\r\nexport {\r\n formatHostProfile,\r\n formatExecResult,\r\n formatGuardRejection,\r\n formatSessionList,\r\n formatDirectoryListing,\r\n} from \"./format\";\r\n\r\n/** Registers every tool this server exposes, in the order the model should discover them. */\r\nexport function registerAllTools(server: McpServer, context: ToolContext): void {\r\n registerHostTools(server, context);\r\n registerSessionTools(server, context);\r\n registerExecTools(server, context);\r\n registerFileTools(server, context);\r\n}\r\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\n\r\nimport { SERVER_NAME, SERVER_VERSION } from \"@/_defs\";\r\nimport { $logger } from \"@/_libs\";\r\nimport type { SshHostProfile } from \"@/_types\";\r\nimport { loadHostProfile } from \"@/modules/config\";\r\nimport { SshSessionManager } from \"@/modules/ssh\";\r\nimport { registerAllTools } from \"@/modules/tools\";\r\nimport type { ToolContext } from \"@/modules/tools\";\r\n\r\nexport interface SshMcpServerInstance {\r\n server: McpServer;\r\n sessions: SshSessionManager;\r\n /** The host read from the `SSH_*` variables at startup, or null when none is set. */\r\n readonly profile: SshHostProfile | null;\r\n /** Stops the sweeper and closes every open session. Call before the process exits. */\r\n shutdown(): void;\r\n}\r\n\r\n/** Handed to the client on connect, so the model knows the workflow before its first call. */\r\nconst SERVER_INSTRUCTIONS = [\r\n \"This server runs shell commands and transfers files on one remote host over SSH.\",\r\n \"The host is fixed in this server's own environment — there is no host argument, and no other machine is reachable through it.\",\r\n \"\",\r\n \"ssh_list_hosts shows which host this is and what it permits. Call it before assuming anything about access.\",\r\n \"\",\r\n \"Workflow:\",\r\n \"1. One command: ssh_exec with just 'command'.\",\r\n \"2. Several commands in a row: ssh_connect once, then ssh_exec with 'session' (the working directory persists across calls), then ssh_disconnect.\",\r\n \"3. Files: ssh_list_dir, ssh_read_file, ssh_write_file, ssh_upload, ssh_download — prefer these over shell equivalents; no quoting to get wrong.\",\r\n \"\",\r\n \"What is permitted: ordinary administration, without asking — package installs, service restarts, sudo, interpreters, config edits, deployments.\",\r\n \"Refused always, and only these: operations that destroy the machine or cut this connection — wiping the filesystem root or a system directory, deleting a file the host cannot boot without, formatting a disk, writing raw bytes to a block device, rebooting or powering off, flushing the firewall, removing every cron job.\",\r\n \"The host may add its own limits (read-only, an allow-list, deny patterns, path restrictions). ssh_list_hosts shows which apply.\",\r\n \"\",\r\n \"Commands run with no terminal and with stdin closed, so anything that waits for input fails immediately instead of hanging: use non-interactive flags (apt-get -y, sudo -n), 'top -bn1' rather than 'top', and ssh_write_file rather than an editor.\",\r\n \"A job that may outlive its timeout should be started detached (nohup … &, tmux new -d) and polled through its log.\",\r\n \"\",\r\n \"A guard rejection is a policy decision, not a transient failure — do not retry the same command; report it and ask before working around it.\",\r\n \"A non-zero exit code is returned as normal output; read it and stderr to judge what happened.\",\r\n \"You still carry the judgement the guards do not: this is a real host, and an irreversible action deserves a check with the operator first.\",\r\n].join(\"\\n\");\r\n\r\n/**\r\n * Builds the MCP server: reads the host from the environment, wires the session manager,\r\n * registers tools.\r\n *\r\n * Transport is deliberately left to the caller — `cli.ts` attaches stdio, tests can attach\r\n * an in-memory pair.\r\n *\r\n * @throws {Error} If the `SSH_*` variables are malformed (a bad policy value must not\r\n * silently degrade into \"no guard\").\r\n */\r\nexport function createSshMcpServer(): SshMcpServerInstance {\r\n const _logger = $logger.child({ context: \"createSshMcpServer\" });\r\n\r\n const profile = loadHostProfile();\r\n\r\n const sessions = new SshSessionManager();\r\n sessions.startIdleSweeper();\r\n\r\n const server = new McpServer(\r\n {\r\n name: SERVER_NAME,\r\n version: SERVER_VERSION,\r\n },\r\n {\r\n instructions: SERVER_INSTRUCTIONS,\r\n }\r\n );\r\n\r\n const context: ToolContext = {\r\n profile: profile,\r\n sessions: sessions,\r\n };\r\n\r\n registerAllTools(server, context);\r\n\r\n _logger.info(\"mcp server built\", { profile: profile?.name ?? null });\r\n\r\n return {\r\n server: server,\r\n sessions: sessions,\r\n profile: profile,\r\n shutdown(): void {\r\n sessions.stopIdleSweeper();\r\n sessions.closeAll();\r\n },\r\n };\r\n}\r\n"]}