@hyzyn/dsh-tty 0.20.2 → 0.22.0-rc.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.d.ts CHANGED
@@ -112,6 +112,14 @@ export interface Config {
112
112
  sftpLimits?: Partial<SftpLimits>;
113
113
  /** 服务器状态条(0.17.0):是否采集并推送会话资源指标(CPU/内存/磁盘/uptime/TCP/网速/温度)。默认开。 */
114
114
  statsEnabled?: boolean;
115
+ /**
116
+ * 允许 ProxyCommand(本机命令执行):**默认关**。
117
+ *
118
+ * 本插件唯一「由设置字段驱动本机任意命令执行」的开关,与跳板机(只连一跳 TCP)不同档:
119
+ * 关着时携带 `proxyCommand` 的连接**明确失败**(不退回直连),`~/.ssh/config` 导入也
120
+ * 永不自动带入该字段——要用的条目必须自己开开关再手填。
121
+ */
122
+ allowProxyCommand?: boolean;
115
123
  /** 内部状态:SSH 持久会话名(远程 tmux 托管,本机 socket 清单看不到,随 settings 留存供新窗口恢复确认)。 */
116
124
  persistSessions?: Array<{
117
125
  tmuxName: string;
@@ -192,6 +200,12 @@ interface TtySession {
192
200
  lastOutputAt: number;
193
201
  /** 最近一次 PTY 输入(input 帧 / tty_send)的时间戳:tty_capture{last} 的在途判据之一。 */
194
202
  lastInputAt: number;
203
+ /** 累计写入环形缓冲的原始输出字符数(单调,D72):buffer 起点 = `outputSeq - buffer.length`。 */
204
+ outputSeq: number;
205
+ /** agent 已读水位线(绝对字符计数,D72);-1 = 还没被 agent 工具碰过(首次触达时落在当下)。 */
206
+ readSeq: number;
207
+ /** 水位线最近一次推进的时刻(D72):`lastCommand.endedAt > readMarkAt` = 这条命令的输出还没被读过。 */
208
+ readMarkAt: number;
195
209
  /** 输出环形缓冲(尾部 256KB,供 tty_capture 与断线重连回放)。 */
196
210
  buffer: string;
197
211
  /** utf8 分帧兜底:跨 chunk 的多字节序列由 StringDecoder 缓存补齐。 */
@@ -259,6 +273,8 @@ declare class LiveConfig {
259
273
  persistSessions: string[];
260
274
  /** SFTP 传输限制(客户端浏览器侧执行)。 */
261
275
  sftpLimits: Required<SftpLimits>;
276
+ /** 允许 ProxyCommand(本机命令执行):默认关(见 Config.allowProxyCommand)。 */
277
+ allowProxyCommand: boolean;
262
278
  constructor(init: {
263
279
  shell: string;
264
280
  term: string;
@@ -273,6 +289,7 @@ declare class LiveConfig {
273
289
  endOnPageClose?: boolean;
274
290
  statsEnabled?: boolean;
275
291
  sftpLimits?: Partial<SftpLimits>;
292
+ allowProxyCommand?: boolean;
276
293
  persistSessions?: string[];
277
294
  });
278
295
  /** 合并部分更新;空字符串/undefined 保持原值;sshHosts/hostKeys/tunnels 传数组即整体替换。 */
@@ -290,6 +307,7 @@ declare class LiveConfig {
290
307
  endOnPageClose: boolean;
291
308
  statsEnabled: boolean;
292
309
  sftpLimits?: Partial<SftpLimits>;
310
+ allowProxyCommand: boolean;
293
311
  persistSessions: string[];
294
312
  }>): void;
295
313
  findSshHost(name: string): SshHostEntry | undefined;
@@ -549,6 +567,8 @@ export declare class TtyServer {
549
567
  private sendStats;
550
568
  /** registerUpgrade 的 handler(loopback 围栏 + ws 握手)。 */
551
569
  handleUpgrade(req: ReqLike, socket: SocketLike, head: Buffer): void;
570
+ /** 围栏放行之后的实际握手(与上面的异步分支共用)。 */
571
+ private finishUpgrade;
552
572
  private onConnection;
553
573
  /**
554
574
  * 解析帧里的 sid。返回:
@@ -691,6 +711,37 @@ export declare class TtyServer {
691
711
  export declare function handleCredentialRefsRoute(req: ReqLike, res: ResLike): Promise<void>;
692
712
  /** 导出仅供单测(test/credential-refs.test.ts):只验键名解析,不取值。 */
693
713
  export declare function readCredentialRefNames(): string[];
714
+ /**
715
+ * 数据路由的统一闸门(**导出仅供单测**):回环围栏(加固档)+ 变更端点的同源证明。
716
+ * 十二处路由此前各抄一遍 403 样板,`mutation` 这条判据一加就会各写各的——收敛成一处后
717
+ * 「拒绝分支」只有一份,负例也只测这一份。
718
+ *
719
+ * 哪些是变更端点(`mutation: true`)——**判据是「这次请求会不会改状态」**:
720
+ * - `POST /probe`:真的拨号,且连接簿条目测试会当场 TOFU 记录主机指纹;
721
+ * - `POST /sftp/{mkdir,rename,remove,upload}`、`POST /local-fs/{mkdir,rename,remove,transfer}`:
722
+ * 写远端 / 写本机 / 起传输任务;
723
+ * - `POST /config`**刻意不在其列**(与 docker 同口径):它是插件被禁用后唯一的恢复入口
724
+ * ——卡片靠它渲染、也是重新启用的唯一 UI 入口;跨站 POST 已由上面那条围栏的
725
+ * `sec-fetch-site: cross-site` 与 Origin 比对拦住。
726
+ * 只读端点(GET 全家 + `sftp /list` `/download`、`local-fs /list`)维持 loopback-only:
727
+ * 它们读的是用户自己主动要的东西,读路由加证明只会把旧 Safari / 裸 curl 一起挡在门外。
728
+ *
729
+ * **WS upgrade 不走这里**(那是 socket 握手,不是 req/res 路由):见 `handleUpgrade`
730
+ * 用的 `isLoopbackRequestStrict`——它自带「Origin 有则必须同源」这条判据,等价于给
731
+ * 升级请求也上了证明,但**刻意不放开 Cookie 例外**:长连的生命周期比一次 POST 长得多。
732
+ */
733
+ export declare function gateRoute(req: ReqLike, res: ResLike, options?: {
734
+ mutation?: boolean;
735
+ }): Promise<boolean>;
736
+ /**
737
+ * 这个前缀路由的子路径是否**会改状态**(导出仅供单测)。
738
+ *
739
+ * 为什么单独抽出来:`/list` 与 `/download` 也是 POST(凭证走 body、不进 URL),但它们只是
740
+ * 读——如果把「POST 就要求证明」一刀切下去,读路由会连带把旧 Safari / 裸 curl 挡在门外,
741
+ * 而它们本来就没有可被跨站利用的副作用。判据是**动作**不是**方法**,所以名单必须显式。
742
+ * 没见过的子路径一律 false(几步之后就是 404,不给它额外的信息量)。
743
+ */
744
+ export declare function isMutationSubroute(prefix: string, sub: string): boolean;
694
745
  interface ResLike {
695
746
  writeHead(status: number, headers?: Record<string, string>): void;
696
747
  /** 二进制响应(SFTP 下载)也走 end;Node 的 ServerResponse 原生接受 Uint8Array。 */