From 8ce269c16afa51687c4904fb50af88aec4067cde Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sun, 13 Sep 2026 13:11:00 +0300 Subject: [PATCH] =?UTF-8?q?docs(security):=20SEC-12=20=E2=80=94=20=D1=86?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BD=D0=B5=20=D0=B2=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B4=D0=B8=D1=80=D1=83=D0=B5=D1=82=D1=81=D1=8F=20=D0=B8=20?= =?UTF-8?q?=D0=B2=20=D0=BD=D0=B5custodial-=D0=B1=D1=8D=D0=BA=D0=B5=D0=BD?= =?UTF-8?q?=D0=B4=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проверено на smart-updown-server: GameService.submitBet не сверяет payload.entryPrice со своим priceSource, сравнение JSON-подсказки с tx лишь логирует log.warn и не отклоняет. Клиент подписывает entry_price сам. Зафиксирована инверсия: custodia-шлюз стенда эту дыру закрывает (цена из NATS на сервере), а неcustodial прод-бэкенд открыт. Переход на неcustodial без серверной сверки entry_price ухудшит положение. --- docs/SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/SECURITY.md b/docs/SECURITY.md index c3b75f8..f847c2a 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -35,7 +35,7 @@ - [ ] **SEC-10. NATS без аутентификации** — `auth_required` не выставлен, анонимная публикация в шину проходит (проверено на тестовом subject; боевой не трогали). Пока цена условна — терпимо; как только станет денежной, у шины нет прав на запись и любой сможет напечатать свой курс. Фикс: auth + subject-ACL, шлюзу — read-only на `market.price.*`. - [ ] **SEC-11. Ключ релейера — единственная точка доверия в контракте** — контракт не проверяет соответствие цены рынку и принимает любое значение от подписанта (`create_bet.rs:75`, `close_bet.rs:62`). Модель «релейер = оракул» стандартна, но компрометация одного ключа `E7Hg…` = произвольный исход всех ставок. Фикс: on-chain оракул (Pyth/Switchboard) для боя. -- [ ] **SEC-12. `entry_price` не проверяется контрактом** — передаётся в `create_bet` целиком на усмотрение вызывающего; контракт не может убедиться, что это цена момента `bet_time`. Место: `create_bet.rs:46,75`. Фикс: внешний верифицируемый фид. +- [ ] **SEC-12. `entry_price` не проверяется ни контрактом, ни прод-бэкендом** — передаётся в `create_bet` целиком на усмотрение вызывающего; контракт не может убедиться, что это цена момента `bet_time` (`create_bet.rs:46,75`). **Проверено на прод-бэкенде `smart-updown-server`:** в `GameService.submitBet` цена из подписанной tx НЕ сверяется со своим `priceSource` — `payload.entryPrice` проходит насквозь; сравнение `entryPrice != payload.entryPrice` (`GameService.kt:100-118`) сверяет лишь JSON-подсказку с tx и на расхождении пишет `log.warn` («trusting the signed transaction»), не отклоняя. Валидируются только `paused`, парсинг tx, `payload.id == global.counter` и границы `amount`. **Клиент подписывает `entry_price` сам** → может зашить любое значение (`entry_price=1` → UP выигрывает почти всегда). **Важная инверсия моделей:** сегодня custodia-шлюз стенда эту дыру случайно ЗАКРЫВАЕТ (цена берётся из NATS на сервере, `gateway.ts:280`), а «правильный» неcustodial прод-бэкенд — ОТКРЫТ. Значит переход на неcustodial-модель (`/bet` принимает подписанную tx, как `smart-updown-server`) БЕЗ серверной проверки `entry_price` по своему оракулу сделает хуже, чем есть. Фикс: внешний верифицируемый фид (Pyth/Switchboard) + серверная сверка `entry_price` с допуском до ретрансляции. ## Отложено — зафиксировать как риск