Files
obsidian-vault/personal/projects/balda/ssh-acp-verification-2026-10-18.md
T

12 KiB
Raw Blame History

Balda → SSH → ACP: проверка 2026-10-18

Статус этой заметки — 2026-10-19

Ниже сохранён датированный технический прогон 2026-10-18, включая его тогдашние открытые проверки. Текущая эксплуатационная сводка: personal/projects/balda/setup, планы: personal/plans/balda-remote-agent и personal/plans/workspace-gateway. С тех пор приняты production SSH/SRT к Mac, SMB доступ/отзыв, shared Beads fixture, native approval bridge и generic SSH Manager 0.3.2 с импортом существующего доверенного доступа Mac и проверкой установленного ключа из клиента; текущая приёмка: personal/projects/balda/ssh-client-route-2026-10-19. Полный модельный Prism lifecycle и реальные project DB остаются отдельной приёмкой.

Исторический протокол проверки

Проверено на исходниках Balda a908ee1a5ff2, runtime v2.6.3, ACP adapter v2.6.1. Актуальный план: personal/plans/balda-remote-agent.

Вывод

Штатный generic_acp.cmd Balda запускает обычный OpenSSH-клиент и работает с ACP-агентом по SSH без отдельной реализации ACP-транспорта.

Что именно запускалось

  • Контрольный SSH-сервер на golang.org/x/crypto/ssh в отдельном временном контейнере; клиент — настоящий /usr/bin/ssh -T с проверкой закреплённого ключа хоста.
  • Штатная фабрика провайдеров Balda/runtime, настоящий ADK runner и штатный обработчик разрешений Balda.
  • Контрольный ACP-агент для разрешения, отказа, отмены и вызова настоящего HTTP MCP-инструмента.
  • Реальный установленный OpenCode: initialize, session/new, разрыв SSH, новое подключение и session/resume.
  • Штатные тесты Balda для подтверждения в Mattermost, отклонения чужого ответа и отмены по timeout; сообщений в реальный Mattermost не отправляли.

Контейнер работал от node, с read-only rootfs, cap-drop=ALL, без privileged и host mounts. Каталог /Users/admin/Developer и production /state/state.db внутри отсутствовали. Тестовые контейнеры и производный образ удалены; исходные Balda и Eagle продолжили работу.

Выявленные условия

  • Высокоуровневый ACP adapter Balda проверяет cwd локально; путь, существующий только на удалённом хосте, отклоняется до создания ACP-сессии.
  • Для прямого SSH нужны согласованные абсолютные пути либо отдельное сопоставление cwd; один cd в удалённой команде не меняет session/new.cwd.
  • MCP-адреса должны быть достижимы из удалённой среды и проходить проверку HTTP hostname; Balda автоматически не создаёт SSH port forwarding.
  • В тесте вызов MCP прошёл по явно настроенному достижимому адресу; проверка DNS rebinding разрешала только тестовые hostname.
  • permissions.mode: ask обрабатывает session/request_permission, поступивший от агента; режим самого агента также должен запрашивать разрешения.
  • Файловая изоляция обеспечивается правами ОС и sandbox среды; выбор cwd её не заменяет.
  • На момент проверки рабочая конфигурация Balda сохраняла allow_all; тест не менял её.

Границы доказательства

Модели не вызывались; полный Prism lifecycle и реальные удалённые Claude/Codex не запускались. Проверка протокола не является приёмкой production SSH-хоста, полной политики sandbox каждого агента или доступа к исходной БД Beads. Для реального OpenCode проверены создание и возобновление сессии, а подтверждения и вызов MCP проверены контрольным ACP-агентом. Поддержка продолжения сессии зависит от capability и сохранённого состояния конкретного агента.

Готовые проекты

  • Scarf: ACP Subprocess и исходный ProcessACPChannel.swift: ACP поверх ssh -T; изучены документация и код, приложение не запускалось и не является плагином Balda.
  • acp-tunnel, revision 6e0543cd5692a0c2a27f5a7b11fb1ad536315461: просмотрены исходники преобразования cwd и MCP policy; транспорт WebSocket, бинарник не запускался.
  • Утверждение, что такой прослойки нигде нет и её обязательно писать заново, проверкой опровергнуто.

Сохранённый отчёт

{
  "source": {
    "balda": "a908ee1a5ff2",
    "runtime": "v2.6.3",
    "acpagent": "v2.6.1",
    "remote_image": "balda-prism:20261018"
  },
  "verified": {
    "native_generic_acp_with_real_ssh": true,
    "native_allow_and_deny_callback_roundtrip": true,
    "ask_without_context_denied": true,
    "native_mattermost_permission_unit_tests": true,
    "wrong_requester_rejected_unit_test": true,
    "permission_timeout_cancels_unit_test": true,
    "actual_remote_http_mcp_tool_call": true,
    "local_only_cwd_validation_reproduced": true,
    "prompt_cancellation_bounded_ms": 253,
    "fixture_session_resume_after_ssh_reconnect": true,
    "real_opencode_initialize_and_new_session_over_ssh": true,
    "real_opencode_resume_after_ssh_reconnect": true,
    "pinned_host_key_mismatch_rejected": true,
    "test_container_host_repositories_and_production_state_unavailable": true
  },
  "requirements": [
    "SSH credentials and pinned host key",
    "Prepared remote ACP agent and native authentication",
    "Remote repository present; matching cwd paths or cwd mapping adapter",
    "MCP endpoint reachable from remote environment with correct hostname policy",
    "OS filesystem isolation independently of ACP permissions",
    "Native ask policy for interactive review; production config currently allow_all"
  ],
  "limits": {
    "model_inference_performed": false,
    "production_config_changed": false,
    "production_sessions_reset": false,
    "external_chat_messages_sent": false,
    "real_claude_or_codex_remote_runs_tested": false,
    "full_prism_lifecycle_tested": false,
    "acp_tunnel_binary_run": false,
    "scarf_application_run": false
  },
  "existing_projects": {
    "acp_tunnel": {
      "revision": "6e0543cd5692a0c2a27f5a7b11fb1ad536315461",
      "assessment": "Source reviewed: WebSocket transport, cwd mapping and MCP policy; no SSH transport; not required for tested direct SSH path"
    },
    "scarf": "Primary source reviewed: ssh -T carries ACP and permission requests; separate application, not a Balda plugin"
  },
  "cleanup": "Both temporary containers and their derived image removed; baseline image preserved",
  "evidence": [
    "compat.log",
    "opencode-live.log",
    "compat_test.go",
    "cmd/fixture/main.go"
  ],
  "evidence_sha256": {
    "compat.log": "5952082d73d418b41b0d92661305926ed5f5a902a250f735562a417ddc7ab2b1",
    "opencode-live.log": "4a9fcaa1fe7619dbb371bffad6327c7364a6f5dfd3bc34c973aa8323eb05df5d",
    "compat_test.go": "d3eb2ccc55c4b5ce7b479a3f7d02689219b6c9a32880f76efb9a79665fa0ded5",
    "cmd/fixture/main.go": "90188cb209f97b5b11e015f3c76decc7219398dd66abc037ea013dea9aaec80b"
  }
}

Протокол SSH-проверки

=== RUN   TestNativeGenericACPThroughSSH
=== RUN   TestNativeGenericACPThroughSSH/approve
=== RUN   TestNativeGenericACPThroughSSH/deny
2026/10/18 16:21:01 INFO connection closed component=runtime.agentfactory.acp subcomponent=acpagent.client cause="peer connection closed"
2026/10/18 16:21:01 INFO connection closed component=runtime.agentfactory.acp subcomponent=acpagent.client cause="peer connection closed"
=== RUN   TestNativeGenericACPThroughSSH/ask_missing_context_fails_closed
2026/10/18 16:21:01 INFO connection closed component=runtime.agentfactory.acp subcomponent=acpagent.client cause="peer connection closed"
--- PASS: TestNativeGenericACPThroughSSH (0.16s)
    --- PASS: TestNativeGenericACPThroughSSH/approve (0.05s)
    --- PASS: TestNativeGenericACPThroughSSH/deny (0.05s)
    --- PASS: TestNativeGenericACPThroughSSH/ask_missing_context_fails_closed (0.06s)
=== RUN   TestMCPRealToolThroughSSH
2026/10/18 16:21:01 INFO connection closed component=runtime.agentfactory.acp subcomponent=acpagent.client cause="peer connection closed"
--- PASS: TestMCPRealToolThroughSSH (0.06s)
=== RUN   TestNativeRemoteOnlyCWDRejectedLocally
    compat_test.go:124: observed local validation: stat acp session cwd "/tmp/acp-remote-only-20261018": stat /tmp/acp-remote-only-20261018: no such file or directory
2026/10/18 16:21:01 INFO connection closed component=runtime.agentfactory.acp subcomponent=acpagent.client cause="peer connection closed"
--- PASS: TestNativeRemoteOnlyCWDRejectedLocally (0.03s)
=== RUN   TestCancellationThroughSSH
    compat_test.go:135: native cancellation completed in 252.165125ms: context deadline exceeded
2026/10/18 16:21:02 INFO connection closed component=runtime.agentfactory.acp subcomponent=acpagent.client cause="peer connection closed"
--- PASS: TestCancellationThroughSSH (0.28s)
=== RUN   TestResumeAfterSSHReconnect
--- PASS: TestResumeAfterSSHReconnect (0.09s)
=== RUN   TestRealOpenCodeACPInitializeAndSessionOverSSH
    compat_test.go:194: real OpenCode ACP initialization and session/new succeeded without inference
--- PASS: TestRealOpenCodeACPInitializeAndSessionOverSSH (0.81s)
=== RUN   TestPinnedHostKeyRejectsMismatch
--- PASS: TestPinnedHostKeyRejectsMismatch (0.01s)
=== RUN   TestConfigurationRecord
    compat_test.go:226: {"acpagent":"v2.6.1","balda":"a908ee1a","model_inference":false,"production_changes":false,"runtime":"v2.6.3"}
--- PASS: TestConfigurationRecord (0.00s)
PASS

Реальный OpenCode после обязательного initialize и с повторным SSH

=== RUN   TestRealOpenCodeACPInitializeAndSessionOverSSH
    compat_test.go:189: real OpenCode capabilities: {"auth":{},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"sessionCapabilities":{"close":{},"fork":{},"list":{},"resume":{}}}
    compat_test.go:198: real OpenCode ACP initialization and session/new succeeded without inference
    compat_test.go:213: real OpenCode session/resume after SSH reconnect succeeded without inference
--- PASS: TestRealOpenCodeACPInitializeAndSessionOverSSH (1.56s)
PASS