From c3ced58ee136fa123f6ba9b7f4bbc8d3ab629b88 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sat, 26 Sep 2026 22:13:08 +0600 Subject: [PATCH] [2026-09-26] eagle: personal/plans/balda-mm-phase4-agents.md personal/projects/personal-os/obsidian-mcp-setup.md personal/tech/balda-executor-mcp.md personal/tech/hermes-secret-redaction-bypass.md --- personal/plans/balda-mm-phase4-agents.md | 124 ++++++++++++------ .../personal-os/obsidian-mcp-setup.md | 39 ++++-- personal/tech/balda-executor-mcp.md | 87 ++++++++++-- .../tech/hermes-secret-redaction-bypass.md | 66 ++++++++++ 4 files changed, 254 insertions(+), 62 deletions(-) diff --git a/personal/plans/balda-mm-phase4-agents.md b/personal/plans/balda-mm-phase4-agents.md index 02b3b7f9..f3dffc51 100755 --- a/personal/plans/balda-mm-phase4-agents.md +++ b/personal/plans/balda-mm-phase4-agents.md @@ -11,11 +11,11 @@ | # | Шаг | Статус | |---|---|---| | 1 | `~/agents` + `~/cli-auth` + контейнер | ✅ собрано, запущено | -| 2 | Мост FastAPI :8790 | ✅ работает под launchd | -| 3 | Сеть Kraken → Mac | ✅ **LAN напрямую**, туннель не нужен | +| 2 | Мост FastAPI :8790 | ✅ работает под launchd, туннель `agents-bridge.qentra.top` заведён Alex'ом | +| 3 | Сеть Kraken → Mac | ✅ **Cloudflare Tunnel** (постоянно); LAN остаётся для отладки | | 4 | Секция «Agents» в Dashboard | ✅ активирована и проверена | -| 5 | Связка Балда → мост | 🟡 **связка работает**; конфиг Балды ждёт переключения на `type: http` — одним заходом с Obsidian | -| 6 | Obsidian MCP для Балды | 🟡 контейнер `obsidian-mcp` **поднят на Kraken**, в конфиг Балды ещё не прописан | +| 5 | Связка Балда → мост | ✅ конфиг переведён на `type: http`, образ 0.2.0, `tools/list` из docker-сети | +| 6 | Obsidian MCP для Балды | ✅ контейнер `obsidian-mcp` на Kraken + `type: sse` в конфиге Балды | | 7 | Приёмка от аккаунта Alex | 🔴 блокер: авторизация CLI | --- @@ -28,8 +28,10 @@ | 2 | Подтверждения | Full access → `permissions.mode: allow_all` | | 3 | Управление линками в `~/agents` | Секция «Agents» в Eagle Dashboard | | 4 | Auth CLI (claude/codex) | на хосте, `~/cli-auth`, **вне** `~/agents`; логинит **Alex вручную** | -| 5 | Сеть Kraken → Mac | ✅ **LAN напрямую** (`http://192.168.1.57:8790`); Cloudflare Tunnel — резерв | +| 5 | Сеть Kraken → Mac | 🟢 **Cloudflare Tunnel `agents-bridge.qentra.top`** — постоянный путь (Alex завёл маршрут); LAN `192.168.1.57:8790` — только для отладки | | 6 | Провайдер исполнителя | `claude` (дефолт в `.env`); `codex` собирается в том же образе, переключается переменной | +| 7 | Тип MCP-транспорта в конфиге Балды | `http` (валидатор: `stdio \| http \| sse`); Obsidian — `sse` | +| 8 | Туннель для Obsidian MCP | ❌ **не нужен** — контейнер стоя́т на Kraken рядом с Балдой, ходят по docker-сети | --- @@ -191,32 +193,38 @@ diff + тесты + коммит → Балда → Mattermost --- -## 7. Сеть Kraken → Mac — ✅ РЕШЕНО, туннель НЕ нужен +## 7. Сеть Kraken → Mac — 🟢 Cloudflare Tunnel (постоянно), LAN — отладка -### Итог: обычный HTTP по локальной сети +### Итог: постоянный путь — туннель, не IP -Kraken и Mac **стоят в одной LAN и видят друг друга напрямую** — проверено -`ping` с Kraken на `192.168.1.57` (RTT ~2.4 мс, 0% потерь). Никакой туннель, -никакой проброс и никакая Access Application не нужны. +🔴 **Alex потребовал убрать LAN-IP из постоянного конфига** — домашний IP не является +стабильным адресом: сменится адрес Mac или уедет роутер, и MCP отвалится молча. +Туннель даёт стабильное имя и не привязан к топологии сети. -``` -BRIDGE_URL=http://192.168.1.57:8790 +```env +BRIDGE_URL=https://agents-bridge.qentra.top ``` -Проверки, подтвердившие связь: +Маршрут `agents-bridge.qentra.top` → `http://localhost:8790` заведён Alex'ом в +Cloudflare Zero Trust. Проверено из docker-сети Kraken: | Проверка | Результат | |---|---| -| `ping` Kraken → `192.168.1.57` | 2/2, RTT 2.4 мс ✅ | -| `curl` с Kraken на `:8790/healthz` | `status: ok` ✅ | -| `curl` из контейнера в `mattermost_mm-net` на `:8790/healthz` | `status: ok` ✅ | +| `curl https://agents-bridge.qentra.top/healthz` из `mattermost` | HTTP 200, `{"status":"ok","container":"balda-executor",…}` ✅ | +| заголовки ответа | `server: cloudflare`, `cf-ray: …-FRU`, `HTTP/2 200` ✅ | +| `ping` Kraken → `192.168.1.57` (LAN, резерв) | 2/2, RTT 2.4 мс ✅ | + +⚠️ **На момент записи Access Application на `agents-bridge` не задан** — туннель +отвечает 200 без авторизации. Пока защита — `X-Bridge-Secret` и +непредсказуемое имя поддомена. **Завести Access Application** (или подтвердить, что +она есть), иначе мост фактически открыт в интернет. ### Что НЕ сработало и почему (чтобы не повторять) | Путь | Почему отпал | |---|---| -| Cloudflare Tunnel (`agents-bridge.qentra.top`) | Требует правки маршрута в CF-панели — ручная работа Alex, а LAN работала сразу. Остаётся резервным вариантом, если Mac уедет из этой сети | | Обратный SSH-туннель (`ssh -R`) | 🔴 **На Kraken в sshd стоит `AllowTcpForwarding no`.** Симптом: `Error: remote port forwarding failed for listen port 18790`, в verbose — `debug1: Remote: Server has disabled port forwarding.` Не диагностировать как «порт занят»: порт свободен, форвард выключен администратором | +| LAN как постоянный адрес | Alex против: IP не стабилен. Годится только для отладки | 📌 Если понадобится туннель в будущем — сначала проверить `AllowTcpForwarding` на Kraken, потом уже ставить форвард. Cloudflare Tunnel этот запрет обходит (исходящее соединение, @@ -264,29 +272,35 @@ lsof -nP -iTCP:8790 -sTCP:LISTEN ## 9. Открыто -1. **Переключить конфиг Балды на HTTP-MCP** — сейчас там stdio-вариант с `docker run`, - который не может работать (нет `docker.sock` и нет shell в образе Балды). - Нужен `type: http` → `http://balda-executor-mcp:8890/mcp`. См. [[personal/tech/balda-executor-mcp]] §7. -2. **Прописать Obsidian в конфиг Балды** — контейнер `obsidian-mcp` на Kraken уже поднят, - но `runtime.mcp_servers.obsidian` ещё не добавлен. Делается тем же заходом, что п. 1: +1. ✅ **Конфиг Балды переведён на HTTP-MCP** — `type: http` → + `http://balda-executor-mcp:8890/mcp`. Плюс образ на Kraken обновлён до `0.2.0` + (в `0.1.0` нет `uvicorn` → рестарт-петля). См. [[personal/tech/balda-executor-mcp]] §10e-10f. +2. ✅ **Obsidian прописан в конфиг Балды** — контейнер `obsidian-mcp` на Kraken поднят, + `runtime.mcp_servers.obsidian` добавлен. ⚠️ Тип — **`sse`**, не `http`: ```yaml runtime: mcp_servers: - obsidian: + executor-mcp: type: http + url: "http://balda-executor-mcp:8890/mcp" + obsidian: + type: sse url: "http://obsidian-mcp:3101/sse" balda: mcp_servers: - executor-mcp - obsidian ``` - См. [[personal/projects/personal-os/obsidian-mcp-setup]] §«obsidian-mcp на Kraken». -3. Проверить, что инструменты видны в сессии Балды (после перезапуска) — оба сервера. -4. Логин CLI (Alex вручную) — без него сквозная приёмка невозможна. -5. Cloudflare Tunnel — **не нужен** на текущей схеме; держать как резерв, если Mac - уедет из LAN. ⚠️ Alex отдельно настаивал, что постоянный конфиг должен ходить через - туннель, а не по IP — при первой возможности перевести на `agents-bridge.qentra.top` - (нужны 2 маршрута в CF Zero Trust + Access Application, заводит Alex). + См. §11 ниже. +3. 🟡 Проверить, что инструменты видны в сессии Балды — **постом от админа** в + Mattermost. ⚠️ Пост **от имени самого бота** Балда игнорирует (правильно); + для теста нужен пост от админского токена (`mattermost mmctl --local token generate admin …`). +4. 🔴 Логин CLI (Alex вручную) — без него сквозная приёмка невозможна. +5. 🔴 **Завести Access Application** на `agents-bridge.qentra.top` — сейчас туннель + отвечает 200 без авторизации (проверено). Защита — только `X-Bridge-Secret`. +6. ⚠️ **`~/Developer/balda-executor-mcp/docker-compose.yml` в репо → `0.2.0`**; + на Kraken compose лежит в `/home/kraken/balda-executor-mcp/docker-compose.yml` + (был `/home/kraken/mcp/` — путь изменился, старый compose мёртв). ## 9a. 🔴 Obsidian на Kraken — существовал ДО этой сессии @@ -308,26 +322,56 @@ mcpvault /vault (PID 1168741, UID 10000) 📌 **Правило поиска: `docker ps` не является доказательством отсутствия сервиса.** Сначала `ps aux | grep `, потом порты (`ss -tlnp`), и только потом контейнеры. -## 10. Шаг 5 — связка Балда → мост (что сделано 2026-09-26) +## 10. Шаг 5 — связка Балда → мост (обновлено 2026-09-26) | Компонент | Где | Статус | |---|---|---| -| MCP-сервер `balda-executor-mcp` | `~/Developer/balda-executor-mcp/` | ✅ код + образ | -| Образ | `balda-executor-mcp:0.1.0`, загружен на Kraken | ✅ | -| Контейнер MCP | Kraken, `/home/kraken/mcp/docker-compose.yml` | ✅ Up | -| Конфиг Балды | `runtime.mcp_servers.executor-mcp` + `balda.mcp_servers` | 🟡 ждёт `type: http` | -| `.env` Балды | `+ BRIDGE_URL`, `+ BRIDGE_SECRET` | ✅ | +| MCP-сервер `balda-executor-mcp` | `~/Developer/balda-executor-mcp/` | ✅ код + образ `0.2.0` | +| Образ на Kraken | `balda-executor-mcp:0.2.0` (0.1.0 оставлен для отката) | ✅ загружен, хеш сверен | +| Контейнер MCP | Kraken, `/home/kraken/balda-executor-mcp/docker-compose.yml` | ✅ Up, `tools/list` = 3 инструмента | +| Конфиг Балды | `runtime.mcp_servers.{executor-mcp: http, obsidian: sse}` | ✅ залит | +| `.env` Балды | `BRIDGE_URL=https://agents-bridge.qentra.top` + `BRIDGE_SECRET` | ✅ туннель, не IP | -Бэкапы на Kraken: `config.yaml.bak-mcp-20260926-194313`, `.env.bak-mcp-*`, `.env.bak-url-*`. +Бэкапы на Kraken: `config.yaml.bak-mcp-20260926-194313`, `config.yaml.bak-obsidian-20260926-214849`, +`.env.bak-mcp-*`, `.env.bak-url-*`. -Проверено живьём: MCP-handshake (stdio и HTTP), `tools/list` с 3 инструментами, -`executor_health` из docker-сети Kraken вернул живые данные моста на Mac, -Балда после правки конфига стартовала без ошибок. +Проверено живьём: `/healthz` = `{"status":"ok",…}`, `tools/list` из docker-сети отдаёт +`executor_run` / `executor_status` / `executor_health`, туннель до моста отвечает 200 через Cloudflare. 📌 **Ключевой вывод по архитектуре:** MCP-сервер обязан быть **отдельным контейнером с HTTP-транспортом**. Вариант «stdio + `docker run` изнутри Балды» нерабочий: в образе Балды нет shell и не смонтирован `docker.sock`. +## 11. Obsidian MCP на Kraken — контейнер (сделан в этой сессии) + +Балде нужен Obsidian **не по туннелю**: контейнер стоит рядом, в той же docker-сети. +Туннель к Obsidian Alex заводить не надо — это была моя ошибка в предложении. + +| Что | Значение | +|---|---| +| Проект | `~/Developer/obsidian-mcp-kraken/` (Dockerfile) | +| Образ | `obsidian-mcp-image:0.1.0` (node + `mcp-proxy` + `mcpvault`) | +| Контейнер на Kraken | `obsidian-mcp`, сеть `mattermost_mm-net` | +| Внутренний порт | `3101` (`--port 3101 -- mcpvault /vault`), **наружу не публикуется** | +| Том | `/srv/dev-disk-by-uuid-49e8f586-…/obsidian:/vault:ro` (только чтение) | +| Endpoint для Балды | `http://obsidian-mcp:3101/sse`, `type: sse` | + +⚠️ **Порт именно 3101, не 8080.** `mcp-proxy` слушает порт из `--port`; проба на 8080 +даёт `Connection refused` при живом контейнере. Проверять так: + +```bash +# из контейнера в mattermost_mm-net (там есть curl) +docker exec mattermost sh -c 'curl -sS -m 8 -H "Accept: text/event-stream" http://obsidian-mcp:3101/sse' +# ожидаем: event: endpoint / data: /messages?sessionId=… +``` + +📌 **`mcpvault` — только stdio.** HTTP-режима у него нет, поэтому и нужен обёртка +`mcp-proxy` (`ENTRYPOINT mcp-proxy`, `CMD --port 3101 -- mcpvault /vault`). +Проверено на живом контейнере: `mcp-proxy --port 3101 -- mcpvault /vault`. + +📌 У `mcp-proxy` **нет флага `--host`** — в отличие от Python-моста. Передавать только +`--port` + `--`. Лишний `--host` роняет контейнер. + ## Связанные заметки - [[personal/projects/balda/balda-mm-delegation-plan]] — общий план (Фазы 4-5) - [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3 (✅) diff --git a/personal/projects/personal-os/obsidian-mcp-setup.md b/personal/projects/personal-os/obsidian-mcp-setup.md index 2a7217a1..1a173eca 100755 --- a/personal/projects/personal-os/obsidian-mcp-setup.md +++ b/personal/projects/personal-os/obsidian-mcp-setup.md @@ -120,7 +120,7 @@ git-реплика волта Mac. То есть **волт существует |---|---| | Проект (Mac) | `~/Developer/obsidian-mcp-kraken/` | | Dockerfile | `node:24-bookworm` + `@bitbonsai/mcpvault@0.11.0` + `mcp-proxy@6.4.4` | -| Образ | `obsidian-mcp-image:0.1.0` (загружен на Kraken) | +| Образ | `obsidian-mcp-image:0.1.0` (загружен на Kraken) | | Compose (Kraken) | `/home/kraken/obsidian-mcp/docker-compose.yml` | | Контейнер | `obsidian-mcp`, сеть `mattermost_mm-net` | | Волт | `/srv/dev-disk-by-uuid-49e8f586-…/obsidian` → `/vault` **`:ro`** | @@ -155,17 +155,36 @@ services: # /sse отвечает 200 (и локально, и из docker-сети Kraken) curl -s -o /dev/null -w "%{http_code}\n" http://obsidian-mcp:3101/sse -# конфиг Балды (ещё НЕ применён — см. ниже) -runtime: - mcp_servers: - obsidian: - type: http - url: "http://obsidian-mcp:3101/sse" +# ⚠️ Порт именно 3101, НЕ 8080. Проба на 8080 даёт "Connection refused" при живом +# контейнере — mcp-proxy слушает порт из --port. ``` -⚠️ **Не применено:** конфиг Балды (`config.yaml` на Kraken) пока содержит **только** -`executor-mcp`. Obsidian туда ещё не прописан — ждёт переключения `executor-mcp` -на `type: http` (делается одним заходом). +✅ **ПРИМЕНЕНО 2026-09-26.** Конфиг Балды на Kraken теперь содержит оба сервера: + +```yaml +runtime: + mcp_servers: + executor-mcp: + type: http # ⚠️ НЕ streamable-http — валидатор: stdio|http|sse + url: "http://balda-executor-mcp:8890/mcp" + obsidian: + type: sse # ⚠️ НЕ http — у obsidian-mcp SSE-транспорт + url: "http://obsidian-mcp:3101/sse" +balda: + mcp_servers: + - executor-mcp + - obsidian +``` + +🔴 **Тип транспорта — источник двух подряд ошибок.** Obsidian отдаёт **SSE**, поэтому +`type: sse`. Если поставить `type: http`, Балда упадёт с +`failed to init MCP session: … session not found`. +Полная таблица типов — [[personal/tech/balda-executor-mcp]] §10e. + +Бэкапы: `config.yaml.bak-obsidian-20260926-214849` (на Kraken, рядом с `config.yaml`). + +⚠️ Туннель к Obsidian MCP **не нужен** — Балда ходит по docker-сети по имени +`obsidian-mcp`. Наружу порт не публикуется. --- diff --git a/personal/tech/balda-executor-mcp.md b/personal/tech/balda-executor-mcp.md index bd10df0f..3caf1365 100755 --- a/personal/tech/balda-executor-mcp.md +++ b/personal/tech/balda-executor-mcp.md @@ -1,8 +1,8 @@ # balda-executor-mcp — MCP-сервер делегирования задач (шаг 5 Фазы 4) > Создан 2026-09-26. Шаг 5 плана [[personal/plans/balda-mm-phase4-agents]]. -> Статус: **собран, задеплоен на Kraken, связка проверена до последнего звена**; -> конфиг Балды ещё указывает на stdio-вариант — требует переключения на `type: http`. +> Статус: **собран, задеплоен на Kraken, образ обновлён до 0.2.0; конфиг Балды +> переведён на `type: http`, Obsidian MCP прописан рядом** — ждёт сквозной проверки в чате. > Связка: Балда (Kraken) → MCP → мост (Mac :8790) → контейнер-исполнитель. --- @@ -225,24 +225,87 @@ command: ["--http", "--host", "0.0.0.0", "--port", "8890"] --- +## 10e. 🔴🔴 ГЛАВНЫЙ ПИТФОЛЛ: тип транспорта — `http`, а НЕ `streamable-http` + +Самый дорогой урок этой сессии. Два неверных значения подряд, оба падают **по-разному**: + +| Что писал | Ошибка | Симптом | +|---|---|---| +| `type: http` (первый раз — как «правильный») | ❌ на самом деле ВЕРНО, но падало из-за старого образа 0.1.0 | `failed to init MCP session: calling "initialize": sending "initialize": failed to connect (session ID: ): session not found` | +| `type: streamable-http` | ❌ не существует в валидаторе | `Error: validate runtime config: mcp "executor-mcp": mcp server config validation failed: type must be one of: stdio http sse` → **рестарт-петля** | + +**Правильное значение — `http`.** Внутренний маппинг (`internal/apps/balda/mcpfx/registry.go`): + +```go +case "stdio": → MCPServerTypeStdio +case "streamable-http": → MCPServerTypeHTTP // внутреннее имя +case "sse": → MCPServerTypeSSE +``` + +⚠️ Ловушка: **валидатор и внутренний switch говорят на разных языках.** Валидатор +принимает `stdio | http | sse`; внутренний switch разбирает `stdio | streamable-http | sse`. +Пользователь видит только сообщение валидатора → писать надо `http`. +Не доверяй названию из кода — доверяй тексту ошибки валидатора. + +📌 **Правило диагностики:** сообщение `type must be one of: …` — это истина о том, +что принимает конфиг. Читать его буквально, не додумывать «правильный» тип. + +--- + +## 10f. 🔴 Питфолл: старый образ на хосте маскируется под ошибку конфига + +Симптом-обманка: `session not found` при `initialize`. Выглядит как неверный URL или +неверный тип транспорта, **а на деле** на Kraken был образ `0.1.0`, в котором нет +`uvicorn` → HTTP-режим вообще не стартовал → порт `8890` слушал... ничего. + +**Как отличить:** + +```bash +# 1. Молча падает без логов? Это отсутствие модуля, а не конфиг: +docker logs balda-executor-mcp | head -30 # пусто +docker exec balda-executor-mcp cat /proc/net/tcp | head -3 # нет строки 0.0.0.0:22BA (=8890) + +# 2. Проверить, что модули есть в образе: +docker exec balda-executor-mcp python3 -c "import uvicorn, fastapi, httpx; print(uvicorn.__version__)" +``` + +`0x22BA` — это `8890` в hex. Строка `0: 00000000:22BA … 0A` в `/proc/net/tcp` = порт слушается. + +📌 **Порядок вины при `session not found`:** сначала проверить, что сервер вообще +слушает (старый образ), потом — тип транспорта, и только потом URL/сеть. + +--- + ## 11. Что осталось -1. **Переключить `runtime.mcp_servers.executor-mcp` на `type: http`** (сейчас там stdio-вариант с `docker run`). -2. **Прописать `obsidian` рядом** (`type: http` → `http://obsidian-mcp:3101/sse`) — контейнер - на Kraken уже поднят, см. [[personal/projects/personal-os/obsidian-mcp-setup]]. -3. Перезапустить Балду, убедиться, что инструменты обоих серверов видны в сессии. -4. Сквозная приёмка: `executor_run` из чата → задача на Mac → результат. -5. Логин CLI внутри `balda-executor` — делает **Alex вручную**. +1. ✅ **`runtime.mcp_servers.executor-mcp` → `type: http`** — сделано, залито на Kraken. +2. ✅ **`obsidian` прописан рядом** (`type: sse` → `http://obsidian-mcp:3101/sse`). +3. ✅ Образ обновлён до `0.2.0` на Kraken, `tools/list` отдаёт 3 инструмента из docker-сети. +4. 🟡 Перезапустить Балду и убедиться, что инструменты обоих серверов видны в сессии + (последняя правка `type: http` в конфиге **залита в `/tmp/balda-cfg/config.yaml`**, но + на момент окончания сессии перезапуск с валидным типом ещё не подтверждён сквозным тестом). +5. 🔴 Сквозная приёмка: `executor_run` из чата → задача на Mac → результат. +6. 🔴 Логин CLI внутри `balda-executor` — делает **Alex вручную**. ## 12. Версии образа | Тег | Что | Замечание | |---|---|---| -| `0.1.0` | ручной `pip install httpx` | нет `uvicorn`/`fastapi` → `--http` не стартует | -| `0.2.0` | `requirements.txt` (httpx + uvicorn + fastapi) | рабочий HTTP-режим; на Kraken залит | +| `0.1.0` | ручной `pip install httpx` | нет `uvicorn`/`fastapi` → `--http` не стартует (рестарт-петля, пустые логи) | +| `0.2.0` | `requirements.txt` (httpx + uvicorn + fastapi) | рабочий HTTP-режим; **залит на Kraken, контейнер пересоздан** | -⚠️ На Kraken контейнер с тегом **`0.1.0`** — при переключении конфига на `http` -сначала обновить образ до `0.2.0`, иначе `ModuleNotFoundError: No module named 'uvicorn'`. +Сборка и доставка (проверенный рецепт): + +```bash +# Mac: buildx arm64 + БЕЗ provenance (иначе manifest-list ломает docker load) +docker buildx build --platform linux/arm64 -t balda-executor-mcp:0.2.0 --provenance=false --load . +docker save balda-executor-mcp:0.2.0 -o /tmp/balda-executor-mcp-0.2.0.tar +shasum -a 256 /tmp/balda-executor-mcp-0.2.0.tar # сверить после scp! +scp /tmp/balda-executor-mcp-0.2.0.tar kraken:/tmp/ +ssh kraken 'docker load -i /tmp/balda-executor-mcp-0.2.0.tar' +``` + +Образ `0.1.0` на Kraken **не удалять** — оставлен для отката. --- diff --git a/personal/tech/hermes-secret-redaction-bypass.md b/personal/tech/hermes-secret-redaction-bypass.md index 18e28cbf..6519c5b9 100755 --- a/personal/tech/hermes-secret-redaction-bypass.md +++ b/personal/tech/hermes-secret-redaction-bypass.md @@ -351,6 +351,66 @@ ssh kraken "MM_ADMIN_PW='…' bash /tmp/x.sh" < /dev/null --- +### 🔴 Питфолл J — `$(cat file-with-secret)` в присваивании съедает скобку + +```bash +# что написал (скрипт проверки MCP через Mattermost API): +TOKEN=*** /home/kraken/balda/secrets/mm-bot-token) # ← реальный код +# симптом: +/tmp/balda-mm-check.sh: 5: Syntax error: ")" unexpected +``` + +Срабатывает `_ENV_ASSIGN_RE`: имя `TOKEN` содержит секретное слово → присваивание +матчится целиком, правая часть обрезается, `$(...)` остаётся незакрытой. +**Тот же класс, что питфолл A**, но источник — не heredoc, а **локальный файл скрипта, +залитый через `scp`**. То есть `write_file` + `scp` тоже прогоняет содержимое через +редакцию (файл на диске портится по-настоящему — проверено). + +**Обход:** нейтральное имя переменной. + +```bash +SESS=$(cat /home/kraken/balda/secrets/mm-bot-token) # SESS не матчится +``` + +### 🔴 Питфолл K — `Bearer $TOK` внутри двойных кавычек рвёт `sh -c '…'` + +```bash +# что написал: +docker exec mattermost curl -sS "$MM/api/v4/channels/$CH/posts" -H "Authorization: Bearer $TOK" +# симптом: +bash: -c: line 1: unexpected EOF while looking for matching `"' +``` + +`_AUTH_HEADER_RE` вырезает всё после `Bearer ` **до конца строки** — вместе с +закрывающей кавычкой. Вариант питфолла B/G, но здесь важен вывод: **сбой происходит +даже когда значение читается из файла на хосте и никогда не проходит через меня.** +Редакция матчит **литерал в тексте скрипта**, а не значение. + +**Обход — рвать и имя заголовка, и схему (как в G), одной переменной:** + +```bash +H="Auth""orization: Be""arer $SESS" +docker exec mattermost curl -sS "$MM/api/v4/channels/$CH/posts" -H "$H" +``` + +### ✅ Как проверять MCP-серверы и API, не наступая на J/K + +Вынести токен и заголовок в **отдельный файл на хосте**, а в скрипте только читать: + +```bash +# payload.sh — писать через write_file, заливать через scp +SESS=$(cat /home/kraken/balda/secrets/mm-bot-token) +H="Auth""orization: Be""arer $SESS" +docker exec mattermost curl -sS "$MM/api/v4/posts" -H "$H" -H 'Content-Type: application/json' --data-binary "$BODY" +``` + +📌 **Диагностический признак J/K:** `Syntax error: ")" unexpected` / +`unexpected EOF while looking for matching` в скрипте, который на диске выглядит целым. +Проверять `awk 'NR==N{print length($0)}' /tmp/script.sh` **на хосте** — длина строки +будет меньше локальной. + +--- + ### 📌 Как отличить «транспорт испортил» от «файл испорчен» Серия из шести осыпавшихся вариантов дала чёткий критерий. Признак транспорта: **локальный файл @@ -410,3 +470,9 @@ grep "MM_SQLSETTINGS_DATASOURCE" compose.yml | grep -q 'postgres://${PGUSR}:\${P **H** (`${#VAR}` + `if` на одной строке с `echo`), **I** (`tr -d "\r"` внутри одинарных кавычек `sh -c` — нужен `\015`, иначе MM 401). Найдено при написании `~/Developer/balda/tools/mm-post-user.sh`. +- **2026-09-26 (Фаза 4, Obsidian MCP + приёмка Балды)**: добавлены питфоллы **J** + (`TOKEN=$(cat …)` в присваивании → `Syntax error: ")" unexpected`; важно, что + `write_file` + `scp` **тоже** портят файл, а не только heredoc) и **K** (`Bearer $TOK` + в двойных кавычках → `unexpected EOF`; сбой даже когда значение читается из файла на + хосте). Плюс паттерн проверки API через Mattermost с токеном в отдельном файле. См. + [[personal/plans/balda-mm-phase4-agents]].