{"title":"SSH-Zugriff über einen Proxy","slug":"ssh-proxy-access","category":"llm-cookbook","summary":"Wie ein LLM-Agent sicher über eine SSH-Proxy-API auf Remote-Maschinen arbeitet statt mit rohen SSH-Keys.","audience":["llm"],"tags":["cookbook","ssh","proxy","infrastructure","remote-execution"],"difficulty":"intermediate","updated":"2026-09-26","word_count":348,"read_minutes":2,"llm_context":"NIEMALS rohe SSH-Private-Keys als LLM besitzen. Nutze eine SSH-Proxy-HTTP-API:\n1) POST /connect?target=<name> → session_id (persistent, hält Stunden).\n2) POST /<session_id>/exec {command, timeout} → exec_id.\n3) GET /<session_id>/exec/<exec_id> → Ausgabe, wenn fertig.\n4) Für Datei-Up-/Download die SFTP-Endpoints statt cat/heredoc nutzen.\nTargets sind vorregistrierte Namen (z.B. \"vps1\"), keine IPs — der Proxy ist\nder einzige Ort, an dem Zugangsdaten liegen.\n","lang":"de","translated":true,"requested_lang":"de","content_markdown":"\n# SSH-Zugriff über einen Proxy\n\nAgents müssen Befehle auf Remote-Maschinen ausführen — einen Container\ndeployen, ein Log lesen, eine Config reparieren. Rohe SSH-Private-Keys an\nein LLM zu übergeben ist eine schlechte Idee: Keys landen in\nKontextfenstern, Logs und Transkripten. Ein SSH-Proxy löst das: er besitzt\ndie Zugangsdaten, der Agent spricht nur HTTP.\n\n## Das Modell\n\n```\nLLM ──HTTPS──► SSH-Proxy ──SSH──► Ziel-Maschinen\n               (besitzt Keys,\n                Audit-Log,\n                Rate-Limits)\n```\n\nDer Proxy stellt persistente Sessions mit einer einfachen REST-Oberfläche\nbereit:\n\n| Endpoint | Zweck |\n|---|---|\n| `POST /connect?target=vps1` | Session öffnen → `session_id` |\n| `POST /<sid>/exec` `{command, timeout}` | Befehl ausführen (async) → `exec_id` |\n| `GET /<sid>/exec/<exec_id>` | auf Ausgabe pollen |\n| SFTP-Endpoints | Datei-Up-/Download, `ls`, `mkdir`, `rm` |\n\nDie Auth ist ein Bearer-Token — dasselbe Muster wie bei jeder anderen API,\ndie du nutzt.\n\n## Das Ausführungsmuster\n\n```bash\n# 1. Einmal verbinden, die Session für die ganze Task wiederverwenden\nSID=$(curl -s -H \"Authorization: Bearer $KEY\" \\\n  \"https://ssh-proxy.example/connect?target=vps1\" | jq -r .session_id)\n\n# 2. Befehl ausführen\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. Ergebnis abholen\ncurl -s -H \"Authorization: Bearer $KEY\" \"https://ssh-proxy.example/$SID/exec/$EID\"\n```\n\n## Spielregeln\n\n1. **Sessions wiederverwenden.** Eine Verbindung pro Befehl verschwendet\n   Ressourcen und rattert gegen Rate-Limits. Speichere die `session_id`\n   (z.B. `/tmp/vps1_sid`) und mache vorher einen Heartbeat.\n2. **Targets benennen statt sie zu suchen.** Targets sind vorregistriert\n   (`vps1`, `pi`, …). Der Proxy ist die einzige Wahrheitsquelle — nie IPs\n   in Agent-Code hardcoden.\n3. **Für Dateien SFTP statt shell-cat/heredoc.** Binärdaten über `exec` zu\n   schicken korrumpiert die Nutzdaten. Nutze die dedizierten Upload-/Download-\n   Endpoints.\n4. **Befehle sauber quoten.** Der JSON-Body wird zweimal geparst (HTTP +\n   Shell). Umschließe Remote-Befehle mit einfachen Anführungszeichen und\n   halte sie einzeilig.\n5. **Von Auditing ausgehen.** Jeder exec landet im Audit-Log des Proxies —\n   verhalte dich so, als würde der Mensch alles lesen (er kann).\n\n## Wann exec NICHT nutzen\n\n- Strukturierten Zustand lesen, der eine API hat (Docker, Datenbanken, die\n  Synapse-Mind-API) — immer die API vor CLI-Output-Parsing bevorzugen.\n- Lange laufende interaktive Prozesse. `exec` ist fire-and-forget mit\n  Timeout; nutze `nohup` + Statusdatei auf dem Ziel.\n- Alles Destruktive ohne explizite Anweisung. Der Proxy gibt dir die\n  Macht — der Mensch gibt dir die Erlaubnis.\n\n## Fehlerbehandlung\n\n| Symptom | Lösung |\n|---|---|\n| 401 beim Verbinden | Token abgelaufen — neu aus Memory/Settings lesen, nie aus einem Transkript |\n| Session 404 | Session abgelaufen (24h Cap) — neu verbinden |\n| exec-Timeout | `timeout` erhöhen oder Befehl splitten; Last auf dem Ziel prüfen |\n| Verstümmelte Dateiinhalte | du hast `exec` statt SFTP genutzt — auf die SFTP-Endpoints wechseln |\n","content_html":"<h1>SSH-Zugriff über einen Proxy</h1>\n<p>Agents müssen Befehle auf Remote-Maschinen ausführen — einen Container\ndeployen, ein Log lesen, eine Config reparieren. Rohe SSH-Private-Keys an\nein LLM zu übergeben ist eine schlechte Idee: Keys landen in\nKontextfenstern, Logs und Transkripten. Ein SSH-Proxy löst das: er besitzt\ndie Zugangsdaten, der Agent spricht nur HTTP.</p>\n<h2>Das Modell</h2>\n<pre><code class=\"hljs language-plaintext\">LLM ──HTTPS──► SSH-Proxy ──SSH──► Ziel-Maschinen\n               (besitzt Keys,\n                Audit-Log,\n                Rate-Limits)</code></pre><p>Der Proxy stellt persistente Sessions mit einer einfachen REST-Oberfläche\nbereit:</p>\n<table>\n<thead>\n<tr>\n<th>Endpoint</th>\n<th>Zweck</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>POST /connect?target=vps1</code></td>\n<td>Session öffnen → <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>Befehl ausführen (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>auf Ausgabe pollen</td>\n</tr>\n<tr>\n<td>SFTP-Endpoints</td>\n<td>Datei-Up-/Download, <code>ls</code>, <code>mkdir</code>, <code>rm</code></td>\n</tr>\n</tbody></table>\n<p>Die Auth ist ein Bearer-Token — dasselbe Muster wie bei jeder anderen API,\ndie du nutzt.</p>\n<h2>Das Ausführungsmuster</h2>\n<pre><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># 1. Einmal verbinden, die Session für die ganze Task wiederverwenden</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. Befehl ausführen</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. Ergebnis abholen</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>Spielregeln</h2>\n<ol>\n<li><strong>Sessions wiederverwenden.</strong> Eine Verbindung pro Befehl verschwendet\nRessourcen und rattert gegen Rate-Limits. Speichere die <code>session_id</code>\n(z.B. <code>/tmp/vps1_sid</code>) und mache vorher einen Heartbeat.</li>\n<li><strong>Targets benennen statt sie zu suchen.</strong> Targets sind vorregistriert\n(<code>vps1</code>, <code>pi</code>, …). Der Proxy ist die einzige Wahrheitsquelle — nie IPs\nin Agent-Code hardcoden.</li>\n<li><strong>Für Dateien SFTP statt shell-cat/heredoc.</strong> Binärdaten über <code>exec</code> zu\nschicken korrumpiert die Nutzdaten. Nutze die dedizierten Upload-/Download-\nEndpoints.</li>\n<li><strong>Befehle sauber quoten.</strong> Der JSON-Body wird zweimal geparst (HTTP +\nShell). Umschließe Remote-Befehle mit einfachen Anführungszeichen und\nhalte sie einzeilig.</li>\n<li><strong>Von Auditing ausgehen.</strong> Jeder exec landet im Audit-Log des Proxies —\nverhalte dich so, als würde der Mensch alles lesen (er kann).</li>\n</ol>\n<h2>Wann exec NICHT nutzen</h2>\n<ul>\n<li>Strukturierten Zustand lesen, der eine API hat (Docker, Datenbanken, die\nSynapse-Mind-API) — immer die API vor CLI-Output-Parsing bevorzugen.</li>\n<li>Lange laufende interaktive Prozesse. <code>exec</code> ist fire-and-forget mit\nTimeout; nutze <code>nohup</code> + Statusdatei auf dem Ziel.</li>\n<li>Alles Destruktive ohne explizite Anweisung. Der Proxy gibt dir die\nMacht — der Mensch gibt dir die Erlaubnis.</li>\n</ul>\n<h2>Fehlerbehandlung</h2>\n<table>\n<thead>\n<tr>\n<th>Symptom</th>\n<th>Lösung</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>401 beim Verbinden</td>\n<td>Token abgelaufen — neu aus Memory/Settings lesen, nie aus einem Transkript</td>\n</tr>\n<tr>\n<td>Session 404</td>\n<td>Session abgelaufen (24h Cap) — neu verbinden</td>\n</tr>\n<tr>\n<td>exec-Timeout</td>\n<td><code>timeout</code> erhöhen oder Befehl splitten; Last auf dem Ziel prüfen</td>\n</tr>\n<tr>\n<td>Verstümmelte Dateiinhalte</td>\n<td>du hast <code>exec</code> statt SFTP genutzt — auf die SFTP-Endpoints wechseln</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","de"],"wordCount":348,"readMinutes":2,"llmContext":"NIEMALS rohe SSH-Private-Keys als LLM besitzen. Nutze eine SSH-Proxy-HTTP-API:\n1) POST /connect?target=<name> → session_id (persistent, hält Stunden).\n2) POST /<session_id>/exec {command, timeout} → exec_id.\n3) GET /<session_id>/exec/<exec_id> → Ausgabe, wenn fertig.\n4) Für Datei-Up-/Download die SFTP-Endpoints statt cat/heredoc nutzen.\nTargets sind vorregistrierte Namen (z.B. \"vps1\"), keine IPs — der Proxy ist\nder einzige Ort, an dem Zugangsdaten liegen.\n","requestedLang":"de","contentMarkdown":"\n# SSH-Zugriff über einen Proxy\n\nAgents müssen Befehle auf Remote-Maschinen ausführen — einen Container\ndeployen, ein Log lesen, eine Config reparieren. Rohe SSH-Private-Keys an\nein LLM zu übergeben ist eine schlechte Idee: Keys landen in\nKontextfenstern, Logs und Transkripten. Ein SSH-Proxy löst das: er besitzt\ndie Zugangsdaten, der Agent spricht nur HTTP.\n\n## Das Modell\n\n```\nLLM ──HTTPS──► SSH-Proxy ──SSH──► Ziel-Maschinen\n               (besitzt Keys,\n                Audit-Log,\n                Rate-Limits)\n```\n\nDer Proxy stellt persistente Sessions mit einer einfachen REST-Oberfläche\nbereit:\n\n| Endpoint | Zweck |\n|---|---|\n| `POST /connect?target=vps1` | Session öffnen → `session_id` |\n| `POST /<sid>/exec` `{command, timeout}` | Befehl ausführen (async) → `exec_id` |\n| `GET /<sid>/exec/<exec_id>` | auf Ausgabe pollen |\n| SFTP-Endpoints | Datei-Up-/Download, `ls`, `mkdir`, `rm` |\n\nDie Auth ist ein Bearer-Token — dasselbe Muster wie bei jeder anderen API,\ndie du nutzt.\n\n## Das Ausführungsmuster\n\n```bash\n# 1. Einmal verbinden, die Session für die ganze Task wiederverwenden\nSID=$(curl -s -H \"Authorization: Bearer $KEY\" \\\n  \"https://ssh-proxy.example/connect?target=vps1\" | jq -r .session_id)\n\n# 2. Befehl ausführen\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. Ergebnis abholen\ncurl -s -H \"Authorization: Bearer $KEY\" \"https://ssh-proxy.example/$SID/exec/$EID\"\n```\n\n## Spielregeln\n\n1. **Sessions wiederverwenden.** Eine Verbindung pro Befehl verschwendet\n   Ressourcen und rattert gegen Rate-Limits. Speichere die `session_id`\n   (z.B. `/tmp/vps1_sid`) und mache vorher einen Heartbeat.\n2. **Targets benennen statt sie zu suchen.** Targets sind vorregistriert\n   (`vps1`, `pi`, …). Der Proxy ist die einzige Wahrheitsquelle — nie IPs\n   in Agent-Code hardcoden.\n3. **Für Dateien SFTP statt shell-cat/heredoc.** Binärdaten über `exec` zu\n   schicken korrumpiert die Nutzdaten. Nutze die dedizierten Upload-/Download-\n   Endpoints.\n4. **Befehle sauber quoten.** Der JSON-Body wird zweimal geparst (HTTP +\n   Shell). Umschließe Remote-Befehle mit einfachen Anführungszeichen und\n   halte sie einzeilig.\n5. **Von Auditing ausgehen.** Jeder exec landet im Audit-Log des Proxies —\n   verhalte dich so, als würde der Mensch alles lesen (er kann).\n\n## Wann exec NICHT nutzen\n\n- Strukturierten Zustand lesen, der eine API hat (Docker, Datenbanken, die\n  Synapse-Mind-API) — immer die API vor CLI-Output-Parsing bevorzugen.\n- Lange laufende interaktive Prozesse. `exec` ist fire-and-forget mit\n  Timeout; nutze `nohup` + Statusdatei auf dem Ziel.\n- Alles Destruktive ohne explizite Anweisung. Der Proxy gibt dir die\n  Macht — der Mensch gibt dir die Erlaubnis.\n\n## Fehlerbehandlung\n\n| Symptom | Lösung |\n|---|---|\n| 401 beim Verbinden | Token abgelaufen — neu aus Memory/Settings lesen, nie aus einem Transkript |\n| Session 404 | Session abgelaufen (24h Cap) — neu verbinden |\n| exec-Timeout | `timeout` erhöhen oder Befehl splitten; Last auf dem Ziel prüfen |\n| Verstümmelte Dateiinhalte | du hast `exec` statt SFTP genutzt — auf die SFTP-Endpoints wechseln |\n","contentHtml":"<h1>SSH-Zugriff über einen Proxy</h1>\n<p>Agents müssen Befehle auf Remote-Maschinen ausführen — einen Container\ndeployen, ein Log lesen, eine Config reparieren. Rohe SSH-Private-Keys an\nein LLM zu übergeben ist eine schlechte Idee: Keys landen in\nKontextfenstern, Logs und Transkripten. Ein SSH-Proxy löst das: er besitzt\ndie Zugangsdaten, der Agent spricht nur HTTP.</p>\n<h2>Das Modell</h2>\n<pre><code class=\"hljs language-plaintext\">LLM ──HTTPS──► SSH-Proxy ──SSH──► Ziel-Maschinen\n               (besitzt Keys,\n                Audit-Log,\n                Rate-Limits)</code></pre><p>Der Proxy stellt persistente Sessions mit einer einfachen REST-Oberfläche\nbereit:</p>\n<table>\n<thead>\n<tr>\n<th>Endpoint</th>\n<th>Zweck</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>POST /connect?target=vps1</code></td>\n<td>Session öffnen → <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>Befehl ausführen (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>auf Ausgabe pollen</td>\n</tr>\n<tr>\n<td>SFTP-Endpoints</td>\n<td>Datei-Up-/Download, <code>ls</code>, <code>mkdir</code>, <code>rm</code></td>\n</tr>\n</tbody></table>\n<p>Die Auth ist ein Bearer-Token — dasselbe Muster wie bei jeder anderen API,\ndie du nutzt.</p>\n<h2>Das Ausführungsmuster</h2>\n<pre><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># 1. Einmal verbinden, die Session für die ganze Task wiederverwenden</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. Befehl ausführen</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. Ergebnis abholen</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>Spielregeln</h2>\n<ol>\n<li><strong>Sessions wiederverwenden.</strong> Eine Verbindung pro Befehl verschwendet\nRessourcen und rattert gegen Rate-Limits. Speichere die <code>session_id</code>\n(z.B. <code>/tmp/vps1_sid</code>) und mache vorher einen Heartbeat.</li>\n<li><strong>Targets benennen statt sie zu suchen.</strong> Targets sind vorregistriert\n(<code>vps1</code>, <code>pi</code>, …). Der Proxy ist die einzige Wahrheitsquelle — nie IPs\nin Agent-Code hardcoden.</li>\n<li><strong>Für Dateien SFTP statt shell-cat/heredoc.</strong> Binärdaten über <code>exec</code> zu\nschicken korrumpiert die Nutzdaten. Nutze die dedizierten Upload-/Download-\nEndpoints.</li>\n<li><strong>Befehle sauber quoten.</strong> Der JSON-Body wird zweimal geparst (HTTP +\nShell). Umschließe Remote-Befehle mit einfachen Anführungszeichen und\nhalte sie einzeilig.</li>\n<li><strong>Von Auditing ausgehen.</strong> Jeder exec landet im Audit-Log des Proxies —\nverhalte dich so, als würde der Mensch alles lesen (er kann).</li>\n</ol>\n<h2>Wann exec NICHT nutzen</h2>\n<ul>\n<li>Strukturierten Zustand lesen, der eine API hat (Docker, Datenbanken, die\nSynapse-Mind-API) — immer die API vor CLI-Output-Parsing bevorzugen.</li>\n<li>Lange laufende interaktive Prozesse. <code>exec</code> ist fire-and-forget mit\nTimeout; nutze <code>nohup</code> + Statusdatei auf dem Ziel.</li>\n<li>Alles Destruktive ohne explizite Anweisung. Der Proxy gibt dir die\nMacht — der Mensch gibt dir die Erlaubnis.</li>\n</ul>\n<h2>Fehlerbehandlung</h2>\n<table>\n<thead>\n<tr>\n<th>Symptom</th>\n<th>Lösung</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>401 beim Verbinden</td>\n<td>Token abgelaufen — neu aus Memory/Settings lesen, nie aus einem Transkript</td>\n</tr>\n<tr>\n<td>Session 404</td>\n<td>Session abgelaufen (24h Cap) — neu verbinden</td>\n</tr>\n<tr>\n<td>exec-Timeout</td>\n<td><code>timeout</code> erhöhen oder Befehl splitten; Last auf dem Ziel prüfen</td>\n</tr>\n<tr>\n<td>Verstümmelte Dateiinhalte</td>\n<td>du hast <code>exec</code> statt SFTP genutzt — auf die SFTP-Endpoints wechseln</td>\n</tr>\n</tbody></table>\n","translationsAvailable":["en","de"]}