@zicolasjac-ai/remote-readonly-ssh 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -75,7 +75,7 @@ rrs <tool> [arg] [--host <ip>] [--config <file>] [--json]
75
75
 
76
76
  | 键 | 说明 |
77
77
  |-----|------|
78
- | `auth_profiles` | **认证组**(可多个):`{组名: {host, port, username, auth}}`。`auth.mode` 支持 `key`(`private_key_path` 必填,可选 `private_key_passphrase`)与 `password`(`password` 必填)。`list-tools` 只显示模式与主机,不显示任何凭据 |
78
+ | `auth_profiles` | **认证组**(可多个):`{组名: {host, port, username, auth, direct?}}`。`auth.mode` 支持 `key`(`private_key_path` 必填,可选 `private_key_passphrase`)与 `password`(`password` 必填)。`direct: true` 表示**直连裸机**(跳过堡垒机菜单,适合无堡垒机的服务器;命令只读校验与堡垒机模式完全一致)。`list-tools` 只显示模式与主机,不显示任何凭据 |
79
79
  | `target_hosts` | 目标主机白名单 `{ip: {profile, keyword?}}`:`profile` 引用认证组名,`keyword` 为堡垒机资产搜索关键字(缺省用 IP)。**多台主机可共用同一认证组(N:1)** |
80
80
  | `audit` | `enabled`(默认 true,关闭则不留痕)与 `include_output`(默认 false,true 时把命令输出摘要写入审计日志) |
81
81
  | `allowed_version_bins` | 允许执行 `-v/-V` 的二进制白名单 |
@@ -137,6 +137,7 @@ npm test # 运行单元测试(node --test)
137
137
  - **主机白名单**:未登记 IP 一律拒绝。
138
138
  - **主机密钥 TOFU**:堡垒机公钥指纹首见记录(`~/.rrs/known_hosts.json`)、每次连接比对,变化即拒绝(防中间人窃取凭据/会话);连接错误消息中的凭据类内容自动脱敏。
139
139
  - **凭据不落命令行**:认证走 ssh2 库内存参数,密码/私钥不出现在进程参数与环境变量中;审计日志只记命令,不含凭据。
140
+ - **直连模式同样受只读强制**:`direct: true` 仅跳过堡垒机菜单选资产环节,命令前缀白名单 / 禁止模式 / 路径黑名单 / 主机白名单 / 审计 / TOFU 对直连主机完全等效——防护不依赖堡垒机这道外部闸门。
140
141
  - **无写原语**:工具集中不存在任何写操作路径;写操作由人工执行,本工具只读。
141
142
 
142
143
  **已知边界**(设计取舍):
package/bin/rrs.js CHANGED
@@ -184,7 +184,7 @@ function listTools(conf) {
184
184
  };
185
185
  for (const [k, v] of Object.entries(tools)) console.log(` ${k.padEnd(20)} ${v}`);
186
186
  const profiles = conf.auth_profiles || {};
187
- console.log(`\n认证组: ${Object.entries(profiles).map(([id, p]) => `${id}(${p.auth.mode}@${p.host})`).join(", ") || "(无)"}`);
187
+ console.log(`\n认证组: ${Object.entries(profiles).map(([id, p]) => `${id}(${p.auth.mode}@${p.host}${p.direct ? ",direct" : ""})`).join(", ") || "(无)"}`);
188
188
  console.log(`允许主机: ${Object.keys(conf.target_hosts || {}).join(", ")}`);
189
189
  console.log(`允许查版本: ${(conf.allowed_version_bins || []).join(", ")}`);
190
190
  console.log(`允许读路径前缀: ${(conf.allowed_path_prefixes || []).join(", ")}`);
@@ -18,6 +18,16 @@
18
18
  "mode": "password",
19
19
  "password": "YOUR_PASSWORD"
20
20
  }
21
+ },
22
+ "profile_direct": {
23
+ "host": "YOUR_DIRECT_SERVER_IP",
24
+ "port": 22,
25
+ "username": "YOUR_USERNAME_3",
26
+ "direct": true,
27
+ "auth": {
28
+ "mode": "password",
29
+ "password": "YOUR_PASSWORD_2"
30
+ }
21
31
  }
22
32
  },
23
33
  "target_hosts": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zicolasjac-ai/remote-readonly-ssh",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "只读 SSH 运维 CLI 与 Agent Skill:通过 JumpServer 堡垒机白名单强制执行只读查询;认证组支持 SSH 私钥/密码两种模式与按 IP 分组;附 SKILL.md 让 opencode / Claude Code / Cursor 等 AI 助手自动发现并正确调用。",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -32,7 +32,8 @@ metadata:
32
32
  ```bash
33
33
  npm install -g @zicolasjac-ai/remote-readonly-ssh
34
34
  rrs init # 生成配置模板 ~/.rrs/config.json
35
- # 编辑配置: auth_profiles(认证组,key/password 两种模式,可多个)/ target_hosts(IP → 认证组+搜索关键字,多 IP 可共用一组)
35
+ # 编辑配置: auth_profiles(认证组,key/password 两种模式,可多个;direct:true 表示跳过堡垒机菜单直连裸机)
36
+ # target_hosts(IP → 认证组+搜索关键字,多 IP 可共用一组)
36
37
  rrs setup skills # 把本 skill 安装到 opencode / claude / agents 等目录
37
38
  ```
38
39
 
package/src/config.js CHANGED
@@ -60,6 +60,9 @@ function validateConfig(conf) {
60
60
  }
61
61
  if (!p.host || typeof p.host !== "string") errors.push(`认证组 [${id}] 缺少 host`);
62
62
  if (!p.username || typeof p.username !== "string") errors.push(`认证组 [${id}] 缺少 username`);
63
+ if (p.direct !== undefined && typeof p.direct !== "boolean") {
64
+ errors.push(`认证组 [${id}] direct 必须是布尔值(true=跳过堡垒机菜单直连)`);
65
+ }
63
66
  if (p.port !== undefined && (!Number.isInteger(p.port) || p.port < 1 || p.port > 65535)) {
64
67
  errors.push(`认证组 [${id}] port 必须是 1-65535 的整数,当前: ${p.port}`);
65
68
  }
package/src/jumpshell.js CHANGED
@@ -123,6 +123,8 @@ class JumpShell {
123
123
  this.entry = resolveHostEntry(conf, host);
124
124
  this.profile = resolveProfile(conf, host);
125
125
  this.searchKeyword = this.entry.keyword || host;
126
+ // direct=true:跳过堡垒机菜单直连裸机(无 Opt> 流程);命令只读校验链路不变
127
+ this.direct = this.profile.direct === true;
126
128
  this.knownHostsDir = opts.knownHostsDir; // 测试注入用;缺省 ~/.rrs
127
129
  this.conn = null;
128
130
  this.stream = null;
@@ -250,6 +252,12 @@ class JumpShell {
250
252
 
251
253
  async login(timeoutSecs = 30) {
252
254
  const deadline = Date.now() + timeoutSecs * 1000;
255
+ if (this.direct) {
256
+ // 直连模式:连上即 shell,只等 prompt;命令仍走白名单校验(whitelist.js)
257
+ if (!(await this.waitPrompt(deadline))) throw new Error("未能进入 shell(直连)");
258
+ await sleep(500);
259
+ return;
260
+ }
253
261
  if (!(await this.waitFor("Opt>", deadline))) throw new Error("跳板机菜单未出现");
254
262
  await sleep(1000);
255
263
  this.send("");