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

166 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](https://github.com/awizemann/scarf/wiki/ACP-Subprocess) и исходный `ProcessACPChannel.swift`: ACP поверх `ssh -T`; изучены документация и код, приложение не запускалось и не является плагином Balda.
- [acp-tunnel](https://github.com/benthecarman/acp-tunnel), revision `6e0543cd5692a0c2a27f5a7b11fb1ad536315461`: просмотрены исходники преобразования cwd и MCP policy; транспорт WebSocket, бинарник не запускался.
- Утверждение, что такой прослойки нигде нет и её обязательно писать заново, проверкой опровергнуто.
## Сохранённый отчёт
```json
{
"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-проверки
```text
=== 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
```text
=== 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
```