Covers missing / malformed / forged / blacklisted / unregistered / valid
JWT cases for the JWT-only identity route that replaces the bare-pubkey
auto-restore path. Route does not exist yet — 6/7 fail as expected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Forward client IP via x-forwarded-for/x-real-ip so the canonical arena's
per-IP rate limiting isn't collapsed to one bucket per node.
- 30s AbortSignal.timeout on non-stream requests; SSE fight streams
(/api/fights/:id/stream) are exempt (long-lived by design).
- On upstream fetch failure, log and return 502 {error} instead of a
buffered hang or a 500 stack trace.
- fights.ts: set X-Accel-Buffering: no on the SSE stream response so an
nginx-fronted arena (nginx-proxy-manager) doesn't buffer live fight events.
- docker-compose.yml: document ARENA_UPSTREAM_URL / TRUSTED_PROXY (commented,
no active value set here — the canonical arena gets its own compose file
in a later plan).
TDD: added the SSE/XFF/502 tests, confirmed the 502 test failed against the
prior implementation, then implemented to green (9/9 arena-proxy tests,
17/17 combined with rate-limit.test.ts).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The creator was being registered with webhookUrl='http://human.local/' and
isHuman=true. Now uses poll.local and isHuman=false. The auto-upgrade logic
on login also converts any existing creator record from human to bot mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- downloadSetupGuide() now triggers a real file download with bot_id/secret injected
- Add POST /api/auth/regenerate-secret endpoint (JWT auth, 3/hour rate limit)
- Add "Download Setup Guide" section to BotProfilePage with secret regeneration flow
- Old secret immediately invalidated on regeneration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests concurrent same-name registration (exactly one succeeds),
case-insensitive name collisions, expired JWT rejection, NIP-98
pubkey mismatch, and duplicate pubkey prevention.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add sanitizeError() helper that strips file paths, stack traces, SQLite
errors, and system errors from messages before returning them to clients.
Applied to all route-level catch blocks in payments, queue, fights, and
admin routes. Includes 12 tests for the sanitizer and static analysis
test verifying no route files leak raw err.message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add test confirming disconnect-wallet deletes walletConnections rows
and sets hasWallet=false on the bot record.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add tests confirming production error handler sanitizes all internal
errors (ENOENT, stack traces, file paths). Add static analysis test
verifying no route file passes err.stack to c.json() responses.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Test all 7 admin endpoints (stats, bots, deactivate, activate,
reset-elo, fights, backup) reject non-creator pubkeys and missing
pubkeys with 403. Verifies global middleware guard works correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduce login and nostr/session rate limits from 30 to 10 requests per
minute per IP to prevent brute-force attacks. Add tests verifying 429
response after exceeding the limit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create server/src/lib/validators.ts with reusable schemas for all API
inputs (auth, fights, bets, payments, tournaments, queue, docs).
Import and use in all route handlers, replacing inline validation.
Add formatZodError helper for user-friendly error messages.
77 test cases in validators.test.ts cover valid, invalid, boundary,
and attack inputs (SQL injection, XSS, prototype pollution).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
14 integration tests covering full fight lifecycle with real in-memory
DB: SSE event ordering, polling bot challenge/response flow, human
player response submission, and 3 concurrent fights without interference.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8 tests covering: complete fight lifecycle, HP progression, ELO
updates, concurrent fight prevention, round data validity, ELO
conservation, status transitions, and win streak tracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Only invalidates __alltime__ and current season cache keys on fight completion,
preserving historical season caches. Test verifies selective behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Global app.onError handler already in app.ts catches all unhandled route
exceptions. Production mode returns "Internal server error" only.
Tests verify no stack traces or file paths leak in responses.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Validates challenge type against the full CHALLENGE_TYPES enum before
processing. Invalid types now return 400 instead of silently falling
back to speed_blitz.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added getDecodedToken validation that rejects malformed tokens with
400 before any DB lookups. Tests cover empty, non-base64, truncated,
and random base64 tokens.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add rateLimit(1_000, 30) middleware to GET /poll endpoint
- Fix Challenge type errors in human-responses test files (missing baseDamage)
- Add rate-limit unit test verifying 429 after exceeding limit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
better-sqlite3 driver is synchronous — .get() does NOT need await.
Added tests for unknown pubkey (404) and missing pubkey (400) on join.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Polling bots have no external script running during practice mode,
so the poll would time out giving empty answers. Now overrides the
webhook URL to human.local so the browser UI handles challenges.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Security:
- Move CREATOR_PUBKEY from hardcoded constant to BOTFIGHTS_CREATOR_PUBKEYS
env var. Shared isCreatorPubkey() in constants.ts used by auth, admin,
tournaments. Frontend checks authorization via API, not client-side.
Mobile fixes:
- Nostr signer: poll for window.nostr up to 3s (Amber injects late).
- TTS: auto-unlock AudioContext on first user interaction via
installAutoUnlock() on fight page mount.
UX:
- Add loading spinners to "I BUILD BOTS" and "I FIGHT MYSELF" buttons.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace insecure raw-pubkey auth with cryptographic NIP-98 signed
requests and server-issued JWT sessions. Logout now fully clears
all state including nsec. Add yellow "Use Nostr Signer" button
for Amber/NIP-07 remote signers.
- Server: JWT middleware (HMAC-SHA256, 24h expiry), NIP-98 verification
- Server: POST /api/auth/nostr/session endpoint
- Frontend: NIP-98 token builder + authFetch wrapper with JWT Bearer
- Frontend: All authenticated API calls use authFetch
- Security: logout clears JWT, pubkey, bot, nsec, and profile pic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Track active fight IDs per bot in orchestrator (Set → Map)
- Return fightId in "already in fight" error responses (409)
- Frontend shows "REJOIN FIGHT" link instead of generic error
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migrate all server modules to use the centralized logger (lib/logger.ts)
instead of raw console calls. Lint warnings reduced from 74 to 25.
Remaining warnings are only no-floating-promises in game engine code.
Files updated: orchestrator.ts, ranked-queue.ts, human-responses.ts,
payments.ts, fight-loop.ts, app.ts, routes/payments.ts
Files suppressed: logger.ts, fight-loop-cli.ts, migrate.ts (legitimate console use)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Creative challenges (roast_battle, creative_writing, meme_war,
code_golf, wrestling_match) now auto-generate multiple choice options
from per-type response pools: 1 good answer + 3 weaker distractors.
The free text input UI is commented out but preserved for future use.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sets up ESLint 10 flat config with @typescript-eslint/no-floating-promises (error)
and no-console (warn, allow warn/error). Fixes all floating promise errors in
server routes, orchestrator reader cleanup, and frontend composables with void operator.
Game engine files get warning-level for intentional fire-and-forget async.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Validate amountSats is a positive integer (1–1,000,000) on both
/zap and /bets/place endpoints to prevent negative, zero, or
absurdly large amounts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Zod schema for webhook response parsing (orchestrator.ts)
- Add Zod schemas for POST /respond and /react request bodies
- Add safe integer validation for batch count param
- Prefer cf-connecting-ip over spoofable x-forwarded-for
- Add ID format validation on URL params
- Add rate limiting on /auth/login (30/min) and /update (10/min)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add periodic cleanup of fightReactions Map (clears when >100 entries).
Add RSS/heap logging every 10 fights in fight-loop for memory monitoring.
Set --max-old-space-size=256 in Docker CMD as safety net.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
POST /api/tournaments (create, admin-only),
POST /api/tournaments/:id/join, POST /api/tournaments/:id/start,
GET /api/tournaments (list), GET /api/tournaments/:id (bracket).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add per-IP SSE connection limit (max 5 concurrent streams). Track
activeSSECount and ssePerIp maps with proper decrement in finally
blocks. Export getActiveSSECount() for admin stats. Change heartbeat
ping from 5s to 15s interval. Connections already clean up on client
disconnect via Hono's streamSSE try/catch/finally pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>