Commit Graph

10 Commits

Author SHA1 Message Date
Hermes Agent 101a4b60a9 docs(sec): SEC-16 — безвозвратная утечка ренты PDA-ставки с кошелька игрока
Найдено при приёмке боевого домена smart-updown.binom.pw (симптом «SOL не обновляется»).

- SEC-16: при create_bet ренту PDA-аккаунта ставки (1 559 040 lamports = (96+32+128)*6960)
  платит БЕТТОР, а не шлюз; баланс шлюза-админа при этом не меняется (замер whose-fee.cjs).
  Аккаунт не закрывается никогда (в контракте 9ALs четыре инструкции, ни одной close = bettor)
  → рента теряется безвозвратно. В цепи 75 аккаунтов = 0.116928 SOL заперто, растёт линейно.
  Вердикт: на тестах терпимо (маскируется краном, 5 SOL = ~3207 раундов), перед mainnet — блокер.
- TASKS.md: фича «если цена не изменилась — подождать ещё 15 с» (НЕ доделана), заметка про
  боевой домен на общем TS-шлюзе и лаг крана 8–13 с.
- tools: доказательные скрипты (whose-fee, bet-pda-rent, faucet-latency, sol-balance-cycle,
  settle-math-test, backend-full-test) + README с питфоллами замера.
- k8s/smart-updown-api-ingress.yaml: /api,/ws вынесены вне helm (релиз игры их сносил).
2026-09-15 05:57:34 +03:00
subochev b8a1f8dc39 docs(sec): SEC-9 закрыт — возврат залога при ничьей
Сводка: открыто 8 (было 9), закрыто 8. SEC-9 перенесён в закрытые с полной
историей: причина (close_bet.rs:66), замер доли ничьих (33.6%), решение
владельца (правка контракта), состав правок, собственная мутационная приёмка,
деплой и живой эффект (залипшие #0/#1 закрылись сами, ваулт −1000).
Хвост SEC-8 о «вечном залипании» помечен как устранённый.

Инструменты приёмки:
- tie-refund-accept.cjs — ставки циклом, проверка статуса на цепи
  (живой прогон: 6 возвратов из 25 ставок)
- tie-distribution.cjs, policy-economics.cjs — выбор политики ничьей
- vault-check.cjs — разбор Bet-аккаунтов сырыми байтами
2026-09-14 02:19:28 +03:00
subochev 6ee30c39fb docs(security): SEC-2/SEC-3/SEC-8 закрыты, SEC-4 усилен (updown-relayer:6)
- SEC-3 закрыт: ручка POST /close удалена, ставки закрывает сам шлюз (404)
- SEC-8 закрыт: авто-закрытие + одна открытая ставка на адрес (409)
- SEC-2 закрыт по факту: планировщик settle.ts работает, проверено живьём
- SEC-4 усилен: пара UP+DOWN больше не набирается (вторя ставка -> 409)
- SEC-5 частично: счётчик больше не накрутить массово, но /bet без авторизации
- tools/autoclose-check.cjs: сквозной живой тест (9/9 пройдено)
2026-09-13 18:49:37 +03:00
subochev 6e7132e935 docs(security): SEC-1, SEC-2, SEC-4 закрыты по сути — цена закрытия из истории
Механизм: шлюз берёт цену закрытия из локальной истории на момент expire_time
ставки (priceHistory, node:sqlite, ретеншен 24ч), а не текущую. Момент клика
перестаёт влиять на исход — окно выбора времени закрыто.

Живая проверка (ставка №10, релиз updown-relayer:3):
  цена на момент экспирации 9977000000
  текущая цена в момент клика 9976000000
  exitPrice в транзакции 9977000000  -> взята история, не 'сейчас'
  статус payout_done

Также: tools/ — скрипты проверки (price-history-probe, exploit-decisive,
exploit-check, check-close-window.sh).

Остаток: в контракте верхней границы окна по-прежнему нет (второй слой, не критично);
серверное авто-закрытие — следующая итерация.
2026-09-13 14:47:21 +03:00
subochev 4caddd6a73 docs(security): SEC-6 ЗАКРЫТ — гейт test/release + genesis, начисление вынесено из /bet
Проверено на живом стенде (релизы updown-relayer:2, test-front:3):
- /bet без address -> 400, ничего не начисляет
- /bet на непополненный кошелёк -> 400 insufficient funds
- полный круг POST /wallet -> GET /faucet -> POST /bet -> 200
- smart.mode=release -> 403 'faucet is disabled: only test mode'
- npm test 24/24, мутационная приёмка: ослабление проверки кластеров роняет 2 теста

Остаточный риск: ручка публично маршрутизируется; в бой — mode=release.
2026-09-13 13:39:09 +03:00
subochev 8ce269c16a docs(security): SEC-12 — цена не валидируется и в неcustodial-бэкенде
Проверено на smart-updown-server: GameService.submitBet не сверяет
payload.entryPrice со своим priceSource, сравнение JSON-подсказки с tx
лишь логирует log.warn и не отклоняет. Клиент подписывает entry_price сам.

Зафиксирована инверсия: custodia-шлюз стенда эту дыру закрывает (цена из
NATS на сервере), а неcustodial прод-бэкенд открыт. Переход на неcustodial
без серверной сверки entry_price ухудшит положение.
2026-09-13 13:11:00 +03:00
subochev 5b47de0870 docs(security): SEC-6 — назначение ручки vs гарантия её отсутствия на проде
Зафиксировано разделение: ручка тестовая по назначению (faucet-TASK.md) и на
проде не нужна, однако проверка сети в коде отсутствует, а дефолт
SMART_ALLOW_AIRDROP='1' (config.ts) — открыт. Значит 'не нужна' держится
памятью деплойщика, а не кодом. Фикс: дефолт '0' + getGenesisHash() в handleFaucet.
2026-09-13 12:54:08 +03:00
subochev 08ea780f7d docs(security): SEC-6 — уточнено, что фантиковая часть не localnet-only
Разделены два механизма ручки /faucet:
(а) SOL-налив (requestAirdrop) — технически только localnet, mainnet-RPC
    отдаёт 410 (проверено вживую);
(б) налив фантиков (mintTo) — НЕ привязан к сети, гейт только конфиг-флаг
    SMART_ALLOW_AIRDROP, проверки кластера в коде нет;
    SOL-барьер обходится: при балансе >=2 SOL airdrop не зовётся (ops.ts:204)
    и выполнение доходит прямо до mintTo.
Отмечено, что на боевом мемкойне pump.fun (revoked mint authority) часть (б)
исчезает сама.
2026-09-13 12:46:11 +03:00
subochev 1d49468920 docs(security): нумерация пунктов чек-листа SEC-1..SEC-15
Сквозные номера не сбиваются при добавлении/закрытии пунктов внутри
разделов. В SEC-4 фикс ссылается на SEC-1/SEC-2. В сводке — раскладка
по важности.
2026-09-13 12:42:27 +03:00
subochev 18ecbb42b3 docs(security): чек-лист безопасности игрового контура (аудит живого стенда)
15 пунктов, отмечаются [ ]/[x]. Критичное найдено воспроизведением на
testfront.binom.pw, не чтением кода:
- close_bet проверяет только нижнюю границу окна (now >= expire_time) —
  верхней нет, ставка висит open бесконечно, момент расчёта выбирает
  вызывающий /close;
- в шлюзе нет авто-закрытия (единственный setInterval — WS-heartbeat);
- /bet, /close, /faucet открыты наружу без авторизации; faucet выдал
  5 SOL + 10000 фантиков при SMART_ALLOW_AIRDROP=1 в боевом поде;
- эксплойт воспроизведён: ставка DOWN (entry=9998000000) висела open >2 мин
  после экспирации 15с и закрыта при exitPrice=9997000000 -> payout_done.
Плюс: множитель 1.3x требует 77% винрейта, запертые средства в ваулте,
равные цены не финализируют ставку, NATS без auth, релейер — точка доверия.
2026-09-13 12:40:51 +03:00