You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows环境下NodeJS搭建SSH CLI端点遇PTY及Shell请求失败问题

解决ssh2库Windows环境下Shell连接报错:PTY allocation request failed on channel 0

你遇到的问题根源在于:原代码只实现了exec命令的处理逻辑,但SSH客户端启动Shell时会先发起PTY(伪终端)分配请求,随后再请求启动Shell,原代码完全没有处理这两个核心事件。再加上Windows没有Linux原生的伪终端环境,直接导致连接失败并抛出错误:

PTY allocation request failed on channel 0
shell request failed on channel 0

以下是适配Windows环境的修复方案,核心是添加PTY请求处理和Shell交互逻辑:

const { timingSafeEqual } = require('crypto');
const { readFileSync } = require('fs');
const { inspect } = require('util');
const { spawn } = require('child_process'); // 新增子进程模块,用于启动Windows终端

const { utils: { parseKey }, Server } = require('ssh2');

const allowedUser = Buffer.from('foo');
const allowedPassword = Buffer.from('bar');
const allowedPubKey = parseKey(readFileSync('foo.pub'));

function checkValue(input, allowed) {
  const autoReject = (input.length !== allowed.length);
  if (autoReject) {
    allowed = input;
  }
  const isMatch = timingSafeEqual(input, allowed);
  return (!autoReject && isMatch);
}

new Server({
  hostKeys: [readFileSync('host.key')]
}, (client) => {
  console.log('Client connected!');

  client.on('authentication', (ctx) => {
    let allowed = true;
    if (!checkValue(Buffer.from(ctx.username), allowedUser))
      allowed = false;
  
    switch (ctx.method) {
      case 'password':
        if (!checkValue(Buffer.from(ctx.password), allowedPassword))
          return ctx.reject();
        break;
      case 'publickey':
        if (ctx.key.algo !== allowedPubKey.type
            || !checkValue(ctx.key.data, allowedPubKey.getPublicSSH())
            || (ctx.signature && allowedPubKey.verify(ctx.blob, ctx.signature) !== true)) {
          return ctx.reject();
        }
        break;
      default:
        return ctx.reject();
    }
  
    if (allowed)
      ctx.accept();
    else
      ctx.reject();
  }).on('ready', () => {
    console.log('Client authenticated!');
  
    client.on('session', (accept, reject) => {
      const session = accept();
      
      // 处理PTY请求:必须接受该请求,否则客户端Shell请求会直接失败
      session.on('pty', (accept, reject, info) => {
        console.log(`PTY requested: ${inspect(info)}`);
        accept();
      });

      // 处理Shell请求:启动Windows终端程序(powershell或cmd)
      session.on('shell', (accept, reject) => {
        const stream = accept();
        // 启动powershell,也可替换为cmd.exe
        const shellProcess = spawn('powershell.exe', [], {
          stdio: ['pipe', 'pipe', 'pipe'],
          windowsHide: true
        });

        // 将SSH流与终端进程的输入输出绑定,实现交互
        shellProcess.stdout.pipe(stream);
        shellProcess.stderr.pipe(stream);
        stream.pipe(shellProcess.stdin);

        // 处理终端进程退出事件
        shellProcess.on('exit', (code) => {
          console.log(`Shell process exited with code ${code}`);
          stream.exit(code);
          stream.end();
        });

        // SSH流关闭时终止终端进程
        stream.on('close', () => {
          shellProcess.kill();
        });
      });

      // 保留原有的exec命令处理逻辑
      session.once('exec', (accept, reject, info) => {
        console.log('Client wants to execute: ' + inspect(info.command));
        const stream = accept();
        stream.stderr.write('Oh no, the dreaded errors!\n');
        stream.write('Just kidding about the errors!\n');
        stream.exit(0);
        stream.end();
      });
    });
  }).on('close', () => {
    console.log('Client disconnected');
  });
}).listen(0, '127.0.0.1', function() {
  console.log('Listening on port ' + this.address().port);
});

关键修复点说明

  • PTY请求处理:SSH客户端打开Shell前必然会发送pty请求,必须调用accept()接受该请求,否则后续的Shell请求会直接失败,这是你遇到第一个错误的核心原因。
  • Windows终端适配:Windows没有原生POSIX Shell,通过spawn启动powershell.exe或cmd.exe作为交互终端,将SSH流与进程的标准输入输出绑定,实现命令交互。
  • 流生命周期管理:绑定SSH流与终端进程的同时,处理双方的关闭/退出事件,避免出现僵尸进程或资源泄漏。

内容的提问来源于stack exchange,提问作者Youssef El Idrissi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 00:45:39