9.6 KiB
9.6 KiB
Confirmed Findings
Severity: Critical / High / Medium / Low / Observation. Confidence: Confirmed / High-confidence inference / Unverified risk.
Full structured detail for every finding (files, functions, scenario, expected/actual, root cause, impact, evidence, fix direction, complexity, regression test, runtime-confirmation flag) is in FINDINGS.json (validated). Source IDs from specialist auditors are in parentheses.
| ID | Severity | Confidence | Title | Area |
|---|---|---|---|---|
| FIN-01 (DBAUD-01, GRP-01) | Critical | Confirmed | Schema/migration drift: MaterialGroup table + groupId columns never migrated — fresh deployments' databases are incompatible with the app (verified: no such column: Deck.groupId, no such table: MaterialGroup) |
DB/migrations |
| FIN-02 (AUTH-01, OPS-03) | Critical | High-confidence inference | Hardcoded fallback SESSION_SECRET ("dev-session-secret-change-in-production-must-be-32-chars") in src/lib/auth.ts:15 + src/proxy.ts:8-9; empty env var in shipped compose → forgeable session cookie → full auth bypass |
Auth/deploy |
| FIN-03 (QUIZ-01, ADV-01) | High | Confirmed | In-viewer "Retake Missed" never sets retakeIds prop → persisted as a full attempt with a wrong, lower score; pollutes SEQUENTIAL progress |
Quiz |
| FIN-04 (CARD-01, IMPT-01, QUIZ-06) | High | Confirmed | Stale card ids in saved progress never filtered on resume (filterAndClampOrder dead code) → deleting a card mid-session bricks the flashcard study session |
Flashcards |
| FIN-05 (OPS-01) | High | Confirmed | Port mismatch: container listens on 3726, compose publishes 3000:3000 → production deployment unreachable | Deploy |
| FIN-06 (OPS-06) | High | High-confidence inference | No .dockerignore: Windows-host builds inject win32 native modules (better-sqlite3, argon2) into the Linux image |
Deploy |
| FIN-07 (OPS-08) | High | Confirmed | docker-compose.override.yml auto-merges on plain docker compose up → production command silently runs dev mode |
Deploy |
| FIN-08 (AUTH-04, ADV-09) | Medium | High-confidence inference | First-login provisioning takeover; ADMIN_PASSWORD_HASH env is dead config |
Auth |
| FIN-09 (AUTH-02) | Medium | High-confidence inference | Proxy auth bypass for any path containing . (latent today; would expose any future non-UUID-id route) |
Auth |
| FIN-10 (AUTH-03) | Medium | High-confidence inference | Password-reset abuse: token overwrite DoS, spoofable x-forwarded-for rate limits, unthrottled complete |
Auth |
| FIN-11 (QUIZ-02) | Medium | High-confidence inference | Attempt route stores unvalidated answersJson; duplicate option ids inflate SATA credit; malformed shapes → 500 |
Quiz |
| FIN-12 (QUIZ-03, FE-04) | Medium | Confirmed | Quiz Finish double-submit creates duplicate attempts; failure path silent | Quiz/FE |
| FIN-13 (QUIZ-04) | Medium | High-confidence inference | Progress PATCH can land after Finish's DELETE → stale "Continue" row for a completed quiz | Quiz |
| FIN-14 (CARD-02, FE-05, QUIZ-09) | Medium | High-confidence inference | Fire-and-forget, unsequenced progress autosaves: out-of-order PATCHes regress resume state; multi-tab last-write-wins | Flashcards/Quiz |
| FIN-15 (CARD-03, ADV-07) | Medium | Confirmed | Completed flashcard session never persisted as complete → resume re-shows the last graded card | Flashcards |
| FIN-16 (CARD-04) | Medium | Confirmed | SRS review endpoint enforces membership only — no due-date or newCardsPerDay enforcement server-side |
SRS |
| FIN-17 (CARD-05, ADV-02) | Medium | Confirmed | Unbounded DB scans: whole StudyActivity table per fetch; all SRS card states per set; Navbar polls every 60 s |
Perf/DB |
| FIN-18 (FE-01, GRP-03, FE-02, GRP-05) | Medium | Confirmed | Library optimistic mutations are fire-and-forget (no res.ok, no rollback); cross-group reorder omits origin group from payload |
FE/Groups |
| FIN-19 (GRP-02, CARD-07, AUTH-06) | Medium | Confirmed | Reorder/create endpoints trust client groupId/sortOrder — no class/type/existence validation; cross-class group membership can leak content into another class's share link |
Groups |
| FIN-20 (FE-03) | Medium | Confirmed | Shared quiz session resets and options reshuffle on any parent re-render (unstable inline quiz prop) |
FE |
| FIN-21 (IMPT-02, FE-08, QUIZ-05, ADV-10) | Medium | Confirmed | /api/progress PATCH completely unvalidated; unguarded client JSON.parse of persisted progress crashes pages |
Quiz/FE |
| FIN-22 (OPS-02) | Medium | High-confidence inference | Entrypoint migrate-deploy chain fragile: crash loop on conflicting DB state; runner prisma CLI install unverified/unpinned | Deploy |
| FIN-23 (OPS-05) | Medium | Confirmed | Non-reproducible installs: npm install (not ci); unpinned prisma@^7.8.0 re-install re-resolves the whole tree in the runner |
Deploy |
| FIN-24 (OPS-07) | Medium | Confirmed | No healthcheck, no backup, no documented recovery | Deploy |
| FIN-25 (OPS-10, TEST-10) | Medium | Confirmed | CI builds/pushes without tests, lint, prisma validate, or container smoke test — deployment-breaking defects ship green | CI |
| FIN-26 (QUIZ-07) | Low | Confirmed | SATA scoring divides by correctIds.length with no zero-guard → NaN |
Quiz |
| FIN-27 (QUIZ-08) | Low | Confirmed | Historical review/category breakdown recompute scores from current content, not stored attempt | Quiz |
| FIN-28 (IMPT-03) | Low | Confirmed | Card add/edit endpoints bypass Zod: empty strings storable; CreateTab silently drops incomplete cards | Imports |
| FIN-29 (IMPT-04) | Low | Confirmed | No payload size/string-length/array-length caps on import schemas/routes; whitespace-only names accepted | Imports |
| FIN-30 (IMPT-05) | Low | Confirmed | SATA constraint mismatch: instructions say ≥2 correct, schema enforces ≥1 | Imports |
| FIN-31 (IMPT-06) | Low | Confirmed | Unvalidated name/groupId override fields on import POST routes → 500; PATCH routes mask failures as 404 |
Imports |
| FIN-32 (GRP-04) | Low | Confirmed | Group deletion leaves duplicate sortOrder values in Uncategorized; no unique constraint/tie-break | Groups |
| FIN-33 (GRP-06) | Low | Confirmed | Keyboard users cannot move items between groups (empty handleDragOver) |
Groups/a11y |
| FIN-34 (CARD-08) | Low | High-confidence inference | Restart deletes progress fire-and-forget; slow DELETE can remove the fresh session's progress row | Flashcards |
| FIN-35 (CARD-09) | Low | Confirmed | Concurrent first review of same new card → P2002 → generic 500 instead of 409 | SRS |
| FIN-36 (CARD-10) | Low | High-confidence inference | "Previous card" during the 350 ms grade animation races the pending timeout | Flashcards |
| FIN-37 (CARD-11) | Low | Confirmed | SRS set page never refreshes on focus → stale membership after deck deletion in another tab | SRS/FE |
| FIN-38 (CARD-06) | Low | Confirmed | Day boundary hardcoded to Arizona (UTC-7): "today"/new-card limit/streak roll over at 07:00 UTC | SRS |
| FIN-39 (AUTH-05) | Low | Confirmed | Proxy destroy() cookie-clear lost on redirect; layout check ignores sessionGeneration |
Auth |
| FIN-40 (AUTH-08) | Low | Confirmed | Session cookie Secure flag off in shipped compose (plain HTTP, no TLS story) | Auth/deploy |
| FIN-41 (DBAUD-02, AUTH-07) | Low | Confirmed | /api/share accepts arbitrary targetType → repeatable junk all-NULL ShareLink rows |
Auth/DB |
| FIN-42 (DBAUD-03) | Low | High-confidence inference | dev.db committed in git history (8 commits), remains in blobs on the LAN remote |
Repo hygiene |
| FIN-43 (OPS-09) | Low | Confirmed | .gitignore gaps (data/, study.db*); scratch files out.css/temp.css/test.css tracked |
Repo hygiene |
| FIN-44 (FE-07, ADV-04) | Low | Confirmed | Dashboard fetchClasses no .catch/res.ok → unhandled rejection, misleading empty state (or render crash) |
FE |
| FIN-45 (FE-09) | Low | Confirmed | Effect fetches without .catch in GenerateTab and ShareMenu |
FE |
| FIN-46 (FE-10) | Low | High-confidence inference | Cross-class navigation fetch race renders wrong-class data; module cache never invalidated | FE |
| FIN-47 (ADV-03) | Low | Confirmed | Logout has no error handling — failed logout strands the user | FE |
| FIN-48 (ADV-05) | Low | Confirmed | slugify can yield an empty slug (unreachable class); renames never update the URL slug |
Classes |
| FIN-49 (ADV-08) | Low | High-confidence inference | Shared-viewer localStorage keys item-scoped, not token-scoped → session bleed across tokens | Sharing/FE |
| FIN-50 (GRP-07) | Observation | Confirmed | Group sortOrder uses inverted desc convention with unvalidated PATCH — latent trap |
Groups |
| FIN-51 (FE-11) | Observation | Confirmed | Collapsed-groups state read in effect → one-frame expand flash | FE |
| FIN-52 (AUTH-10) | Observation | Confirmed | Shared quiz links ship the full answer key to anonymous viewers — by design, worth a warning | Sharing |
| FIN-53 (TEST-01) | Low | Confirmed | False-confidence test: "distinct valid state for every rating" never asserts distinctness | Tests |
Notes on consolidation
- Duplicate findings across specialist reports were merged under one root cause (see
sourceIds). - FE-06 ("all /api/* routes unauthenticated") was refuted by the adversarial review —
src/proxy.tsIS the compiled middleware and enforces session checks; the residual truth is FIN-09 (dot-bypass). See REJECTED_FINDINGS.md. - OPS-02's fresh-volume crash-loop framing was weakened by the adversarial review (on a fresh volume
migrate deploysucceeds; the app then fails per FIN-01) — reframed as FIN-22. - FIN-18 severity downgraded from the frontend auditor's High to Medium after adversarial review (no visible break for a single user; silent divergence self-heals on reload).