Date: 2026-03-22 | Audited by: Backend Architect, Frontend Architect, Remotion Engineer, DB Architect, Security Auditor, Performance Engineer | ~90 unique issues after deduplication
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 |
Significant bugs affecting security, correctness, or user experience. Not immediately exploitable or crash-inducing but need prompt attention.
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
Suboptimal patterns, technical debt, and issues that compound under load or scale.
is_active (BaseModelMixin) vs is_deleted (files, media) on different tables. Some tables have both columns."a" (users/schemas.py)settings.py:44)notifications/service.py:44)db/session.py:44)tasks/service.py:1158)new QueryClient() leaks cache between server requests (shared/lib/query_client.ts)package.json)WizardContext.tsx:361)models.py)server_default on BaseModelMixin — direct SQL/migrations bypass Python-side defaults (db/base.py:20)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)settings.py:29)files/router.py:39)onError swallows error details — all errors become "Oops, fetch failed", impossible to distinguish 401/404/500 (shared/api/index.ts:49)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)relationship() across 11 models, traps future N+1 patterns (all models.py)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)console.log("Verifying token:", token) in server action (server.ts:16)Loader.tsx, HomePage.tsx)pyproject.toml)WebhookRead includes plaintext secret field (webhooks/schemas.py:16)main.py)tasks/service.py:1062)/health endpoint on Remotion service — Docker/K8s probes have nothing to hit (server/index.ts)handleSave recreated on every keystroke (TranscriptionEditor.tsx:124)font_size, fade_duration_frames can crash renderer (CaptionStyleSchema.ts, DocumentSchema.ts)Code quality issues, missing conventions, and minor inefficiencies.
ERROR_ constants (all routers)is_active/is_deleted semantics (some models have both columns)console.log/console.error statements in production frontend codedata-testid on 18 of 21 shared UI componentspostgres/postgres with no production guardminioadmin/minioadminemail column has no unique constraintbroker_id on jobs has no indexjson import in media/service.pyformatBytes duplicated in 3 Remotion filesGET /api/render returns bare string "Hello" (debug leftover)justifyContent uses "left"/"right" instead of "flex-start"/"flex-end" in RemotionregionIdCounter shared across component instancesFragmentsStep component is 843 lines (guideline: 150 max).env not in backend .gitignoreuseBreadcrumbs uses JSON.stringify in dependency arrayBreadcrumbsProvider context value not memoizedTranscriptionModal passes queryKey in wrong argument positionSIGTERM handled in Remotion, not SIGINTremoveOnFail TTL (2h) makes debugging failed renders difficult| 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 |