9.1 KiB
Phase 2.4 — Seed Pre-Commit Gate Report (Eliminating Seed Grinding)
Date: 2026-08-20
Base Commit SHA: 9927e74421223135a170de640255803ab513fd48
Result Commit SHA: 78151572bd2ae01645d70a0768c6ece517e2cab0
Status: IMPLEMENTED / READY FOR INDEPENDENT RE-REVIEW
Assigned Agent: Antigravity (Implementation Orchestrator)
1. Executive Summary
Закрыта критическая уязвимость манипуляции результатами розыгрышей (Seed Grinding / Pre-computation attack), при которой организатор мог локально перебрать seed'ы на открытом списке участников и передать в POST /api/giveaways/[id]/draw подобранный seed, гарантирующий победу нужного участника при успешном статусе верификации verified: true.
Реализована схема Cryptographic Seed Pre-Commitment:
- Клиентский
seedполностью исключён из входных схем (createGiveawaySchema,executeDrawSchema). Попытка передатьseedв теле запросаPOST /drawстрого отклоняется со статусом400 VALIDATION_ERROR. - Seed генерируется на сервере исключительно через CSPRNG (
generateCryptoSecureSeed()) в момент создания и блокировки неизменяемого слепка участников (createAndLockSnapshot) и сохраняется в БД (Giveaway.seed) в единой атомарной операции. - До момента проведения жеребьёвки (
DRAWN) открытыйseedскрыт от клиента во всех эндпоинтах (POST /api/giveaways/[id]/snapshot,GET /api/giveaways/[id],GET /api/giveaways,GET /api/giveaways/[id]/participants). Клиенту отдаётся только криптографическое обязательствоseedCommitment = sha256(seed). - Роут жеребьёвки
POST /api/giveaways/[id]/drawчитает seed строго из базы данных (giveaway.seed). Любой fallback на генерацию seed в роуте жеребьёвки удалён. Если seed отсутствует — возвращается409 CONFLICT. - После завершения жеребьёвки
seedраскрывается публично (giveaway.seedиdrawResult.seedUsed), позволяя любому участнику подтвердить равенствоsha256(drawResult.seedUsed) === seedCommitmentи математическую честность через независимыйGET /api/giveaways/[id]/verify.
2. Modified Files
| File | Type | Description |
|---|---|---|
src/core/randomizer/hasher.ts |
Core | Добавлена функция computeSeedCommitment(seed: string): string (SHA-256 hex digest). |
src/core/validation/giveaway-schemas.ts |
Validation | Удалено поле seed из createGiveawaySchema и executeDrawSchema (строгая .strict() валидация на draw). |
src/lib/repository/giveaway-repository.ts |
Repository | Добавлено поле seedCommitment?: string | null в GiveawayWithRelations, удален seed из CreateGiveawayInput. |
src/lib/repository/memory-repository.ts |
Storage Driver | Инициализация seed: null, генерация и фиксация seed + seedCommitment в createAndLockSnapshot. |
src/lib/repository/prisma-repository.ts |
Storage Driver | Фиксация seed в БД внутри $transaction при createAndLockSnapshot, маппинг seedCommitment. |
src/app/api/giveaways/route.ts |
API Route | Удалена передача клиентского seed при создании розыгрыша. |
src/app/api/giveaways/[id]/snapshot/route.ts |
API Route | Возврат seedCommitment вместо раскрытия plaintext seed. |
src/app/api/giveaways/[id]/draw/route.ts |
API Route | Строгое чтение pre-committed seed из БД; 409 CONFLICT при отсутствии; удалён fallback. |
src/app/api/giveaways/[id]/route.ts |
API Route | Маскирование seed: null до статуса DRAWN, отдача seedCommitment. |
src/app/giveaways/new/page.tsx |
Frontend UI | Удалено поле ручного ввода seed из шага 4; добавлен индикатор защиты от подбора (Seed Pre-Commitment) со значением SHA-256 commitment. |
tests/api-validation.test.ts |
Tests | Обновлены тесты валидации на строгое отклонение seed. |
tests/seed-precommit-gate.test.ts |
Tests (NEW) | Комплексный adversarial & regression test suite (7 тестов). |
tests/storage-driver.test.ts |
Tests | Исправлен мок IGiveawayRepository (добавлены listGiveawaysSummary и getParticipantsPaginated). |
3. Core Cryptographic Invariants Preserved
Ни один из базовых криптографических алгоритмов НЕ изменялся:
HMAC_SHA256_FY_V1DeterministicHmacStreamexecuteDeterministicDrawV1computeParticipantsSnapshotHashcomputeConditionsHashcomputeDeterministicProofHashcomputeAuditEventHashverifyDrawResult
4. API Contract & Database Migration
- Database Migration Required:
NO(ПолеGiveaway.seedуже существует вprisma/schema.prismaкак nullableString?и готово к сохранению CSPRNG seed). - API Contract Changes:
POST /api/giveaways: полеseedудалено из входящего тела (автоматически отбрасывается.strip()).POST /api/giveaways/[id]/draw: полеseedстрого запрещено в теле запроса (.strict()), возвращает400 VALIDATION_ERRORпри попытке передачи.POST /api/giveaways/[id]/snapshot: в ответ добавлено полеseedCommitment: string(SHA-256 hex от сгенерированного seed).GET /api/giveaways/[id]: в объектеgiveawayвозвращаетсяseedCommitment: string | null. До статусаDRAWNполеgiveaway.seedмаскируется (null), после проведения розыгрыша раскрывается исходныйseed.GET /api/giveaways: полеseedотсутствует вGiveawaySummaryи не утекает в списках.
5. Verification Evidence & Test Gate
Фактически выполненные команды:
npx prisma generate -> Exit code 0 (Prisma Client v5.22.0 generated)
npm test -> Exit code 0 (49 test files, 284 tests passed, 0 failed)
npm run lint -> Exit code 0 (Next.js ESLint passed clean)
npm run build -> Exit code 0 (Next.js production build compiled successfully)
npx tsc --noEmit -> Exit code 0 (Clean TypeScript check)
Regression Tests Summary (tests/seed-precommit-gate.test.ts):
adversarial attempt to pass custom seed in draw body fails with 400 and keeps status SNAPSHOT_LOCKED→ PASSgrinding regression: local brute-force of 100 seeds cannot alter the pre-committed API winner→ PASSdraw attempt on giveaway without locked snapshot and seed returns 409 Conflict→ PASSGET /api/giveaways/[id] masks seed before DRAWN and exposes seedCommitment→ PASSafter DRAWN, sha256(seedUsed) strictly equals seedCommitment and verify endpoint succeeds→ PASSMemoryGiveawayRepository generates and locks seed during createAndLockSnapshot→ PASSPrismaGiveawayRepository maps seedCommitment correctly→ PASS
6. UNVERIFIED Assertions & Tech Debt
-
UNVERIFIED: Prisma integration harness with live DB:
- В текущем тестовом сьюте все функциональные тесты выполняются с драйвером
STORAGE_DRIVER=memory. ХотяPrismaGiveawayRepositoryполностью реализован, компилируется (tsc --noEmit), собирается (npm run build) и покрыт маппинг-тестами, его сквозное выполнение в интеграционном тесте с реальной БД PostgreSQL не автоматизировано в Vitest. - Рекомендация / Proposed Next Task: Добавить тестовый сьют
tests/prisma-integration.test.tsдля запуска прогона репозитория против тестового экземпляра PostgreSQL.
- В текущем тестовом сьюте все функциональные тесты выполняются с драйвером
-
CRITICAL Finding Status:
- Исполнитель не объявляет CRITICAL finding автоматически закрытым самостоятельно. Требуется независимое re-review ревизии.