{"title":"Acceso SSH vía proxy","slug":"ssh-proxy-access","category":"llm-cookbook","summary":"Cómo ejecuta comandos un agente LLM en máquinas remotas de forma segura mediante una API de proxy SSH en lugar de claves SSH crudas.","audience":["llm"],"tags":["cookbook","ssh","proxy","infrastructure","remote-execution"],"difficulty":"intermediate","updated":"2026-09-26","word_count":403,"read_minutes":2,"llm_context":"NUNCA guardes claves privadas SSH crudas como LLM. Usa una API HTTP de proxy SSH:\n1) POST /connect?target=<name> → session_id (persistente, sobrevive horas).\n2) POST /<session_id>/exec {command, timeout} → exec_id.\n3) GET /<session_id>/exec/<exec_id> → salida cuando termine.\n4) Usa los endpoints SFTP para subir/descargar archivos en lugar de cat/heredoc.\nLos targets son nombres pre-registrados (p. ej. \"vps1\"), no IPs — el proxy es\nel único lugar donde viven las credenciales.\n","lang":"es","translated":true,"requested_lang":"es","content_markdown":"\n# Acceso SSH vía proxy\n\nLos agentes necesitan ejecutar comandos en máquinas remotas — desplegar un contenedor, leer un log, arreglar una configuración. Entregar claves privadas SSH crudas a un LLM es una mala idea: las claves acaban en ventanas de contexto, logs y transcripciones. Un proxy SSH lo resuelve: él posee las credenciales, el agente solo habla HTTP.\n\n## El modelo\n\n```\nLLM ──HTTPS──► Proxy SSH ──SSH──► máquinas objetivo\n                (posee las claves,\n                 log de auditoría,\n                 límites de tasa)\n```\n\nEl proxy expone sesiones persistentes con una superficie REST sencilla:\n\n| Endpoint | Propósito |\n|---|---|\n| `POST /connect?target=vps1` | abrir una sesión → `session_id` |\n| `POST /<sid>/exec` `{command, timeout}` | ejecutar un comando (async) → `exec_id` |\n| `GET /<sid>/exec/<exec_id>` | consultar la salida |\n| Endpoints SFTP | subida/descarga de archivos, `ls`, `mkdir`, `rm` |\n\nLa autenticación es un token Bearer — el mismo patrón que todas las demás APIs que usas.\n\n## El patrón de ejecución\n\n```bash\n# 1. conecta una vez, reutiliza la sesión para toda la tarea\nSID=$(curl -s -H \"Authorization: Bearer $KEY\" \\\n  \"https://ssh-proxy.example/connect?target=vps1\" | jq -r .session_id)\n\n# 2. ejecuta un comando\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. consulta el resultado\ncurl -s -H \"Authorization: Bearer $KEY\" \"https://ssh-proxy.example/$SID/exec/$EID\"\n```\n\n## Reglas de uso\n\n1. **Reutiliza sesiones.** Conectar por comando desperdicia recursos y dispara límites de tasa. Guarda el `session_id` (p. ej. `/tmp/vps1_sid`) y hazle heartbeat antes de reutilizarlo.\n2. **Nombra los targets, no los descubras.** Los targets están pre-registrados (`vps1`, `pi`, …). El proxy es la única fuente de verdad — nunca hardcodees IPs en código de agente.\n3. **Prefiere SFTP a cat/heredoc de shell para archivos.** Escapar datos binarios a través de `exec` corrompe los payloads. Usa los endpoints dedicados de subida/descarga.\n4. **Cita tus comandos.** El cuerpo JSON se parsea dos veces (HTTP + shell). Envuelve los comandos remotos en comillas simples y mantenlos en una sola línea.\n5. **Asume auditoría.** Cada exec queda en el log de auditoría del proxy — compórtate como si el humano leyera todo (puede hacerlo).\n\n## Cuándo NO usar exec\n\n- Leer estado estructurado que tiene una API (Docker, bases de datos, la Mind API de Synapse) — siempre prefiere la API a parsear salida de CLI.\n- Procesos interactivos de larga duración. `exec` es fire-and-forget con timeout; usa `nohup` + un archivo de estado en el objetivo.\n- Cualquier cosa destructiva sin instrucción explícita. El proxy te da el poder — el humano te da el permiso.\n\n## Recuperación de errores\n\n| Síntoma | Solución |\n|---|---|\n| 401 al conectar | token expirado — relee de memoria/settings, nunca de una transcripción |\n| sesión 404 | sesión expirada (tope 24h) — reconecta |\n| timeout de exec | sube `timeout` o divide el comando; revisa la carga del objetivo |\n| contenido de archivo corrupto | usaste `exec` en vez de SFTP — cambia a los endpoints SFTP |\n","content_html":"<h1>Acceso SSH vía proxy</h1>\n<p>Los agentes necesitan ejecutar comandos en máquinas remotas — desplegar un contenedor, leer un log, arreglar una configuración. Entregar claves privadas SSH crudas a un LLM es una mala idea: las claves acaban en ventanas de contexto, logs y transcripciones. Un proxy SSH lo resuelve: él posee las credenciales, el agente solo habla HTTP.</p>\n<h2>El modelo</h2>\n<pre><code class=\"hljs language-plaintext\">LLM ──HTTPS──► Proxy SSH ──SSH──► máquinas objetivo\n                (posee las claves,\n                 log de auditoría,\n                 límites de tasa)</code></pre><p>El proxy expone sesiones persistentes con una superficie REST sencilla:</p>\n<table>\n<thead>\n<tr>\n<th>Endpoint</th>\n<th>Propósito</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>POST /connect?target=vps1</code></td>\n<td>abrir una sesión → <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>ejecutar un comando (async) → <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>consultar la salida</td>\n</tr>\n<tr>\n<td>Endpoints SFTP</td>\n<td>subida/descarga de archivos, <code>ls</code>, <code>mkdir</code>, <code>rm</code></td>\n</tr>\n</tbody></table>\n<p>La autenticación es un token Bearer — el mismo patrón que todas las demás APIs que usas.</p>\n<h2>El patrón de ejecución</h2>\n<pre><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># 1. conecta una vez, reutiliza la sesión para toda la tarea</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. ejecuta un comando</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. consulta el resultado</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>Reglas de uso</h2>\n<ol>\n<li><strong>Reutiliza sesiones.</strong> Conectar por comando desperdicia recursos y dispara límites de tasa. Guarda el <code>session_id</code> (p. ej. <code>/tmp/vps1_sid</code>) y hazle heartbeat antes de reutilizarlo.</li>\n<li><strong>Nombra los targets, no los descubras.</strong> Los targets están pre-registrados (<code>vps1</code>, <code>pi</code>, …). El proxy es la única fuente de verdad — nunca hardcodees IPs en código de agente.</li>\n<li><strong>Prefiere SFTP a cat/heredoc de shell para archivos.</strong> Escapar datos binarios a través de <code>exec</code> corrompe los payloads. Usa los endpoints dedicados de subida/descarga.</li>\n<li><strong>Cita tus comandos.</strong> El cuerpo JSON se parsea dos veces (HTTP + shell). Envuelve los comandos remotos en comillas simples y mantenlos en una sola línea.</li>\n<li><strong>Asume auditoría.</strong> Cada exec queda en el log de auditoría del proxy — compórtate como si el humano leyera todo (puede hacerlo).</li>\n</ol>\n<h2>Cuándo NO usar exec</h2>\n<ul>\n<li>Leer estado estructurado que tiene una API (Docker, bases de datos, la Mind API de Synapse) — siempre prefiere la API a parsear salida de CLI.</li>\n<li>Procesos interactivos de larga duración. <code>exec</code> es fire-and-forget con timeout; usa <code>nohup</code> + un archivo de estado en el objetivo.</li>\n<li>Cualquier cosa destructiva sin instrucción explícita. El proxy te da el poder — el humano te da el permiso.</li>\n</ul>\n<h2>Recuperación de errores</h2>\n<table>\n<thead>\n<tr>\n<th>Síntoma</th>\n<th>Solución</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>401 al conectar</td>\n<td>token expirado — relee de memoria/settings, nunca de una transcripción</td>\n</tr>\n<tr>\n<td>sesión 404</td>\n<td>sesión expirada (tope 24h) — reconecta</td>\n</tr>\n<tr>\n<td>timeout de exec</td>\n<td>sube <code>timeout</code> o divide el comando; revisa la carga del objetivo</td>\n</tr>\n<tr>\n<td>contenido de archivo corrupto</td>\n<td>usaste <code>exec</code> en vez de SFTP — cambia a los endpoints 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":403,"readMinutes":2,"llmContext":"NUNCA guardes claves privadas SSH crudas como LLM. Usa una API HTTP de proxy SSH:\n1) POST /connect?target=<name> → session_id (persistente, sobrevive horas).\n2) POST /<session_id>/exec {command, timeout} → exec_id.\n3) GET /<session_id>/exec/<exec_id> → salida cuando termine.\n4) Usa los endpoints SFTP para subir/descargar archivos en lugar de cat/heredoc.\nLos targets son nombres pre-registrados (p. ej. \"vps1\"), no IPs — el proxy es\nel único lugar donde viven las credenciales.\n","requestedLang":"es","contentMarkdown":"\n# Acceso SSH vía proxy\n\nLos agentes necesitan ejecutar comandos en máquinas remotas — desplegar un contenedor, leer un log, arreglar una configuración. Entregar claves privadas SSH crudas a un LLM es una mala idea: las claves acaban en ventanas de contexto, logs y transcripciones. Un proxy SSH lo resuelve: él posee las credenciales, el agente solo habla HTTP.\n\n## El modelo\n\n```\nLLM ──HTTPS──► Proxy SSH ──SSH──► máquinas objetivo\n                (posee las claves,\n                 log de auditoría,\n                 límites de tasa)\n```\n\nEl proxy expone sesiones persistentes con una superficie REST sencilla:\n\n| Endpoint | Propósito |\n|---|---|\n| `POST /connect?target=vps1` | abrir una sesión → `session_id` |\n| `POST /<sid>/exec` `{command, timeout}` | ejecutar un comando (async) → `exec_id` |\n| `GET /<sid>/exec/<exec_id>` | consultar la salida |\n| Endpoints SFTP | subida/descarga de archivos, `ls`, `mkdir`, `rm` |\n\nLa autenticación es un token Bearer — el mismo patrón que todas las demás APIs que usas.\n\n## El patrón de ejecución\n\n```bash\n# 1. conecta una vez, reutiliza la sesión para toda la tarea\nSID=$(curl -s -H \"Authorization: Bearer $KEY\" \\\n  \"https://ssh-proxy.example/connect?target=vps1\" | jq -r .session_id)\n\n# 2. ejecuta un comando\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. consulta el resultado\ncurl -s -H \"Authorization: Bearer $KEY\" \"https://ssh-proxy.example/$SID/exec/$EID\"\n```\n\n## Reglas de uso\n\n1. **Reutiliza sesiones.** Conectar por comando desperdicia recursos y dispara límites de tasa. Guarda el `session_id` (p. ej. `/tmp/vps1_sid`) y hazle heartbeat antes de reutilizarlo.\n2. **Nombra los targets, no los descubras.** Los targets están pre-registrados (`vps1`, `pi`, …). El proxy es la única fuente de verdad — nunca hardcodees IPs en código de agente.\n3. **Prefiere SFTP a cat/heredoc de shell para archivos.** Escapar datos binarios a través de `exec` corrompe los payloads. Usa los endpoints dedicados de subida/descarga.\n4. **Cita tus comandos.** El cuerpo JSON se parsea dos veces (HTTP + shell). Envuelve los comandos remotos en comillas simples y mantenlos en una sola línea.\n5. **Asume auditoría.** Cada exec queda en el log de auditoría del proxy — compórtate como si el humano leyera todo (puede hacerlo).\n\n## Cuándo NO usar exec\n\n- Leer estado estructurado que tiene una API (Docker, bases de datos, la Mind API de Synapse) — siempre prefiere la API a parsear salida de CLI.\n- Procesos interactivos de larga duración. `exec` es fire-and-forget con timeout; usa `nohup` + un archivo de estado en el objetivo.\n- Cualquier cosa destructiva sin instrucción explícita. El proxy te da el poder — el humano te da el permiso.\n\n## Recuperación de errores\n\n| Síntoma | Solución |\n|---|---|\n| 401 al conectar | token expirado — relee de memoria/settings, nunca de una transcripción |\n| sesión 404 | sesión expirada (tope 24h) — reconecta |\n| timeout de exec | sube `timeout` o divide el comando; revisa la carga del objetivo |\n| contenido de archivo corrupto | usaste `exec` en vez de SFTP — cambia a los endpoints SFTP |\n","contentHtml":"<h1>Acceso SSH vía proxy</h1>\n<p>Los agentes necesitan ejecutar comandos en máquinas remotas — desplegar un contenedor, leer un log, arreglar una configuración. Entregar claves privadas SSH crudas a un LLM es una mala idea: las claves acaban en ventanas de contexto, logs y transcripciones. Un proxy SSH lo resuelve: él posee las credenciales, el agente solo habla HTTP.</p>\n<h2>El modelo</h2>\n<pre><code class=\"hljs language-plaintext\">LLM ──HTTPS──► Proxy SSH ──SSH──► máquinas objetivo\n                (posee las claves,\n                 log de auditoría,\n                 límites de tasa)</code></pre><p>El proxy expone sesiones persistentes con una superficie REST sencilla:</p>\n<table>\n<thead>\n<tr>\n<th>Endpoint</th>\n<th>Propósito</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>POST /connect?target=vps1</code></td>\n<td>abrir una sesión → <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>ejecutar un comando (async) → <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>consultar la salida</td>\n</tr>\n<tr>\n<td>Endpoints SFTP</td>\n<td>subida/descarga de archivos, <code>ls</code>, <code>mkdir</code>, <code>rm</code></td>\n</tr>\n</tbody></table>\n<p>La autenticación es un token Bearer — el mismo patrón que todas las demás APIs que usas.</p>\n<h2>El patrón de ejecución</h2>\n<pre><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># 1. conecta una vez, reutiliza la sesión para toda la tarea</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. ejecuta un comando</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. consulta el resultado</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>Reglas de uso</h2>\n<ol>\n<li><strong>Reutiliza sesiones.</strong> Conectar por comando desperdicia recursos y dispara límites de tasa. Guarda el <code>session_id</code> (p. ej. <code>/tmp/vps1_sid</code>) y hazle heartbeat antes de reutilizarlo.</li>\n<li><strong>Nombra los targets, no los descubras.</strong> Los targets están pre-registrados (<code>vps1</code>, <code>pi</code>, …). El proxy es la única fuente de verdad — nunca hardcodees IPs en código de agente.</li>\n<li><strong>Prefiere SFTP a cat/heredoc de shell para archivos.</strong> Escapar datos binarios a través de <code>exec</code> corrompe los payloads. Usa los endpoints dedicados de subida/descarga.</li>\n<li><strong>Cita tus comandos.</strong> El cuerpo JSON se parsea dos veces (HTTP + shell). Envuelve los comandos remotos en comillas simples y mantenlos en una sola línea.</li>\n<li><strong>Asume auditoría.</strong> Cada exec queda en el log de auditoría del proxy — compórtate como si el humano leyera todo (puede hacerlo).</li>\n</ol>\n<h2>Cuándo NO usar exec</h2>\n<ul>\n<li>Leer estado estructurado que tiene una API (Docker, bases de datos, la Mind API de Synapse) — siempre prefiere la API a parsear salida de CLI.</li>\n<li>Procesos interactivos de larga duración. <code>exec</code> es fire-and-forget con timeout; usa <code>nohup</code> + un archivo de estado en el objetivo.</li>\n<li>Cualquier cosa destructiva sin instrucción explícita. El proxy te da el poder — el humano te da el permiso.</li>\n</ul>\n<h2>Recuperación de errores</h2>\n<table>\n<thead>\n<tr>\n<th>Síntoma</th>\n<th>Solución</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>401 al conectar</td>\n<td>token expirado — relee de memoria/settings, nunca de una transcripción</td>\n</tr>\n<tr>\n<td>sesión 404</td>\n<td>sesión expirada (tope 24h) — reconecta</td>\n</tr>\n<tr>\n<td>timeout de exec</td>\n<td>sube <code>timeout</code> o divide el comando; revisa la carga del objetivo</td>\n</tr>\n<tr>\n<td>contenido de archivo corrupto</td>\n<td>usaste <code>exec</code> en vez de SFTP — cambia a los endpoints 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"]}