27e03cc56c
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
683 lines
28 KiB
HTML
683 lines
28 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Cofee Project — Bug Audit Report (2026-03-22)</title>
|
|
<style>
|
|
:root {
|
|
--bg: #0d1117;
|
|
--surface: #161b22;
|
|
--surface-2: #1c2129;
|
|
--border: #30363d;
|
|
--text: #e6edf3;
|
|
--text-muted: #8b949e;
|
|
--accent: #58a6ff;
|
|
--critical: #f85149;
|
|
--critical-bg: rgba(248, 81, 73, 0.1);
|
|
--high: #f0883e;
|
|
--high-bg: rgba(240, 136, 62, 0.1);
|
|
--medium: #d29922;
|
|
--medium-bg: rgba(210, 153, 34, 0.1);
|
|
--low: #8b949e;
|
|
--low-bg: rgba(139, 148, 158, 0.08);
|
|
--green: #3fb950;
|
|
--green-bg: rgba(63, 185, 80, 0.1);
|
|
}
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
line-height: 1.6;
|
|
padding: 2rem;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2rem;
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
color: var(--text);
|
|
}
|
|
|
|
h2 {
|
|
font-size: 1.5rem;
|
|
font-weight: 600;
|
|
margin-top: 2.5rem;
|
|
margin-bottom: 1rem;
|
|
padding-bottom: 0.5rem;
|
|
border-bottom: 1px solid var(--border);
|
|
color: var(--text);
|
|
}
|
|
|
|
h3 {
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
margin-top: 1.5rem;
|
|
margin-bottom: 0.5rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.subtitle {
|
|
color: var(--text-muted);
|
|
font-size: 0.95rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.summary-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
gap: 1rem;
|
|
margin: 1.5rem 0;
|
|
}
|
|
|
|
.summary-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
padding: 1.2rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.summary-card .count {
|
|
font-size: 2.5rem;
|
|
font-weight: 700;
|
|
line-height: 1;
|
|
}
|
|
|
|
.summary-card .label {
|
|
font-size: 0.85rem;
|
|
color: var(--text-muted);
|
|
margin-top: 0.4rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.count-critical { color: var(--critical); }
|
|
.count-high { color: var(--high); }
|
|
.count-medium { color: var(--medium); }
|
|
.count-low { color: var(--low); }
|
|
.count-total { color: var(--accent); }
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 1rem 0;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
th {
|
|
background: var(--surface);
|
|
text-align: left;
|
|
padding: 0.75rem 1rem;
|
|
font-weight: 600;
|
|
color: var(--text-muted);
|
|
border-bottom: 2px solid var(--border);
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1;
|
|
}
|
|
|
|
td {
|
|
padding: 0.75rem 1rem;
|
|
border-bottom: 1px solid var(--border);
|
|
vertical-align: top;
|
|
}
|
|
|
|
tr:hover td {
|
|
background: var(--surface-2);
|
|
}
|
|
|
|
.badge {
|
|
display: inline-block;
|
|
padding: 0.15em 0.6em;
|
|
border-radius: 12px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.03em;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.badge-critical { background: var(--critical-bg); color: var(--critical); border: 1px solid var(--critical); }
|
|
.badge-high { background: var(--high-bg); color: var(--high); border: 1px solid var(--high); }
|
|
.badge-medium { background: var(--medium-bg); color: var(--medium); border: 1px solid var(--medium); }
|
|
.badge-low { background: var(--low-bg); color: var(--low); border: 1px solid var(--low); }
|
|
|
|
.area-badge {
|
|
display: inline-block;
|
|
padding: 0.15em 0.5em;
|
|
border-radius: 4px;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
code {
|
|
font-family: 'SF Mono', 'Fira Code', 'Fira Mono', Menlo, Consolas, monospace;
|
|
font-size: 0.85em;
|
|
background: var(--surface);
|
|
padding: 0.15em 0.4em;
|
|
border-radius: 4px;
|
|
border: 1px solid var(--border);
|
|
color: var(--accent);
|
|
word-break: break-all;
|
|
}
|
|
|
|
.section-description {
|
|
color: var(--text-muted);
|
|
margin-bottom: 1rem;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.quick-wins {
|
|
background: var(--green-bg);
|
|
border: 1px solid var(--green);
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
margin: 1.5rem 0;
|
|
}
|
|
|
|
.quick-wins h3 {
|
|
color: var(--green);
|
|
margin-top: 0;
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.quick-wins table {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.quick-wins td, .quick-wins th {
|
|
border-color: rgba(63, 185, 80, 0.2);
|
|
}
|
|
|
|
.agents-section {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
margin: 1.5rem 0;
|
|
}
|
|
|
|
.agents-section h3 {
|
|
margin-top: 0;
|
|
color: var(--accent);
|
|
}
|
|
|
|
.agents-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 0.75rem;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.agent-card {
|
|
background: var(--surface-2);
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
padding: 0.75rem 1rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.agent-card strong {
|
|
color: var(--accent);
|
|
}
|
|
|
|
.agent-card .agent-count {
|
|
float: right;
|
|
color: var(--text-muted);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.theme-list {
|
|
list-style: none;
|
|
padding: 0;
|
|
}
|
|
|
|
.theme-list li {
|
|
padding: 0.5rem 0;
|
|
border-bottom: 1px solid var(--border);
|
|
color: var(--text-muted);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.theme-list li:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.theme-list code {
|
|
font-size: 0.8em;
|
|
}
|
|
|
|
.cross-confirm {
|
|
font-size: 0.85rem;
|
|
color: var(--text-muted);
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.cross-confirm li {
|
|
margin-bottom: 0.4rem;
|
|
}
|
|
|
|
footer {
|
|
margin-top: 3rem;
|
|
padding-top: 1.5rem;
|
|
border-top: 1px solid var(--border);
|
|
color: var(--text-muted);
|
|
font-size: 0.85rem;
|
|
text-align: center;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
body { padding: 1rem; }
|
|
.summary-grid { grid-template-columns: repeat(2, 1fr); }
|
|
.agents-grid { grid-template-columns: 1fr; }
|
|
table { font-size: 0.8rem; }
|
|
td, th { padding: 0.5rem; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
|
|
<h1>Cofee Project — Bug Audit Report</h1>
|
|
<p class="subtitle">
|
|
Date: 2026-03-22 |
|
|
Audited by: Backend Architect, Frontend Architect, Remotion Engineer, DB Architect, Security Auditor, Performance Engineer |
|
|
~90 unique issues after deduplication
|
|
</p>
|
|
|
|
<div class="summary-grid">
|
|
<div class="summary-card">
|
|
<div class="count count-total">~90</div>
|
|
<div class="label">Total Issues</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="count count-critical">10</div>
|
|
<div class="label">Critical</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="count count-high">20</div>
|
|
<div class="label">High</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="count count-medium">30+</div>
|
|
<div class="label">Medium</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="count count-low">30+</div>
|
|
<div class="label">Low</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ============================================================ -->
|
|
<h2>Critical — Fix Immediately</h2>
|
|
<p class="section-description">These issues can cause security breaches, data loss, or application crashes under normal usage.</p>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr><th>#</th><th>Area</th><th>Issue</th><th>File(s)</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>1</td>
|
|
<td><span class="badge badge-critical">Security</span></td>
|
|
<td><strong>Path traversal</strong> — any authenticated user can read arbitrary server files via <code>../../etc/passwd</code>. The endpoint resolves the path but never validates it stays within the storage directory.</td>
|
|
<td><code>files/router.py:103</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>2</td>
|
|
<td><span class="badge badge-critical">Security</span></td>
|
|
<td><strong>Unauthenticated webhook</strong> — <code>POST /api/tasks/webhook/{job_id}/</code> has no auth. Anyone can forge job status, inject arbitrary output data, or mark jobs as failed.</td>
|
|
<td><code>tasks/router.py:195</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>3</td>
|
|
<td><span class="badge badge-critical">Security</span></td>
|
|
<td><strong>JWT in JS-accessible cookies</strong> — tokens set via <code>js-cookie</code> with no HttpOnly/Secure/SameSite flags. Any XSS steals both access and refresh tokens.</td>
|
|
<td><code>useCookie.tsx</code>, <code>LoginPage.tsx:36</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>4</td>
|
|
<td><span class="badge badge-critical">Security</span></td>
|
|
<td><strong>PyJWT CVE-2026-32597</strong> — active vulnerability in the core auth library. Fix available in v2.12.0.</td>
|
|
<td><code>pyproject.toml</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>5</td>
|
|
<td><span class="area-badge">Frontend</span></td>
|
|
<td><strong>No token refresh</strong> — when access token expires, all API calls fail with opaque <code>"Oops, fetch failed"</code>. Refresh token is set during login but never used again.</td>
|
|
<td><code>shared/api/index.ts:27</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>6</td>
|
|
<td><span class="area-badge">Frontend</span></td>
|
|
<td><strong>setState during render</strong> — <code>setCaptionedVideoFileId()</code> and <code>setStatus()</code> called outside useEffect, causing infinite re-render loops that freeze the browser tab.</td>
|
|
<td><code>CaptionResultStep.tsx:69</code>, <code>ConvertMediaView.tsx:51</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>7</td>
|
|
<td><span class="area-badge">Frontend</span></td>
|
|
<td><strong>Workspace state race condition</strong> — WizardProvider and WorkspaceProvider independently PATCH <code>workspace_state</code>, overwriting each other's data on the 1000ms debounce boundary.</td>
|
|
<td><code>WizardContext.tsx:345</code>, <code>WorkspaceContext.tsx:111</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>8</td>
|
|
<td><span class="area-badge">Backend</span></td>
|
|
<td><strong>Auth session closed prematurely</strong> — <code>get_current_user</code> closes its DB session in <code>finally</code>, leaving the returned User object detached. Any lazy-loaded relationship access causes <code>DetachedInstanceError</code>.</td>
|
|
<td><code>infrastructure/auth.py:62</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>9</td>
|
|
<td><span class="area-badge">Remotion</span></td>
|
|
<td><strong>Custom fonts never loaded</strong> — only Lobster is loaded at module level. Any other <code>font_family</code> in styleConfig silently renders with system sans-serif.</td>
|
|
<td><code>Captions.tsx:3,12</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>10</td>
|
|
<td><span class="area-badge">Performance</span></td>
|
|
<td><strong>Sequential S3 frame uploads</strong> — 300 frames uploaded one-at-a-time (30s of round-trip time). Should use <code>asyncio.gather()</code> with semaphore (~3s).</td>
|
|
<td><code>media/service.py:497</code></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- ============================================================ -->
|
|
<h2>High — Fix This Sprint</h2>
|
|
<p class="section-description">Significant bugs affecting security, correctness, or user experience. Not immediately exploitable or crash-inducing but need prompt attention.</p>
|
|
|
|
<h3>Security</h3>
|
|
<table>
|
|
<thead><tr><th>Issue</th><th>File(s)</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>No refresh token rotation</strong> — stolen token grants permanent access for 30 days with no revocation mechanism</td>
|
|
<td><code>users/router.py:211</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Remotion has zero authentication</strong> — port 3001 exposed, enables SSRF via <code>callbackUrl</code></td>
|
|
<td><code>server/index.ts:22</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>IDOR on artifacts/transcriptions/events</strong> — any authenticated user reads/modifies anyone's data (<code>_ = current_user</code>)</td>
|
|
<td><code>media/router.py:205</code>, <code>transcription/router.py:30</code>, <code>jobs/router.py:106</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>No rate limiting</strong> on login/register — unlimited brute force</td>
|
|
<td><code>users/router.py:176</code></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3>Backend</h3>
|
|
<table>
|
|
<thead><tr><th>Issue</th><th>File(s)</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>Token refresh skips user validation</strong> — deactivated users keep generating new access tokens</td>
|
|
<td><code>users/router.py:211</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Repository update drops explicit None</strong> — impossible to clear nullable fields via PATCH (affects 7 repos)</td>
|
|
<td><code>jobs/repository.py:78</code> + 6 others</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Routers bypass service layer</strong> — media, transcription, notification routers use repositories directly</td>
|
|
<td><code>media/router.py:128</code>, <code>transcription/router.py:36</code>, <code>notifications/router.py:63</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>TaskService couples to 6 cross-module repos</strong> — bypasses business rules in other modules</td>
|
|
<td><code>tasks/service.py:26</code></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3>Frontend</h3>
|
|
<table>
|
|
<thead><tr><th>Issue</th><th>File(s)</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>Zero error boundaries</strong> — any JS error crashes the entire app to a blank white screen</td>
|
|
<td><code>app/</code> (no <code>error.tsx</code> anywhere)</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>WebSocket token in URL query string</strong> — logged by proxies and browser history</td>
|
|
<td><code>SocketProvider.tsx:209</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Raw fetch() bypasses auth middleware</strong> — 3 notification endpoints use manual cookies</td>
|
|
<td><code>NotificationPopup.tsx:84,94</code>, <code>SocketProvider.tsx:156</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>FSD layer violation</strong> — feature imports from widget layer</td>
|
|
<td><code>SubtitleRevisionStep.tsx:24</code></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3>Database</h3>
|
|
<table>
|
|
<thead><tr><th>Issue</th><th>File(s)</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>Missing FK indexes on notifications</strong> — <code>job_id</code>, <code>project_id</code> cause full sequential scans</td>
|
|
<td><code>notifications/models.py:18</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>No pagination</strong> on 8 of 9 list endpoints — unbounded queries load entire tables</td>
|
|
<td>All <code>repository.py list_all()</code> methods</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>No CHECK constraints on status columns</strong> — typo in status string = invisible orphaned row</td>
|
|
<td><code>jobs/models.py</code>, <code>projects/models.py</code>, <code>notifications/models.py</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><code>files.path</code> queried without index</strong> — sequential scan on every file lookup by path</td>
|
|
<td><code>files/repository.py:36</code></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3>Remotion</h3>
|
|
<table>
|
|
<thead><tr><th>Issue</th><th>File(s)</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>No retry on DONE/FAILED webhook</strong> — missed webhook = user's job stuck forever in "running"</td>
|
|
<td><code>webhook.ts:13</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Empty transcription silently renders with no captions</strong> — wasted compute, confusing UX</td>
|
|
<td><code>useCaptions.ts:27</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Sync render path has no concurrency limit</strong> — N requests spawn N Chromium processes, causes OOM</td>
|
|
<td><code>server/index.ts:42</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><code>out/</code> directory not created at startup</strong> — first render fails outside Docker with ENOENT</td>
|
|
<td><code>render_video.ts:134</code></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3>Performance</h3>
|
|
<table>
|
|
<thead><tr><th>Issue</th><th>File(s)</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>New psycopg2 connection per cancellation check</strong> — 5-20ms overhead + connection churn in Dramatiq</td>
|
|
<td><code>tasks/service.py:224</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>No GZip middleware</strong> — transcription JSON (100KB+) sent uncompressed to frontend</td>
|
|
<td><code>main.py</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>WizardContext subscribes to full notification store</strong> — entire wizard re-renders every 3 seconds during task processing</td>
|
|
<td><code>WizardContext.tsx:353</code></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- ============================================================ -->
|
|
<h2>Medium — Fix Next Sprint</h2>
|
|
<p class="section-description">Suboptimal patterns, technical debt, and issues that compound under load or scale.</p>
|
|
|
|
<ul class="theme-list">
|
|
<li><strong>Inconsistent soft-delete</strong> — <code>is_active</code> (BaseModelMixin) vs <code>is_deleted</code> (files, media) on different tables. Some tables have both columns.</li>
|
|
<li><strong>No password complexity requirements</strong> — users can register with password <code>"a"</code> <span style="color:var(--text-muted)">(<code>users/schemas.py</code>)</span></li>
|
|
<li><strong>Connection pool defaults too small</strong> — 5+10=15 max; production with 4 workers needs 60+ <span style="color:var(--text-muted)">(<code>settings.py:44</code>)</span></li>
|
|
<li><strong>Redis connection never closed on shutdown</strong> — singleton created lazily, no lifespan cleanup <span style="color:var(--text-muted)">(<code>notifications/service.py:44</code>)</span></li>
|
|
<li><strong>No explicit session rollback on failure</strong> — uncommitted state can leak between requests <span style="color:var(--text-muted)">(<code>db/session.py:44</code>)</span></li>
|
|
<li><strong>Multiple DB commits per webhook callback</strong> — 7+ commits with no atomicity, partial saves on failure <span style="color:var(--text-muted)">(<code>tasks/service.py:1158</code>)</span></li>
|
|
<li><strong>SSR QueryClient singleton</strong> — module-level <code>new QueryClient()</code> leaks cache between server requests <span style="color:var(--text-muted)">(<code>shared/lib/query_client.ts</code>)</span></li>
|
|
<li><strong>Unused npm dependencies</strong> — lodash, axios, xior = ~85KB dead weight in bundle <span style="color:var(--text-muted)">(<code>package.json</code>)</span></li>
|
|
<li><strong>Redundant 2s polling alongside WebSocket</strong> — 30 API requests/min per active wizard, WebSocket already delivers same data <span style="color:var(--text-muted)">(<code>WizardContext.tsx:361</code>)</span></li>
|
|
<li><strong>All JSON columns should be JSONB</strong> — 10 columns use plain JSON, can't be indexed or queried efficiently <span style="color:var(--text-muted)">(all <code>models.py</code>)</span></li>
|
|
<li><strong>No <code>server_default</code> on BaseModelMixin</strong> — direct SQL/migrations bypass Python-side defaults <span style="color:var(--text-muted)">(<code>db/base.py:20</code>)</span></li>
|
|
<li><strong>S3 filename collision</strong> — re-rendering same video overwrites previous captioned version <span style="color:var(--text-muted)">(<code>remotion_service/server/services/s3.ts:76</code>)</span></li>
|
|
<li><strong><code>lines_per_screen</code> and <code>animation_speed</code> accepted but never used</strong> — schema promises features that don't exist <span style="color:var(--text-muted)">(<code>CaptionStyleSchema.ts</code>)</span></li>
|
|
<li><strong>Default JWT secret "dev-secret"</strong> — no production guard prevents deployment with guessable secret <span style="color:var(--text-muted)">(<code>settings.py:29</code>)</span></li>
|
|
<li><strong>No file content type validation on upload</strong> — extension/MIME/magic bytes not checked <span style="color:var(--text-muted)">(<code>files/router.py:39</code>)</span></li>
|
|
<li><strong>API <code>onError</code> swallows error details</strong> — all errors become <code>"Oops, fetch failed"</code>, impossible to distinguish 401/404/500 <span style="color:var(--text-muted)">(<code>shared/api/index.ts:49</code>)</span></li>
|
|
<li><strong>Irreversible migration downgrade</strong> — <code>b3c4d5e6f7a8</code> downgrade crashes with NOT NULL violation <span style="color:var(--text-muted)">(<code>alembic/versions/</code>)</span></li>
|
|
<li><strong><code>project_pct</code> column misnaming</strong> — DB says "project" but API says "progress", confusing mapping <span style="color:var(--text-muted)">(<code>jobs/models.py:34</code>, <code>notifications/service.py:143</code>)</span></li>
|
|
<li><strong>No ORM relationships defined</strong> — zero <code>relationship()</code> across 11 models, traps future N+1 patterns <span style="color:var(--text-muted)">(all <code>models.py</code>)</span></li>
|
|
<li><strong>Double audio file loading</strong> — <code>detect_silence</code> decodes the same file twice, doubling memory and time <span style="color:var(--text-muted)">(<code>media/service.py:86</code>)</span></li>
|
|
<li><strong><code>StorageService.get_file_info</code> makes 3 sequential S3 calls</strong> — could be 1 <code>head_object</code> <span style="color:var(--text-muted)">(<code>storage/base.py:88</code>)</span></li>
|
|
<li><strong>Token logged to server console</strong> — <code>console.log("Verifying token:", token)</code> in server action <span style="color:var(--text-muted)">(<code>server.ts:16</code>)</span></li>
|
|
<li><strong>framer-motion in critical path</strong> — 32KB gzipped for 2 components, should use CSS animations <span style="color:var(--text-muted)">(<code>Loader.tsx</code>, <code>HomePage.tsx</code>)</span></li>
|
|
<li><strong>Additional dependency CVEs</strong> — protobuf, pyasn1, python-multipart have known fixes available <span style="color:var(--text-muted)">(<code>pyproject.toml</code>)</span></li>
|
|
<li><strong>Webhook secrets exposed in API response</strong> — <code>WebhookRead</code> includes plaintext <code>secret</code> field <span style="color:var(--text-muted)">(<code>webhooks/schemas.py:16</code>)</span></li>
|
|
<li><strong>No request timing middleware</strong> — can't detect performance regressions <span style="color:var(--text-muted)">(<code>main.py</code>)</span></li>
|
|
<li><strong>Redis SCAN in cancellation cleanup</strong> — O(n) over entire keyspace instead of direct key lookup <span style="color:var(--text-muted)">(<code>tasks/service.py:1062</code>)</span></li>
|
|
<li><strong>No <code>/health</code> endpoint on Remotion service</strong> — Docker/K8s probes have nothing to hit <span style="color:var(--text-muted)">(<code>server/index.ts</code>)</span></li>
|
|
<li><strong>TranscriptionEditor callback churn</strong> — <code>handleSave</code> recreated on every keystroke <span style="color:var(--text-muted)">(<code>TranscriptionEditor.tsx:124</code>)</span></li>
|
|
<li><strong>No numeric bounds on Remotion schema fields</strong> — negative <code>font_size</code>, <code>fade_duration_frames</code> can crash renderer <span style="color:var(--text-muted)">(<code>CaptionStyleSchema.ts</code>, <code>DocumentSchema.ts</code>)</span></li>
|
|
</ul>
|
|
|
|
<!-- ============================================================ -->
|
|
<h2>Low — Technical Debt</h2>
|
|
<p class="section-description">Code quality issues, missing conventions, and minor inefficiencies.</p>
|
|
|
|
<ul class="theme-list">
|
|
<li>Inline error strings instead of <code>ERROR_</code> constants (all routers)</li>
|
|
<li>Inconsistent <code>is_active</code>/<code>is_deleted</code> semantics (some models have both columns)</li>
|
|
<li>19 <code>console.log</code>/<code>console.error</code> statements in production frontend code</li>
|
|
<li>Missing <code>data-testid</code> on 18 of 21 shared UI components</li>
|
|
<li>No Content-Security-Policy or security headers on frontend</li>
|
|
<li>OpenAPI/Swagger docs exposed unconditionally (even in production)</li>
|
|
<li>Redis without authentication in Docker Compose</li>
|
|
<li>Default DB credentials <code>postgres/postgres</code> with no production guard</li>
|
|
<li>MinIO default credentials <code>minioadmin/minioadmin</code></li>
|
|
<li><code>email</code> column has no unique constraint</li>
|
|
<li>Webhook secrets stored as plaintext in DB</li>
|
|
<li><code>broker_id</code> on jobs has no index</li>
|
|
<li>Duplicate <code>json</code> import in <code>media/service.py</code></li>
|
|
<li><code>formatBytes</code> duplicated in 3 Remotion files</li>
|
|
<li><code>GET /api/render</code> returns bare string "Hello" (debug leftover)</li>
|
|
<li><code>justifyContent</code> uses "left"/"right" instead of "flex-start"/"flex-end" in Remotion</li>
|
|
<li>Module-level mutable <code>regionIdCounter</code> shared across component instances</li>
|
|
<li><code>FragmentsStep</code> component is 843 lines (guideline: 150 max)</li>
|
|
<li>Login page shows no error message to user on failure</li>
|
|
<li><code>.env</code> not in backend <code>.gitignore</code></li>
|
|
<li><code>useBreadcrumbs</code> uses <code>JSON.stringify</code> in dependency array</li>
|
|
<li><code>BreadcrumbsProvider</code> context value not memoized</li>
|
|
<li><code>TranscriptionModal</code> passes <code>queryKey</code> in wrong argument position</li>
|
|
<li>Only <code>SIGTERM</code> handled in Remotion, not <code>SIGINT</code></li>
|
|
<li>Short <code>removeOnFail</code> TTL (2h) makes debugging failed renders difficult</li>
|
|
</ul>
|
|
|
|
<!-- ============================================================ -->
|
|
<div class="quick-wins">
|
|
<h3>Top 5 Quick Wins (highest impact, lowest effort)</h3>
|
|
<table>
|
|
<thead><tr><th>Fix</th><th>Effort</th><th>Impact</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>Path traversal guard — add 3-line <code>is_relative_to()</code> check</td>
|
|
<td>5 min</td>
|
|
<td>Blocks arbitrary file read (Critical security fix)</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Add <code>GZipMiddleware</code> — single line in <code>main.py</code></td>
|
|
<td>2 min</td>
|
|
<td>5-10x smaller JSON responses</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Parallelize S3 frame uploads — <code>asyncio.gather()</code> + semaphore</td>
|
|
<td>30 min</td>
|
|
<td>10-60s saved per frame extraction job</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Remove unused npm packages (lodash, axios, xior)</td>
|
|
<td>5 min</td>
|
|
<td>~85KB bundle size reduction</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Fix setState-during-render — wrap in <code>useEffect</code></td>
|
|
<td>10 min</td>
|
|
<td>Prevents browser tab freezes</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- ============================================================ -->
|
|
<div class="agents-section">
|
|
<h3>Contributing Agents</h3>
|
|
<div class="agents-grid">
|
|
<div class="agent-card">
|
|
<strong>Backend Architect</strong> <span class="agent-count">25 findings</span><br>
|
|
API logic, race conditions, service layer patterns, error handling
|
|
</div>
|
|
<div class="agent-card">
|
|
<strong>Frontend Architect</strong> <span class="agent-count">24 findings</span><br>
|
|
React/Next.js bugs, state management, FSD compliance, type safety
|
|
</div>
|
|
<div class="agent-card">
|
|
<strong>Remotion Engineer</strong> <span class="agent-count">22 findings</span><br>
|
|
Render pipeline, S3 integration, caption edge cases, webhook reliability
|
|
</div>
|
|
<div class="agent-card">
|
|
<strong>DB Architect</strong> <span class="agent-count">23 findings</span><br>
|
|
Schema issues, missing indexes, migration risks, query patterns
|
|
</div>
|
|
<div class="agent-card">
|
|
<strong>Security Auditor</strong> <span class="agent-count">22 findings</span><br>
|
|
OWASP audit, auth/JWT, IDOR, SSRF, dependency CVEs, scanning tools
|
|
</div>
|
|
<div class="agent-card">
|
|
<strong>Performance Engineer</strong> <span class="agent-count">25 findings</span><br>
|
|
Async blocking, S3 throughput, connection pools, re-renders, bundle size
|
|
</div>
|
|
</div>
|
|
|
|
<ul class="cross-confirm">
|
|
<li><strong>Path traversal</strong> and <strong>unauthenticated webhook</strong> — confirmed independently by Backend Architect + Security Auditor</li>
|
|
<li><strong>Missing pagination</strong> — flagged by Backend Architect + DB Architect + Performance Engineer</li>
|
|
<li><strong>Inconsistent soft-delete</strong> — flagged by Backend Architect + DB Architect</li>
|
|
<li><strong>IDOR on artifacts/transcriptions</strong> — flagged by Backend Architect + DB Architect + Security Auditor</li>
|
|
<li><strong>WizardContext re-renders</strong> — flagged by Frontend Architect + Performance Engineer</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<footer>
|
|
Generated by Claude Code agent team (Orchestrator + 6 specialists) on 2026-03-22
|
|
</footer>
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|