{"title":"通过代理访问 SSH","slug":"ssh-proxy-access","category":"llm-cookbook","summary":"LLM 智能体如何通过 SSH 代理 API（而不是裸 SSH 密钥）安全地在远程机器上执行命令。","audience":["llm"],"tags":["cookbook","ssh","proxy","infrastructure","remote-execution"],"difficulty":"intermediate","updated":"2026-09-26","word_count":133,"read_minutes":1,"llm_context":"作为 LLM 绝不持有裸的 SSH 私钥。使用 SSH 代理 HTTP API：\n1) POST /connect?target=<name> → session_id（持久，可存活数小时）。\n2) POST /<session_id>/exec {command, timeout} → exec_id。\n3) GET /<session_id>/exec/<exec_id> → 完成后取回输出。\n4) 文件上传/下载使用 SFTP 端点，而不是 cat/heredoc。\n目标是预先注册的名称（如 \"vps1\"），不是 IP——代理是凭据唯一的居所。\n","lang":"zh","translated":true,"requested_lang":"zh","content_markdown":"\n# 通过代理访问 SSH\n\n智能体需要在远程机器上执行命令——部署容器、读日志、修配置。把裸的 SSH 私钥交给 LLM 是个坏主意：密钥会落进上下文窗口、日志和转录文本。SSH 代理解决了这个问题：凭据由它掌管，智能体只说 HTTP。\n\n## 模型\n\n```\nLLM ──HTTPS──► SSH 代理 ──SSH──► 目标机器\n                （掌管密钥、\n                  审计日志、\n                  速率限制）\n```\n\n代理通过简单的 REST 面提供持久会话：\n\n| 端点 | 用途 |\n|---|---|\n| `POST /connect?target=vps1` | 打开会话 → `session_id` |\n| `POST /<sid>/exec` `{command, timeout}` | 执行命令（异步）→ `exec_id` |\n| `GET /<sid>/exec/<exec_id>` | 轮询输出 |\n| SFTP 端点 | 文件上传/下载，`ls`，`mkdir`，`rm` |\n\n认证是 Bearer 令牌——和你用的其他 API 相同的模式。\n\n## 执行模式\n\n```bash\n# 1. 连接一次，整个任务复用会话\nSID=$(curl -s -H \"Authorization: Bearer $KEY\" \\\n  \"https://ssh-proxy.example/connect?target=vps1\" | jq -r .session_id)\n\n# 2. 执行命令\nEID=$(curl -s -H \"Authorization: Bearer $KEY\" -H \"Content-Type: application/json\" \\\n  -X POST \"https://ssh-proxy.example/$SID/exec\" \\\n  -d '{\"command\": \"docker ps --format \\\"{{.Names}}\\\"\", \"timeout\": 120}' | jq -r .exec_id)\n\n# 3. 轮询结果\ncurl -s -H \"Authorization: Bearer $KEY\" \"https://ssh-proxy.example/$SID/exec/$EID\"\n```\n\n## 行为准则\n\n1. **复用会话。** 每条命令都连接一次会浪费资源并触发速率限制。保存 `session_id`（如 `/tmp/vps1_sid`），复用前先心跳确认。\n2. **给目标命名，而不是去发现它们。** 目标是预先注册的（`vps1`、`pi`……）。代理是唯一的事实来源——永远不要把 IP 地址硬编码进智能体代码。\n3. **文件优先用 SFTP，而不是 shell 的 cat/heredoc。** 通过 `exec` 转义二进制数据会损坏载荷。使用专用的上传/下载端点。\n4. **给你的命令加引号。** JSON 体会被解析两次（HTTP + shell）。远程命令用单引号包裹，并保持单行。\n5. **假设审计存在。** 每次执行都会落入代理的审计日志——像人类在读一切那样行事（他确实可以）。\n\n## 什么时候不要用 exec\n\n- 读取拥有 API 的结构化状态（Docker、数据库、Synapse 的 Mind API）——永远优先用 API，而不是解析 CLI 输出。\n- 长时间运行的交互式进程。`exec` 是带超时的即发即忘；改用目标机上的 `nohup` + 状态文件。\n- 没有明确指令的任何破坏性操作。代理给你能力——人类给你许可。\n\n## 错误恢复\n\n| 症状 | 处理 |\n|---|---|\n| 连接时 401 | 令牌过期——从 memory/settings 重新读取，绝不从转录文本 |\n| 会话 404 | 会话过期（24 小时上限）——重新连接 |\n| exec 超时 | 提高 `timeout` 或拆分命令；检查目标机负载 |\n| 文件内容损坏 | 你用了 `exec` 而不是 SFTP——改用 SFTP 端点 |\n","content_html":"<h1>通过代理访问 SSH</h1>\n<p>智能体需要在远程机器上执行命令——部署容器、读日志、修配置。把裸的 SSH 私钥交给 LLM 是个坏主意：密钥会落进上下文窗口、日志和转录文本。SSH 代理解决了这个问题：凭据由它掌管，智能体只说 HTTP。</p>\n<h2>模型</h2>\n<pre><code class=\"hljs language-plaintext\">LLM ──HTTPS──► SSH 代理 ──SSH──► 目标机器\n                （掌管密钥、\n                  审计日志、\n                  速率限制）</code></pre><p>代理通过简单的 REST 面提供持久会话：</p>\n<table>\n<thead>\n<tr>\n<th>端点</th>\n<th>用途</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>POST /connect?target=vps1</code></td>\n<td>打开会话 → <code>session_id</code></td>\n</tr>\n<tr>\n<td><code>POST /&lt;sid&gt;/exec</code> <code>{command, timeout}</code></td>\n<td>执行命令（异步）→ <code>exec_id</code></td>\n</tr>\n<tr>\n<td><code>GET /&lt;sid&gt;/exec/&lt;exec_id&gt;</code></td>\n<td>轮询输出</td>\n</tr>\n<tr>\n<td>SFTP 端点</td>\n<td>文件上传/下载，<code>ls</code>，<code>mkdir</code>，<code>rm</code></td>\n</tr>\n</tbody></table>\n<p>认证是 Bearer 令牌——和你用的其他 API 相同的模式。</p>\n<h2>执行模式</h2>\n<pre><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># 1. 连接一次，整个任务复用会话</span>\nSID=$(curl -s -H <span class=\"hljs-string\">&quot;Authorization: Bearer <span class=\"hljs-variable\">$KEY</span>&quot;</span> \\\n  <span class=\"hljs-string\">&quot;https://ssh-proxy.example/connect?target=vps1&quot;</span> | jq -r .session_id)\n\n<span class=\"hljs-comment\"># 2. 执行命令</span>\nEID=$(curl -s -H <span class=\"hljs-string\">&quot;Authorization: Bearer <span class=\"hljs-variable\">$KEY</span>&quot;</span> -H <span class=\"hljs-string\">&quot;Content-Type: application/json&quot;</span> \\\n  -X POST <span class=\"hljs-string\">&quot;https://ssh-proxy.example/<span class=\"hljs-variable\">$SID</span>/exec&quot;</span> \\\n  -d <span class=\"hljs-string\">&#x27;{&quot;command&quot;: &quot;docker ps --format \\&quot;{{.Names}}\\&quot;&quot;, &quot;timeout&quot;: 120}&#x27;</span> | jq -r .exec_id)\n\n<span class=\"hljs-comment\"># 3. 轮询结果</span>\ncurl -s -H <span class=\"hljs-string\">&quot;Authorization: Bearer <span class=\"hljs-variable\">$KEY</span>&quot;</span> <span class=\"hljs-string\">&quot;https://ssh-proxy.example/<span class=\"hljs-variable\">$SID</span>/exec/<span class=\"hljs-variable\">$EID</span>&quot;</span></code></pre><h2>行为准则</h2>\n<ol>\n<li><strong>复用会话。</strong> 每条命令都连接一次会浪费资源并触发速率限制。保存 <code>session_id</code>（如 <code>/tmp/vps1_sid</code>），复用前先心跳确认。</li>\n<li><strong>给目标命名，而不是去发现它们。</strong> 目标是预先注册的（<code>vps1</code>、<code>pi</code>……）。代理是唯一的事实来源——永远不要把 IP 地址硬编码进智能体代码。</li>\n<li><strong>文件优先用 SFTP，而不是 shell 的 cat/heredoc。</strong> 通过 <code>exec</code> 转义二进制数据会损坏载荷。使用专用的上传/下载端点。</li>\n<li><strong>给你的命令加引号。</strong> JSON 体会被解析两次（HTTP + shell）。远程命令用单引号包裹，并保持单行。</li>\n<li><strong>假设审计存在。</strong> 每次执行都会落入代理的审计日志——像人类在读一切那样行事（他确实可以）。</li>\n</ol>\n<h2>什么时候不要用 exec</h2>\n<ul>\n<li>读取拥有 API 的结构化状态（Docker、数据库、Synapse 的 Mind API）——永远优先用 API，而不是解析 CLI 输出。</li>\n<li>长时间运行的交互式进程。<code>exec</code> 是带超时的即发即忘；改用目标机上的 <code>nohup</code> + 状态文件。</li>\n<li>没有明确指令的任何破坏性操作。代理给你能力——人类给你许可。</li>\n</ul>\n<h2>错误恢复</h2>\n<table>\n<thead>\n<tr>\n<th>症状</th>\n<th>处理</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>连接时 401</td>\n<td>令牌过期——从 memory/settings 重新读取，绝不从转录文本</td>\n</tr>\n<tr>\n<td>会话 404</td>\n<td>会话过期（24 小时上限）——重新连接</td>\n</tr>\n<tr>\n<td>exec 超时</td>\n<td>提高 <code>timeout</code> 或拆分命令；检查目标机负载</td>\n</tr>\n<tr>\n<td>文件内容损坏</td>\n<td>你用了 <code>exec</code> 而不是 SFTP——改用 SFTP 端点</td>\n</tr>\n</tbody></table>\n","urls":{"html":"/docs/llm-cookbook/ssh-proxy-access","text":"/docs/llm-cookbook/ssh-proxy-access?format=text","json":"/docs/llm-cookbook/ssh-proxy-access?format=json","llm":"/docs/llm-cookbook/ssh-proxy-access?format=llm"},"translations_available":["en","zh","hi","es","fr","ar","pt","ru","ja","de","it","ko","nl","pl","tr","sv","vi","th","id","uk"],"wordCount":133,"readMinutes":1,"llmContext":"作为 LLM 绝不持有裸的 SSH 私钥。使用 SSH 代理 HTTP API：\n1) POST /connect?target=<name> → session_id（持久，可存活数小时）。\n2) POST /<session_id>/exec {command, timeout} → exec_id。\n3) GET /<session_id>/exec/<exec_id> → 完成后取回输出。\n4) 文件上传/下载使用 SFTP 端点，而不是 cat/heredoc。\n目标是预先注册的名称（如 \"vps1\"），不是 IP——代理是凭据唯一的居所。\n","requestedLang":"zh","contentMarkdown":"\n# 通过代理访问 SSH\n\n智能体需要在远程机器上执行命令——部署容器、读日志、修配置。把裸的 SSH 私钥交给 LLM 是个坏主意：密钥会落进上下文窗口、日志和转录文本。SSH 代理解决了这个问题：凭据由它掌管，智能体只说 HTTP。\n\n## 模型\n\n```\nLLM ──HTTPS──► SSH 代理 ──SSH──► 目标机器\n                （掌管密钥、\n                  审计日志、\n                  速率限制）\n```\n\n代理通过简单的 REST 面提供持久会话：\n\n| 端点 | 用途 |\n|---|---|\n| `POST /connect?target=vps1` | 打开会话 → `session_id` |\n| `POST /<sid>/exec` `{command, timeout}` | 执行命令（异步）→ `exec_id` |\n| `GET /<sid>/exec/<exec_id>` | 轮询输出 |\n| SFTP 端点 | 文件上传/下载，`ls`，`mkdir`，`rm` |\n\n认证是 Bearer 令牌——和你用的其他 API 相同的模式。\n\n## 执行模式\n\n```bash\n# 1. 连接一次，整个任务复用会话\nSID=$(curl -s -H \"Authorization: Bearer $KEY\" \\\n  \"https://ssh-proxy.example/connect?target=vps1\" | jq -r .session_id)\n\n# 2. 执行命令\nEID=$(curl -s -H \"Authorization: Bearer $KEY\" -H \"Content-Type: application/json\" \\\n  -X POST \"https://ssh-proxy.example/$SID/exec\" \\\n  -d '{\"command\": \"docker ps --format \\\"{{.Names}}\\\"\", \"timeout\": 120}' | jq -r .exec_id)\n\n# 3. 轮询结果\ncurl -s -H \"Authorization: Bearer $KEY\" \"https://ssh-proxy.example/$SID/exec/$EID\"\n```\n\n## 行为准则\n\n1. **复用会话。** 每条命令都连接一次会浪费资源并触发速率限制。保存 `session_id`（如 `/tmp/vps1_sid`），复用前先心跳确认。\n2. **给目标命名，而不是去发现它们。** 目标是预先注册的（`vps1`、`pi`……）。代理是唯一的事实来源——永远不要把 IP 地址硬编码进智能体代码。\n3. **文件优先用 SFTP，而不是 shell 的 cat/heredoc。** 通过 `exec` 转义二进制数据会损坏载荷。使用专用的上传/下载端点。\n4. **给你的命令加引号。** JSON 体会被解析两次（HTTP + shell）。远程命令用单引号包裹，并保持单行。\n5. **假设审计存在。** 每次执行都会落入代理的审计日志——像人类在读一切那样行事（他确实可以）。\n\n## 什么时候不要用 exec\n\n- 读取拥有 API 的结构化状态（Docker、数据库、Synapse 的 Mind API）——永远优先用 API，而不是解析 CLI 输出。\n- 长时间运行的交互式进程。`exec` 是带超时的即发即忘；改用目标机上的 `nohup` + 状态文件。\n- 没有明确指令的任何破坏性操作。代理给你能力——人类给你许可。\n\n## 错误恢复\n\n| 症状 | 处理 |\n|---|---|\n| 连接时 401 | 令牌过期——从 memory/settings 重新读取，绝不从转录文本 |\n| 会话 404 | 会话过期（24 小时上限）——重新连接 |\n| exec 超时 | 提高 `timeout` 或拆分命令；检查目标机负载 |\n| 文件内容损坏 | 你用了 `exec` 而不是 SFTP——改用 SFTP 端点 |\n","contentHtml":"<h1>通过代理访问 SSH</h1>\n<p>智能体需要在远程机器上执行命令——部署容器、读日志、修配置。把裸的 SSH 私钥交给 LLM 是个坏主意：密钥会落进上下文窗口、日志和转录文本。SSH 代理解决了这个问题：凭据由它掌管，智能体只说 HTTP。</p>\n<h2>模型</h2>\n<pre><code class=\"hljs language-plaintext\">LLM ──HTTPS──► SSH 代理 ──SSH──► 目标机器\n                （掌管密钥、\n                  审计日志、\n                  速率限制）</code></pre><p>代理通过简单的 REST 面提供持久会话：</p>\n<table>\n<thead>\n<tr>\n<th>端点</th>\n<th>用途</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>POST /connect?target=vps1</code></td>\n<td>打开会话 → <code>session_id</code></td>\n</tr>\n<tr>\n<td><code>POST /&lt;sid&gt;/exec</code> <code>{command, timeout}</code></td>\n<td>执行命令（异步）→ <code>exec_id</code></td>\n</tr>\n<tr>\n<td><code>GET /&lt;sid&gt;/exec/&lt;exec_id&gt;</code></td>\n<td>轮询输出</td>\n</tr>\n<tr>\n<td>SFTP 端点</td>\n<td>文件上传/下载，<code>ls</code>，<code>mkdir</code>，<code>rm</code></td>\n</tr>\n</tbody></table>\n<p>认证是 Bearer 令牌——和你用的其他 API 相同的模式。</p>\n<h2>执行模式</h2>\n<pre><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># 1. 连接一次，整个任务复用会话</span>\nSID=$(curl -s -H <span class=\"hljs-string\">&quot;Authorization: Bearer <span class=\"hljs-variable\">$KEY</span>&quot;</span> \\\n  <span class=\"hljs-string\">&quot;https://ssh-proxy.example/connect?target=vps1&quot;</span> | jq -r .session_id)\n\n<span class=\"hljs-comment\"># 2. 执行命令</span>\nEID=$(curl -s -H <span class=\"hljs-string\">&quot;Authorization: Bearer <span class=\"hljs-variable\">$KEY</span>&quot;</span> -H <span class=\"hljs-string\">&quot;Content-Type: application/json&quot;</span> \\\n  -X POST <span class=\"hljs-string\">&quot;https://ssh-proxy.example/<span class=\"hljs-variable\">$SID</span>/exec&quot;</span> \\\n  -d <span class=\"hljs-string\">&#x27;{&quot;command&quot;: &quot;docker ps --format \\&quot;{{.Names}}\\&quot;&quot;, &quot;timeout&quot;: 120}&#x27;</span> | jq -r .exec_id)\n\n<span class=\"hljs-comment\"># 3. 轮询结果</span>\ncurl -s -H <span class=\"hljs-string\">&quot;Authorization: Bearer <span class=\"hljs-variable\">$KEY</span>&quot;</span> <span class=\"hljs-string\">&quot;https://ssh-proxy.example/<span class=\"hljs-variable\">$SID</span>/exec/<span class=\"hljs-variable\">$EID</span>&quot;</span></code></pre><h2>行为准则</h2>\n<ol>\n<li><strong>复用会话。</strong> 每条命令都连接一次会浪费资源并触发速率限制。保存 <code>session_id</code>（如 <code>/tmp/vps1_sid</code>），复用前先心跳确认。</li>\n<li><strong>给目标命名，而不是去发现它们。</strong> 目标是预先注册的（<code>vps1</code>、<code>pi</code>……）。代理是唯一的事实来源——永远不要把 IP 地址硬编码进智能体代码。</li>\n<li><strong>文件优先用 SFTP，而不是 shell 的 cat/heredoc。</strong> 通过 <code>exec</code> 转义二进制数据会损坏载荷。使用专用的上传/下载端点。</li>\n<li><strong>给你的命令加引号。</strong> JSON 体会被解析两次（HTTP + shell）。远程命令用单引号包裹，并保持单行。</li>\n<li><strong>假设审计存在。</strong> 每次执行都会落入代理的审计日志——像人类在读一切那样行事（他确实可以）。</li>\n</ol>\n<h2>什么时候不要用 exec</h2>\n<ul>\n<li>读取拥有 API 的结构化状态（Docker、数据库、Synapse 的 Mind API）——永远优先用 API，而不是解析 CLI 输出。</li>\n<li>长时间运行的交互式进程。<code>exec</code> 是带超时的即发即忘；改用目标机上的 <code>nohup</code> + 状态文件。</li>\n<li>没有明确指令的任何破坏性操作。代理给你能力——人类给你许可。</li>\n</ul>\n<h2>错误恢复</h2>\n<table>\n<thead>\n<tr>\n<th>症状</th>\n<th>处理</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>连接时 401</td>\n<td>令牌过期——从 memory/settings 重新读取，绝不从转录文本</td>\n</tr>\n<tr>\n<td>会话 404</td>\n<td>会话过期（24 小时上限）——重新连接</td>\n</tr>\n<tr>\n<td>exec 超时</td>\n<td>提高 <code>timeout</code> 或拆分命令；检查目标机负载</td>\n</tr>\n<tr>\n<td>文件内容损坏</td>\n<td>你用了 <code>exec</code> 而不是 SFTP——改用 SFTP 端点</td>\n</tr>\n</tbody></table>\n","translationsAvailable":["en","zh","hi","es","fr","ar","pt","ru","ja","de","it","ko","nl","pl","tr","sv","vi","th","id","uk"]}