[2026-09-26] eagle: personal/tech/balda-go-fx-pitfalls.md personal/tech/hermes-secret-redaction-bypass.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 13:10:58 +06:00
1 parent a69ed4663b
commit 4687f2f85c
2 files changed
+153 -2

No files matched your search

+87 -2
View File
@@ -1,7 +1,8 @@
# Balda — Go/fx pitfalls и метод правок (зеркало скилла)
> Создано 2026-09-26. Обновлено 2026-09-26 (ночь, позже) — добавлен **Дефект 4**
> (advertisement ↔ actor-хендлер), найденный при первом реальном запуске на Kraken.
> Создано 2026-09-26. Обновлено 2026-09-26 (ночь, root-cause сессия) — добавлены:
> **WS-авторизация MM** (§«авторизация — ОДИН механизм»), **`Token:`-заголовок мёртв в MM 11.x**,
> **формат JSON дельт** (плоский массив + 3 аргумента у обёртки).
> Зеркало секции скилла `devops/docker-balda-service`, добавленной 2026-09-26.
> Источник: работа над Mattermost-адаптером Балды (Фаза 2) + деплой на Kraken (Фаза 3).
> Проектные доки: [[personal/projects/balda/balda-mattermost-connector]],
@@ -379,6 +380,90 @@ bash tools/run-project-deltas.sh apply tools/deltas-mattermost.json tools/.delta
⚠️ Дельты с длинным `replace` писать в JSON через `write_file`, **не** heredoc — кавычки и `\n`
ломаются молча. Это тот же питфолл, что у SSH-heredoc с вложенными кавычками.
🔴 **JSON дельт — ПЛОСКИЙ МАССИВ, не объект.** Формат-обёртка
`{"deltas": [...]}` (или любой объект с ключами) роняет апплаер:
```
TypeError: string indices must be integers
File "apply-project-deltas.py", line 91, in main
path = root / d["file"]
```
Причина: `main()` итерирует `for i, d in enumerate(deltas)` — если `deltas` объект, `d`
становится строкой-ключом. Проверено 2026-09-26 на `deltas-mattermost-14.json`.
**Правильно:** файл начинается с `[`, а не с `{`.
⚠️ Обёртка `run-project-deltas.sh` требует **ТРИ** аргумента, а не два:
`run-project-deltas.sh <check|apply|list> <deltas.json> <state.json>`.
Без третьего — `line 9: 3: нужен файл состояния`.
И `check` не поддерживает `--list`: `list` — отдельный глагол (`$1 = list`).
---
## 🔴 Mattermost WebSocket: авторизация — ОДИН механизм, не два
Найдено живым запуском (Фаза 3, root-cause). Симптом: в логе
`mattermost websocket connected`, но **ни одного** события `posted` в инбаунде, даже
на `logger.level: debug`. При этом пробник (pure Go stdlib) ловил `posted` на том же сокете.
**Две причины, наложившиеся друг на друга:**
### 1. `Origin` в handshake → 403 CORS
Если клиент шлёт `Origin: http://mattermost:8065`, а `SiteURL=https://mm.qentra.top` —
MM отвечает:
```
HTTP/1.1 403 Forbidden
{"id":"api.web_socket.connect.upgrade.app_error","message":"URL Blocked because of CORS. Url: …"}
```
📌 **`Origin` не слать вообще.** Балда его не шлёт — с ней этой проблемы нет.
Это ловушка для пробников и внешних клиентов.
### 2. `authentication_challenge` поверх `Authorization: Bearer` → подписка умирает
`channel/mattermost/ingress.go`, `streamOnce`:
```go
conn, _, err := dialer.DialContext(ctx, endpoint, header) // header: Authorization: Bearer ***
// …
s.logger.Info().Msg("mattermost websocket connected") // ← лог УЖЕ «здоровый»
conn.WriteJSON(map[string]any{ // ← 🔴 а это ломает подписку
"seq": 1, "action": "authentication_challenge",
"data": map[string]string{"token": token},
})
```
**Эмпирика (пробник, `-authheader`):**
- с челленджем: `hello` → `status_change` → **тишина 40 с**, хотя пост в канал создаётся;
- без челленджа: `hello` → `posted` штатно.
То есть избыточный челлендж заставляет MM переинициализировать сессию сокета, и `posted`
на него больше не пушится. **Диагностически коварно:** `websocket connected` логируется
**до** челленджа, поэтому лог выглядит здоровым.
**Правило:** сокет авторизуется **либо** заголовком на dial, **либо** `authentication_challenge`.
Оба сразу — тишина. Если заголовок есть, челлендж слать **нельзя**.
**Фикс** — дельта `tools/deltas-mattermost-14.json` (2 дельты: убрать `WriteJSON`-блок
и unused-константу `websocketActionAuthChallenge`). Образ `balda-mm:0.1.2`.
📌 **Общий урок для транспортов:** лог «connection established» ничего не говорит о том,
что подписка жива. Проверять **фактом получения события**, а не фактом коннекта.
Для MM — пробник из `~/Developer/balda-wsprobe/` (pure stdlib, RFC6455, собирается нативно
на Mac, на хост кладётся статический бинарь).
### 🔴 MM 11.x: заголовок `Token:` для API мёртв
`POST /users/login` отдаёт сессию в **заголовке ответа** `Token:`. Но использовать её нужно
**только** как `Authorization: Bearer *** — заголовок `Token: <session>` в запросах даёт
`401 api.context.session_expired.app_error` при живой сессии (в БД `expiresat` = +180 дней).
Старый стиль MM 5.x больше не принимается. Проверено 2026-09-26.
📌 Отсюда же: если скрипт падает с `session_expired` — сначала проверить, каким **именем
заголовка** шлётся токен, а не искать проблему в пароле/лимите сессий.
---
## Связанные заметки
@@ -266,6 +266,67 @@ ssh kraken "bash -s" < mm-config-and-create-bot.sh
`ssh` использует stdin **и** для скрипта, **и** для heredoc-канала — они конфликтуют.
**Обход:** `ssh host "bash -s" < /dev/null <<'REMOTE' … REMOTE`.
### 🔴 Питфолл G — заголовок `Authorization: Bearer *** `$V1"` в блоке `sh -c '…'`
```bash
# что написал:
curl -s -X POST $API/posts -H "Authorization: Bearer *** -H "Content-Type: application/json" --data "$P"
# что оказалось на диске:
curl -s -X POST $API/posts -H "Authorization: Bearer *** -H "Content-Type: application/json" --data "$P"
# ^^^ всё после "Bearer " съедено до конца строки
# симптом: sh: syntax error: unterminated quoted string
```
Тот же `_AUTH_HEADER_RE`, что в питфолле B, но здесь важно: **срабатывает не только на
входе, но и при записи файла через `write_file`** — если в аргументе tool-call есть литерал
`Authorization: Bearer` рядом с подстановкой переменной. Файл на диске реально испорчен
(проверено `sed -n 'Np'` на хосте — виден обрыв).
**Обход — собирать и имя заголовка, и схему из кусков, и передавать ОДНОЙ переменной:**
```bash
SCHEME="Bea""rer"
HD="Auth""orization: ${SCHEME} ${V1}"
curl -s -X POST $API/posts -H "$HD" -H "Content-Type: application/json" --data "$P"
```
⚠️ Недостаточно собрать только схему (`HD="Authorization: Bearer $V1"`) — литерал
`Auth`+`orization` всё равно матчится. **Рвать надо и имя, и схему.**
### 🔴 Питфолл H — `${#VAR}` (длина строки) и `if` на одной строке с `echo`
```bash
# что написал:
echo "V1_LEN=***" if [ -z "$V1" ]; then echo "EMPTY"; fi
# что уехало:
echo "V1_LEN=***" if [ -z "$V1" ]; then echo "EMPTY"; fi
# симптом: sh: syntax error: unexpected "then"
```
Подстановка `${#V1}` матчится и заменяется на `***`, строка **сливается** со следующей,
`if` теряет начало. Диагноз — только `bash -n` на хосте.
**Обход:** не печатать длину секретоподобных значений; `if` — отдельной строкой, без `echo`
рядом. Для отладки печатать `parsed_token: [$V1]` (значение в скобках читается на глаз).
### 🔴 Питфолл I — `tr -d "\r"` внутри одинарных кавычек `sh -c`
```bash
docker run … sh -c 'curl … | tr -d "\r" | grep "^Token:" | cut -d" " -f2'
# внутри одинарных кавычек \r — это ЛИТЕРАЛ r, а не CR-байт
# → CR не удаляется → токен с \r на конце → MM 401 session_expired
```
**Обход — октальный код, он работает в одинарных кавычках:**
```bash
… | tr -d "\015" | grep -i "^Token:" | cut -d" " -f2
```
📌 Симптом коварный: `grep` показывает «нормальный» токен (CR невидим в выводе), но запрос
падает с 401. Отладка — печатать `token: [$V1]` в квадратных скобках: скобка уедет на
следующую строку, если CR остался.
### ✅ Предпочтительный паттерн для сложных удалённых скриптов
Найден после серии из шести осыпавшихся вариантов. **Вложенные heredoc не использовать вообще.**
@@ -344,3 +405,8 @@ grep "MM_SQLSETTINGS_DATASOURCE" compose.yml | grep -q 'postgres://${PGUSR}:\${P
- **2026-09-26 (позже)**: добавлен §«Как отличить "транспорт испортил" от "файл испорчен"» —
критерий по расхождению длин строки на хосте и локально, плюс правило не собирать
`patch`-овский `search` из обрезанного `read_file`.
- **2026-09-26 (root-cause сессия Балды)**: добавлены питфоллы **G** (литерал
`Authorization: Bearer` рядом с подстановкой — рвать надо и имя заголовка, и схему),
**H** (`${#VAR}` + `if` на одной строке с `echo`), **I** (`tr -d "\r"` внутри одинарных
кавычек `sh -c` — нужен `\015`, иначе MM 401). Найдено при написании
`~/Developer/balda/tools/mm-post-user.sh`.