# Cofee Project — Bug Audit Report

Date: 2026-03-22 | Audited by: Backend Architect, Frontend Architect, Remotion Engineer, DB Architect, Security Auditor, Performance Engineer | ~90 unique issues after deduplication

~90
Total Issues
10
Critical
20
High
30+
Medium
30+
Low

## Critical — Fix Immediately

These issues can cause security breaches, data loss, or application crashes under normal usage.

# | Area | Issue | File(s)
--- | --- | --- | ---
1 | Security | Path traversal — any authenticated user can read arbitrary server files via `../../etc/passwd`. The endpoint resolves the path but never validates it stays within the storage directory. | `files/router.py:103`
2 | Security | Unauthenticated webhook — `POST /api/tasks/webhook/{job_id}/` has no auth. Anyone can forge job status, inject arbitrary output data, or mark jobs as failed. | `tasks/router.py:195`
3 | Security | JWT in JS-accessible cookies — tokens set via `js-cookie` with no HttpOnly/Secure/SameSite flags. Any XSS steals both access and refresh tokens. | `useCookie.tsx`, `LoginPage.tsx:36`
4 | Security | PyJWT CVE-2026-32597 — active vulnerability in the core auth library. Fix available in v2.12.0. | `pyproject.toml`
5 | Frontend | No token refresh — when access token expires, all API calls fail with opaque `"Oops, fetch failed"`. Refresh token is set during login but never used again. | `shared/api/index.ts:27`
6 | Frontend | setState during render — `setCaptionedVideoFileId()` and `setStatus()` called outside useEffect, causing infinite re-render loops that freeze the browser tab. | `CaptionResultStep.tsx:69`, `ConvertMediaView.tsx:51`
7 | Frontend | Workspace state race condition — WizardProvider and WorkspaceProvider independently PATCH `workspace_state`, overwriting each other's data on the 1000ms debounce boundary. | `WizardContext.tsx:345`, `WorkspaceContext.tsx:111`
8 | Backend | Auth session closed prematurely — `get_current_user` closes its DB session in `finally`, leaving the returned User object detached. Any lazy-loaded relationship access causes `DetachedInstanceError`. | `infrastructure/auth.py:62`
9 | Remotion | Custom fonts never loaded — only Lobster is loaded at module level. Any other `font_family` in styleConfig silently renders with system sans-serif. | `Captions.tsx:3,12`
10 | Performance | Sequential S3 frame uploads — 300 frames uploaded one-at-a-time (30s of round-trip time). Should use `asyncio.gather()` with semaphore (~3s). | `media/service.py:497`

## High — Fix This Sprint

Significant bugs affecting security, correctness, or user experience. Not immediately exploitable or crash-inducing but need prompt attention.

### Security

Issue | File(s)
--- | ---
No refresh token rotation — stolen token grants permanent access for 30 days with no revocation mechanism | `users/router.py:211`
Remotion has zero authentication — port 3001 exposed, enables SSRF via `callbackUrl` | `server/index.ts:22`
IDOR on artifacts/transcriptions/events — any authenticated user reads/modifies anyone's data (`_ = current_user`) | `media/router.py:205`, `transcription/router.py:30`, `jobs/router.py:106`
No rate limiting on login/register — unlimited brute force | `users/router.py:176`

### Backend

Issue | File(s)
--- | ---
Token refresh skips user validation — deactivated users keep generating new access tokens | `users/router.py:211`
Repository update drops explicit None — impossible to clear nullable fields via PATCH (affects 7 repos) | `jobs/repository.py:78` + 6 others
Routers bypass service layer — media, transcription, notification routers use repositories directly | `media/router.py:128`, `transcription/router.py:36`, `notifications/router.py:63`
TaskService couples to 6 cross-module repos — bypasses business rules in other modules | `tasks/service.py:26`

### Frontend

Issue | File(s)
--- | ---
Zero error boundaries — any JS error crashes the entire app to a blank white screen | `app/` (no `error.tsx` anywhere)
WebSocket token in URL query string — logged by proxies and browser history | `SocketProvider.tsx:209`
Raw fetch() bypasses auth middleware — 3 notification endpoints use manual cookies | `NotificationPopup.tsx:84,94`, `SocketProvider.tsx:156`
FSD layer violation — feature imports from widget layer | `SubtitleRevisionStep.tsx:24`

### Database

Issue | File(s)
--- | ---
Missing FK indexes on notifications — `job_id`, `project_id` cause full sequential scans | `notifications/models.py:18`
No pagination on 8 of 9 list endpoints — unbounded queries load entire tables | All `repository.py list_all()` methods
No CHECK constraints on status columns — typo in status string = invisible orphaned row | `jobs/models.py`, `projects/models.py`, `notifications/models.py`
`files.path` queried without index — sequential scan on every file lookup by path | `files/repository.py:36`

### Remotion

Issue | File(s)
--- | ---
No retry on DONE/FAILED webhook — missed webhook = user's job stuck forever in "running" | `webhook.ts:13`
Empty transcription silently renders with no captions — wasted compute, confusing UX | `useCaptions.ts:27`
Sync render path has no concurrency limit — N requests spawn N Chromium processes, causes OOM | `server/index.ts:42`
`out/` directory not created at startup — first render fails outside Docker with ENOENT | `render_video.ts:134`

### Performance

Issue | File(s)
--- | ---
New psycopg2 connection per cancellation check — 5-20ms overhead + connection churn in Dramatiq | `tasks/service.py:224`
No GZip middleware — transcription JSON (100KB+) sent uncompressed to frontend | `main.py`
WizardContext subscribes to full notification store — entire wizard re-renders every 3 seconds during task processing | `WizardContext.tsx:353`

## Medium — Fix Next Sprint

Suboptimal patterns, technical debt, and issues that compound under load or scale.

- Inconsistent soft-delete — `is_active` (BaseModelMixin) vs `is_deleted` (files, media) on different tables. Some tables have both columns.
- No password complexity requirements — users can register with password `"a"` (`users/schemas.py`)
- Connection pool defaults too small — 5+10=15 max; production with 4 workers needs 60+ (`settings.py:44`)
- Redis connection never closed on shutdown — singleton created lazily, no lifespan cleanup (`notifications/service.py:44`)
- No explicit session rollback on failure — uncommitted state can leak between requests (`db/session.py:44`)
- Multiple DB commits per webhook callback — 7+ commits with no atomicity, partial saves on failure (`tasks/service.py:1158`)
- SSR QueryClient singleton — module-level `new QueryClient()` leaks cache between server requests (`shared/lib/query_client.ts`)
- Unused npm dependencies — lodash, axios, xior = ~85KB dead weight in bundle (`package.json`)
- Redundant 2s polling alongside WebSocket — 30 API requests/min per active wizard, WebSocket already delivers same data (`WizardContext.tsx:361`)
- All JSON columns should be JSONB — 10 columns use plain JSON, can't be indexed or queried efficiently (all `models.py`)
- No `server_default` on BaseModelMixin — direct SQL/migrations bypass Python-side defaults (`db/base.py:20`)
- S3 filename collision — re-rendering same video overwrites previous captioned version (`remotion_service/server/services/s3.ts:76`)
- `lines_per_screen` and `animation_speed` accepted but never used — schema promises features that don't exist (`CaptionStyleSchema.ts`)
- Default JWT secret "dev-secret" — no production guard prevents deployment with guessable secret (`settings.py:29`)
- No file content type validation on upload — extension/MIME/magic bytes not checked (`files/router.py:39`)
- API `onError` swallows error details — all errors become `"Oops, fetch failed"`, impossible to distinguish 401/404/500 (`shared/api/index.ts:49`)
- Irreversible migration downgrade — `b3c4d5e6f7a8` downgrade crashes with NOT NULL violation (`alembic/versions/`)
- `project_pct` column misnaming — DB says "project" but API says "progress", confusing mapping (`jobs/models.py:34`, `notifications/service.py:143`)
- No ORM relationships defined — zero `relationship()` across 11 models, traps future N+1 patterns (all `models.py`)
- Double audio file loading — `detect_silence` decodes the same file twice, doubling memory and time (`media/service.py:86`)
- `StorageService.get_file_info` makes 3 sequential S3 calls — could be 1 `head_object` (`storage/base.py:88`)
- Token logged to server console — `console.log("Verifying token:", token)` in server action (`server.ts:16`)
- framer-motion in critical path — 32KB gzipped for 2 components, should use CSS animations (`Loader.tsx`, `HomePage.tsx`)
- Additional dependency CVEs — protobuf, pyasn1, python-multipart have known fixes available (`pyproject.toml`)
- Webhook secrets exposed in API response — `WebhookRead` includes plaintext `secret` field (`webhooks/schemas.py:16`)
- No request timing middleware — can't detect performance regressions (`main.py`)
- Redis SCAN in cancellation cleanup — O(n) over entire keyspace instead of direct key lookup (`tasks/service.py:1062`)
- No `/health` endpoint on Remotion service — Docker/K8s probes have nothing to hit (`server/index.ts`)
- TranscriptionEditor callback churn — `handleSave` recreated on every keystroke (`TranscriptionEditor.tsx:124`)
- No numeric bounds on Remotion schema fields — negative `font_size`, `fade_duration_frames` can crash renderer (`CaptionStyleSchema.ts`, `DocumentSchema.ts`)

## Low — Technical Debt

Code quality issues, missing conventions, and minor inefficiencies.

- Inline error strings instead of `ERROR_` constants (all routers)
- Inconsistent `is_active`/`is_deleted` semantics (some models have both columns)
- 19 `console.log`/`console.error` statements in production frontend code
- Missing `data-testid` on 18 of 21 shared UI components
- No Content-Security-Policy or security headers on frontend
- OpenAPI/Swagger docs exposed unconditionally (even in production)
- Redis without authentication in Docker Compose
- Default DB credentials `postgres/postgres` with no production guard
- MinIO default credentials `minioadmin/minioadmin`
- `email` column has no unique constraint
- Webhook secrets stored as plaintext in DB
- `broker_id` on jobs has no index
- Duplicate `json` import in `media/service.py`
- `formatBytes` duplicated in 3 Remotion files
- `GET /api/render` returns bare string "Hello" (debug leftover)
- `justifyContent` uses "left"/"right" instead of "flex-start"/"flex-end" in Remotion
- Module-level mutable `regionIdCounter` shared across component instances
- `FragmentsStep` component is 843 lines (guideline: 150 max)
- Login page shows no error message to user on failure
- `.env` not in backend `.gitignore`
- `useBreadcrumbs` uses `JSON.stringify` in dependency array
- `BreadcrumbsProvider` context value not memoized
- `TranscriptionModal` passes `queryKey` in wrong argument position
- Only `SIGTERM` handled in Remotion, not `SIGINT`
- Short `removeOnFail` TTL (2h) makes debugging failed renders difficult

### Top 5 Quick Wins (highest impact, lowest effort)

Fix | Effort | Impact
--- | --- | ---
Path traversal guard — add 3-line `is_relative_to()` check | 5 min | Blocks arbitrary file read (Critical security fix)
Add `GZipMiddleware` — single line in `main.py` | 2 min | 5-10x smaller JSON responses
Parallelize S3 frame uploads — `asyncio.gather()` + semaphore | 30 min | 10-60s saved per frame extraction job
Remove unused npm packages (lodash, axios, xior) | 5 min | ~85KB bundle size reduction
Fix setState-during-render — wrap in `useEffect` | 10 min | Prevents browser tab freezes

### Contributing Agents

Backend Architect 25 findings
API logic, race conditions, service layer patterns, error handling
Frontend Architect 24 findings
React/Next.js bugs, state management, FSD compliance, type safety
Remotion Engineer 22 findings
Render pipeline, S3 integration, caption edge cases, webhook reliability
DB Architect 23 findings
Schema issues, missing indexes, migration risks, query patterns
Security Auditor 22 findings
OWASP audit, auth/JWT, IDOR, SSRF, dependency CVEs, scanning tools
Performance Engineer 25 findings
Async blocking, S3 throughput, connection pools, re-renders, bundle size

- Path traversal and unauthenticated webhook — confirmed independently by Backend Architect + Security Auditor
- Missing pagination — flagged by Backend Architect + DB Architect + Performance Engineer
- Inconsistent soft-delete — flagged by Backend Architect + DB Architect
- IDOR on artifacts/transcriptions — flagged by Backend Architect + DB Architect + Security Auditor
- WizardContext re-renders — flagged by Frontend Architect + Performance Engineer