[2026-09-26] eagle: personal/tech/balda-go-fx-pitfalls.md personal/tech/hermes-secret-redaction-bypass.md
This commit is contained in:
1 parent
a69ed4663b
commit
4687f2f85c
2 files changed
+153
-2
No files matched your search
@@ -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`.
|
||||
Reference in new issue
Block a user