From 9528441c97666c6a05c22ad7dc1e61e2aa6938cc Mon Sep 17 00:00:00 2001 From: Repinoid Date: Mon, 5 Oct 2026 21:38:03 +0300 Subject: [PATCH] =?UTF-8?q?doc(sqs):=20=D0=BC=D0=BE=D0=B9=20=D0=BA=D1=80?= =?UTF-8?q?=D0=B8=D1=82=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=B9=20=D0=B0?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=D0=B8=D0=B7=20=D0=BE=D1=82=D1=87=D1=91=D1=82?= =?UTF-8?q?=D0=B0=20Opus=20(=D0=BF=D0=B5=D1=80=D0=B5=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BA=D0=B0=20=D0=BF=D0=BE=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=B4=D1=83)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../my-analysis-of-opus-report-2026-10-05.md | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 doc/thinking/my-analysis-of-opus-report-2026-10-05.md diff --git a/doc/thinking/my-analysis-of-opus-report-2026-10-05.md b/doc/thinking/my-analysis-of-opus-report-2026-10-05.md new file mode 100644 index 0000000..4890b04 --- /dev/null +++ b/doc/thinking/my-analysis-of-opus-report-2026-10-05.md @@ -0,0 +1,91 @@ +# Критический анализ отчёта Opus (мой разбор, 2026-10-05) + +> Кем: я (DeepSeek). Что: критический разбор отчёта Opus +> (`opus-report-2026-10-05.md`) по архитектуре shared-sqs. +> Метод: ключевые утверждения Опуса перепроверялись по коду напрямую, а не +> принимались на веру. Источники — «файл:строка». + +--- + +## Что я перепроверил по коду (факты) + +| Утверждение Опуса | Вердикт | Источник в коде | +|---|---|---| +| P0.1: SecretKey детерминирован из email + соль в исходнике | ✅ подтверждено | `tenant_store.go:289-292` `secretKeyFromEmail` = `SHA256(lower(email)+":shared-sqs:secret-key:v1")`; соль — константа `secretKeySalt` (`tenant_store.go:286`); `accessKeyFromEmail` = `SHA256(email)[:12]` | +| P0.2: JWT без проверки подписи, доверие на PingNubesAPI | ✅ подтверждено | `jwt.go` — парсинг структуры + `exp`; `PingNubesAPI` принимает, если хоть один стенд не 401/403 | +| Кэш: отозванный токен живёт до ~7 мин | ✅ подтверждено | `cache.go`: `cacheTTL=5m`, `cacheGraceTTL=2m` | +| Глобальный мьютекс на весь сервис | ✅ подтверждено | `globals.go:20-23` — один `SyncQueues.RWMutex` + `Queues map[string]*Queue` | +| Сиротство очередей при пересчёте legacy-тенанта | ✅ подтверждено | `tenant_store.go:202-205` — комментарий в коде сам это описывает | +| MessageRetentionPeriod не реализован | ✅ подтверждено | `models.go` — `// TODO - not used` | + +Ложных утверждений не найдено. + +--- + +## Сильные стороны отчёта + +1. **P0.1 — самая ценная находка.** Детерминированный вывод SecretKey из + публичного email — реальный, доказуемый по коду дефект. Указан не только + механизм, но и точные функции. Именно это реально блокирует «готовность SQS». +2. **Верно разделены SigV4 и JWT.** SigV4 проверяется полноценно + (`auth_middleware.go`: canonical request, `subtle.ConstantTimeCompare`, + clock skew, presigned); JWT — нет. Большинство поверхностных ревью их путают. +3. **Классификация P0/P1/P2 адекватна** — безопасность+durability отделены от + масштабируемости и гигиены. +4. **Пункты 3–7** (глобальный лок, SPOF, async-Redis, нет RetentionPeriod, + плоский слайс) — архитектурно точны. +5. **Раздел «противоречия док↔код» конкретен** (особенно №1: док про + `sub`/`bySub`/`sless-`, код про `email`/`byEmail`/`t-`). + +--- + +## Слабые места и оговорки + +1. **P0.2 переоценён как самостоятельный «критичный».** Вектор «подмена email + → доступ» опасен **только в связке с P0.1**: неверный email даёт чужие креды, + потому что креды детерминированы из email. Исправь P0.1 (случайные креды в + Redis) — и неверный email из JWT уже не даст чужих ключей, только + провиженинг под чужим именем (менее критично). Опус не связал P0.1 и P0.2 + явно, хотя атака работает попарно. Стоит оформить как один инцидент. + +2. **П.4 «SPOF/нет горизонтального масштабирования»** — справедливо + архитектурно, но сервис и проектировался single-instance managed + HTTP-контейнером (README). Это осознанное ограничение, а не баг. В разряд + «высокого риска сейчас» ставить преждевременно — это долгосрочная + архитектурная развилка, а не текущий блокер. + +3. **П.5 (async-Redis = потеря подтверждённых сообщений) — полуверно.** + Сообщение в памяти уже есть (клиент получил 200 = сообщение в памяти, + источнике правды). Теряется только при краше пода **до** async-`HSet`, и + только это узкое окно. Окно реально, но «нарушение durability» подано + сильнее, чем есть: теряются не все сообщения, а только при краше в окне. + +4. **П.10 (timing side-channel admin-токена)** — технически верно, но admin-токен + приходит как высокоэнтропийный bearer; практическая эксплуатируемость timing-атаки + через сеть у одного короткого сравнения ничтожна. Формально в P2 и есть, но + вес в подаче чуть завышен. + +5. **П.14 (`/metrics` публичен)** — стоит оговорка: лейбл `tenant` = tenantID + (`t-...`), а НЕ SecretKey. Утечка реальна (число очередей/сообщений по + тенанту), но это не утечка кредов. Формулировка корректна, но без оговорки + читается страшнее. + +6. **Пробел контекста (не ошибка Опуса, а следствие моего allowlist-запрета):** + отчёт не отделяет «код-блокировку» от «платформенной блокировки» — периодические + таймауты ~31–33с и MSS-баг для тел >~1400 байт (разобрано в + `doc/thinking/nubes-ticket.md`). Даже идеальный код их не уберёт. Итоговый + вывод о «готовности SQS» надо читать с этой поправкой. + +--- + +## Итоговая оценка + +Отчёт — **добротный, по фактам точный**. Из 16 пунктов рисков ложных нет; +спорные — только **вес/приоритет**: +- P0.2 связать с P0.1 (один инцидент); +- п.4 (SPOF) и п.10 (timing) слегка переоценены для текущей single-instance роли. + +Главная ценность: **P0.1 (детерминированный SecretKey)** — подтверждённый по +коду блокер готовности, который до этого так жёстко не формулировался. +Единственный реальный пробел — платформенный контекст (таймауты/MSS), но он +возник из моего же allowlist и устраняется дополнением, не исправлением.