wip: 01-tournament-engine paused at security feedback (1/7 applied)
Applied security fix 1 (no default admin PIN in prod, first-run setup). Fixes 2-7 remaining: WS auth, HTTP hardening, audit triggers, JWT validation, NATS subject validation, CSV safety. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
21ff95068e
commit
aa07c0270d
2 changed files with 119 additions and 1 deletions
110
.planning/phases/01-tournament-engine/.continue-here.md
Normal file
110
.planning/phases/01-tournament-engine/.continue-here.md
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
---
|
||||||
|
phase: 01-tournament-engine
|
||||||
|
task: planning-feedback
|
||||||
|
total_tasks: 14 plans (A-N)
|
||||||
|
status: in_progress
|
||||||
|
last_updated: 2026-03-01T02:16:50.435Z
|
||||||
|
---
|
||||||
|
|
||||||
|
<current_state>
|
||||||
|
Phase 1 planning is complete (14 plans, 6 waves, 68/68 requirements covered). Currently applying security feedback — item 1 of 7 is done, items 2-7 remain. All plans are committed except the security hardening edits.
|
||||||
|
</current_state>
|
||||||
|
|
||||||
|
<completed_work>
|
||||||
|
|
||||||
|
## Planning Pipeline (fully complete)
|
||||||
|
- Research: 01-RESEARCH.md written (go-libsql, NATS JetStream, Svelte 5, ICM complexity)
|
||||||
|
- Plans A-N created (14 plans, 6 waves)
|
||||||
|
- Plan checker: 2 iterations, all blockers resolved (G dep on F, J/L scope splits → M/N)
|
||||||
|
- Committed: `21ff950 docs(01): create Phase 1 plans (A-N) with research and feedback`
|
||||||
|
|
||||||
|
## User Feedback Round 1 (fully applied, committed)
|
||||||
|
- Correction 1: CONTEXT.md — Added hand-for-hand decisions (per-table completion tracking)
|
||||||
|
- Correction 2: Plan D/H — Fixed hand-for-hand (removed time deduction, added per-table handCompleted)
|
||||||
|
- Correction 3: REQUIREMENTS.md — Reworded SEAT-06 to "tap-tap manual seat moves"
|
||||||
|
- Correction 4: Plan C — Removed FIN-14 duplicate (Plan F is sole owner)
|
||||||
|
- Correction 5: Plan B — Clarified re-entry clears bust fields
|
||||||
|
- Gap 6: CONTEXT.md — Defined punctuality bonus concretely
|
||||||
|
- Gap 7: Plan I — Added integration_test.go with full tournament lifecycle
|
||||||
|
- Gap 8: Plan A — Health check verifies LibSQL + NATS + hub
|
||||||
|
- Gap 9: Plan C — JWT expiry 24h → 7 days
|
||||||
|
- Suggestion 10: Plan B — DKK defaults + Copenhagen chip set
|
||||||
|
- Suggestion 12: Plan M — Multi-tournament state caching
|
||||||
|
- Suggestion 13: Plan K — Removed clock tap-to-pause (FAB only)
|
||||||
|
|
||||||
|
## Security Feedback (partially applied)
|
||||||
|
- Fix 1 DONE: Plan B — Removed default admin PIN seed, added first-run setup flow + dev-only seed migration
|
||||||
|
- Fixes 2-7: NOT YET APPLIED (see remaining_work)
|
||||||
|
</completed_work>
|
||||||
|
|
||||||
|
<remaining_work>
|
||||||
|
|
||||||
|
## Security Fixes (MUST APPLY — continue from fix 2)
|
||||||
|
|
||||||
|
### Fix 2: Plan A — Authenticated WebSocket connections
|
||||||
|
- Require JWT as query param on /ws?token=...
|
||||||
|
- Validate JWT before allowing subscription
|
||||||
|
- Enforce tournament scope server-side
|
||||||
|
- Validate Origin header, reject unknown origins
|
||||||
|
- Edit Plan A task A2 WebSocket hub section
|
||||||
|
|
||||||
|
### Fix 3: Plan A — Harden HTTP server baseline
|
||||||
|
- Set explicit timeouts: ReadHeaderTimeout(10s), ReadTimeout(30s), WriteTimeout(60s), IdleTimeout(120s), MaxHeaderBytes(1MB)
|
||||||
|
- Apply http.MaxBytesReader on all request body reads (default 1MB, larger for CSV import)
|
||||||
|
- Edit Plan A task A2 HTTP server section
|
||||||
|
|
||||||
|
### Fix 4: Plan B — Audit trail tamper protection triggers
|
||||||
|
- SQLite trigger REJECT UPDATE on audit_entries (except undone_by)
|
||||||
|
- SQLite trigger REJECT DELETE on audit_entries entirely
|
||||||
|
- Add to Plan B task B1 schema migration
|
||||||
|
|
||||||
|
### Fix 5: Plan C — Fix JWT validation and PIN rate limiting
|
||||||
|
- Enforce HS256 via jwt.WithValidMethods([]string{"HS256"})
|
||||||
|
- JWT TTL already 7 days (done in earlier feedback)
|
||||||
|
- Persist failed login attempts in LibSQL (not in-memory)
|
||||||
|
- Key rate limiting by operator ID (not IP)
|
||||||
|
- Emit audit entries for 5+ consecutive failures
|
||||||
|
- Defer key rotation to Phase 7
|
||||||
|
|
||||||
|
### Fix 6: Plan C/A (NATS) — Validate subject construction
|
||||||
|
- UUID validation helper before constructing NATS subjects
|
||||||
|
- Apply in internal/nats/publisher.go (Plan A)
|
||||||
|
|
||||||
|
### Fix 7: Plan G + Plan F — CSV safety
|
||||||
|
- Import limits: max 10,000 rows, 20 columns, 1,000 chars/field
|
||||||
|
- Export: neutralize formula injection (prefix =, +, -, @ with tab)
|
||||||
|
- Test cases for both
|
||||||
|
|
||||||
|
### Also Apply:
|
||||||
|
- Keep-as-is item 8: Add TODO comment in Plan J auth store re: Phase 7 HttpOnly cookies
|
||||||
|
- Acceptance criteria: Add security acceptance criteria to relevant plan verification sections
|
||||||
|
|
||||||
|
### After Security Fixes:
|
||||||
|
- Commit all security edits
|
||||||
|
- Phase 1 planning is DONE — ready for /gsd:execute-phase 1
|
||||||
|
</remaining_work>
|
||||||
|
|
||||||
|
<decisions_made>
|
||||||
|
|
||||||
|
- Plans split from 12→14: J split into J+M (layout shell), L split into L+N (tables+more) — checker blocker on file count
|
||||||
|
- G moved to wave 4 (depends on F for financial engine calls) — checker blocker on missing dependency
|
||||||
|
- I moved to wave 5, K/L/N moved to wave 6 — cascading from G wave change
|
||||||
|
- Hand-for-hand is per-table completion tracking (not time deduction) — user correction
|
||||||
|
- SEAT-06 reworded to tap-tap (not drag-and-drop) — user correction, matches CONTEXT.md locked decision
|
||||||
|
- Clock tap-to-pause removed — accidental pause risk, FAB only
|
||||||
|
- JWT 7 days not 24h — 12+ hour tournament sessions
|
||||||
|
- DKK as default currency — Copenhagen demo venue
|
||||||
|
- No default admin PIN in prod — first-run setup screen instead
|
||||||
|
</decisions_made>
|
||||||
|
|
||||||
|
<blockers>
|
||||||
|
None — just need to finish applying security fixes 2-7
|
||||||
|
</blockers>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
The user provided security feedback as a structured list (7 must-apply items, 2 keep-as-is items, acceptance criteria). Fix 1 (Plan B default admin) is applied. The remaining 6 fixes are straightforward edits to existing plan files. All the files have already been read in this session. After applying fixes 2-7, commit, and Phase 1 planning is complete. Next step after planning: /gsd:execute-phase 1.
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<next_action>
|
||||||
|
Start with: Apply security fix 2 (Plan A WebSocket auth) — edit the WebSocket hub section in 01-PLAN-A.md task A2 to require JWT token as query parameter, validate before subscription, enforce tournament scope, validate Origin header. Then continue with fixes 3-7 sequentially, add acceptance criteria to verification sections, commit all.
|
||||||
|
</next_action>
|
||||||
|
|
@ -241,12 +241,20 @@ END;
|
||||||
|
|
||||||
**4. Seed data:**
|
**4. Seed data:**
|
||||||
Create `internal/store/migrations/003_seed_data.sql`:
|
Create `internal/store/migrations/003_seed_data.sql`:
|
||||||
- Insert a default admin operator (name: "Admin", PIN hash for "1234", role: "admin")
|
|
||||||
- Insert a default venue_settings row (currency: DKK, symbol: kr, rounding: 5000 = 50 kr, receipt_mode: digital)
|
- Insert a default venue_settings row (currency: DKK, symbol: kr, rounding: 5000 = 50 kr, receipt_mode: digital)
|
||||||
- Insert a default chip set ("Standard") with common denominations:
|
- Insert a default chip set ("Standard") with common denominations:
|
||||||
- 25 (white, #FFFFFF), 100 (red, #FF0000), 500 (green, #00AA00), 1000 (black, #000000), 5000 (blue, #0000FF)
|
- 25 (white, #FFFFFF), 100 (red, #FF0000), 500 (green, #00AA00), 1000 (black, #000000), 5000 (blue, #0000FF)
|
||||||
- Insert a second chip set ("Copenhagen") with DKK-friendly denominations:
|
- Insert a second chip set ("Copenhagen") with DKK-friendly denominations:
|
||||||
- 100 (white), 500 (red), 1000 (green), 5000 (black), 10000 (blue)
|
- 100 (white), 500 (red), 1000 (green), 5000 (black), 10000 (blue)
|
||||||
|
- **NO default admin operator in seed migration** — first-run setup handles this
|
||||||
|
- Dev-only seed: create `internal/store/migrations/004_dev_seed.sql` with default admin (name: "Admin", PIN hash for "1234", role: "admin"). Only applied when `--dev` flag is set.
|
||||||
|
|
||||||
|
**5. First-run setup:**
|
||||||
|
- On startup, check if operators table has zero rows
|
||||||
|
- If zero operators AND not --dev mode: server starts but returns a setup page at all routes except `/api/v1/setup`
|
||||||
|
- `POST /api/v1/setup` — body: `{"name": "...", "pin": "..."}` — creates the first admin operator. Only works when zero operators exist. Returns JWT.
|
||||||
|
- After first operator is created, normal app behavior resumes
|
||||||
|
- In --dev mode: dev seed migration creates default admin, no setup screen needed
|
||||||
|
|
||||||
**Verification:**
|
**Verification:**
|
||||||
- `make run` starts the binary and auto-applies all 3 migrations
|
- `make run` starts the binary and auto-applies all 3 migrations
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue