Поликопи — Полный аудит проекта (2026-06-24)
Аудит: Rust API (агент, ssh Malaysia) + React-фронт (агент, локально) + money-path ядро (Бендер: демон/executor/deposit-watch, ssh Malaysia). Read-only, ничего не менялось. Прод: poly-dev.szhub.space / Malaysia 72.62.247.119.
🔴 HIGH (исправлять в первую очередь)
H1 — /api/polymarket/sign открыт без auth (Rust, routes.rs:927-934, route 3718)
Любой из интернета (через nginx location /) POST-ит {method,path,body} → получает валидную builder-HMAC-подпись (POLYMARKET_BUILDER_SECRET) + возвращает builder apiKey+passphrase. Позволяет форжить authed builder-вызовы под нашей builder-identity. Самая эксплуатируемая дыра.
Фикс: gate по SIWE/Privy-сессии или service-token; не возвращать key/passphrase; whitelist допустимых path/method.
H2 — service_ok timing/naive == на money-эндпоинтах демона (Rust, copytrade.rs:306-310)
/api/copy/{market-resolution,market-tokens,wallets,positions GET+POST} используют наивный == (timing-оракул на секрете, который гейтит запись позиций/resolution-истину). Рядом уже есть hardened service_token_ok (constant-time, строки 37-44), используемый /active и /pnl.
Фикс: 1 строка — пустить service_ok через service_token_ok.
H3 — copy-config не валидируется на клиенте, сырьём уходит на бэкенд (Front, CopyConfigModal + useCopySubscriptions.ts:90)
min/max у NumInput — только HTML-атрибуты, не enforce. Юзер вводит allocValue/maxPerTrade/maxExposure/drawdownStop любым значением (999999, negatives через spinner-bypass) → PUT сырым. Демон это потребляет (sizing позиций).
Фикс: clamp в set()/на сохранении (allocValue% ≤100 & >0; $-поля ≥0 & ≤ разумный cap; drawdownStop 0-100) + серверная валидация (не доверять клиенту). Сейчас клампятся только fadeMin/fadeMax.
🟠 MED
M1 — derive-key/create-key возвращают CLOB secret наружу, без auth (Rust, routes.rs:928-1048)
Публичные. Эхо secret/passphrase в JSON (комментарий говорит "secret never needs to leave backend" — но он уходит, строки 992/1046). Cross-user poisoning смягчён (CLOB валидирует подпись над address). Нет rate-limit → бесплатный прокси к CLOB derive.
Фикс: не возвращать secret/passphrase (только apiKey для отображения); + auth-gate.
M2 — NaN-паника замораживает market-sync (Rust, gamma.rs:532)
.partial_cmp(...).unwrap() паникует на NaN из внешнего Gamma API → spawned cron-таск умирает → 2-мин синк цен/resolution встаёт навсегда (стейл-данные демону) до рестарта.
Фикс: unwrap_or(Ordering::Equal) (паттерн уже есть в copytrade.rs:483).
M3 — priceMin/priceMax не упорядочены (Front, CopyConfigModal:209-214)
Нет guard priceMin ≤ priceMax. Юзер ставит min=60,max=5 → инвертированная полоса = "копировать ничего" молча/undefined.
Фикс: enforce порядок на сохранении + сообщение.
🟡 LOW / заметки
- maxUint256 approve на ONRAMP (front dwRedeem.ts:44 + deposit-watch.mjs wrap) — wrap нужен ровно usdce; системный Polymarket-контракт, низкий риск. Можно approve(ONRAMP, usdce).
- order.ts:107 share-side floor → 0 на суб-cent ордерах; из UI недостижимо (min $1), но демон строит ордера сам.
- deposit-watch DRY default = LIVE: ручной запуск без DRY=1 = реальные транзы (конвертит свои же деньги, низкий риск).
✅ Чисто (проверено)
- Money-path ядро (демон/executor/deposit-watch): durable reserve до ордера (анти-double-buy), dedup через рестарты, atomic state write, re-entrancy guard, reconcile с on-chain ERC1155 verify перед банковкой, resolved/untradeable handling, orphan-cancel, mirror leader-exit. Chunk6 slippage-cap (сегодня). USDC 6-decimals консистентно. Executor sigType3, per-market tick через SDK, creds-кэш root-only.
- deposit-watch автозапуск: pm2 cron */10 мин (status=stopped между тиками = норм). Swap с 0.5% slippage-guard, wrap 1:1.
- Rust: SQL чисто (bound params + whitelist match), нет паник в request hot-path, нет утечек секретов в логи, CORS безопасен (env-origins, no credentials, HttpOnly cookie), нет хардкод-секретов, SIWE nonce + Privy JWKS корректны, IDOR (X-User-Address) уже убран.
- Front: нет утечек секретов в бандл, нет eval/dangerouslySetInnerHTML, нет reverse-tabnabbing, auto-redeem session-guard корректен (анти sign-loop), deriveApiKey inFlight-guard.
- ⚠️ Известный design-risk (вне клиента): server-delegate добавлен БЕЗ policy (addSigners без policyIds) → нет on-chain гарантии "сервер не выведет". TODO на ре-архитектуру.
Приоритет действий
- H1 (sign-oracle) — remote, без кредов, самое опасное
- H2 (service_ok) — 1 строка, лёгкая победа
- H3 (валидация copy-config) — money-path конфиг
- M1 (secret echo) → M2 (NaN panic) → M3 (price ordering)
✅ СТАТУС ФИКСОВ (2026-06-24, ветка redesign-nav, всё на GitHub)
- H2 ✅
7ae0810 — const-time service-token на money-эндпоинтах демона
- H1 ✅
8828bc7 — whitelist sign-route (только POST /submit + GET /transactions; произвольное → 403)
- H3 ✅
253ff66 — validate_config() server-side (Rust) + NumInput clamp (фронт). Закрыл и M3 (priceMin≤priceMax)
- M2 ✅
4a40242 — NaN-safe partial_cmp в gamma-синке
- M1 ✅
1b90f56 — derive/create-key больше не отдают secret/passphrase (только apiKey; secret в cred_store server-side)
- Chunk6 ✅ (ранее) — slippage-cap в демоне
Сознательно НЕ сделано (с обоснованием)
- Auth-gate на H1/M1: derive-key/sign зовутся в онбординге ДО SIWE-логина (логин ручной) → gate сломал бы онбординг. Вместо этого: H1 = whitelist путей, M1 = убран secret-echo. Cross-user poisoning уже смягчён валидацией подписи в CLOB.
- LOW (открыто, низкий риск): maxUint256 approve на ONRAMP (dwRedeem.ts:44 + deposit-watch wrap — системный Polymarket-контракт); order.ts:107 round-to-zero (недостижимо из UI, min $1); rate-limit на derive/create-key.
- Design-risk (вне кода): server-delegate без policy (addSigners без policyIds) — нет on-chain гарантии «сервер не выведет». Требует ре-архитектуры, не правка.
Бэкапы всех изменённых файлов → ~/openclaw-backups/*.pre-{h1,h2,h3,m1,m2}-2026-06-24 на Malaysia.