Lucifer-cyber007 Claude commited on
Commit
2514bac
·
1 Parent(s): d4a0163

feat: add free code review endpoint and associated features

Browse files

- Add new /review/free endpoint for arbitrary code review
- Implement free_review.py module with review_free_code function
- Update app.py with FreeReviewRequest and FreeReviewResponse models
- Update dashboard.html with free review functionality
- Update tasks.py with enhancements

This allows users to review any code without being confined to grading,
perfect for ad-hoc reviews and demonstrations.

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (7) hide show
  1. app.py +62 -0
  2. dashboard.html +473 -0
  3. free_review.py +91 -0
  4. tasks.py +500 -168
  5. tmpclaude-46e1-cwd +1 -0
  6. tmpclaude-5602-cwd +1 -0
  7. tmpclaude-ee5f-cwd +1 -0
app.py CHANGED
@@ -28,6 +28,7 @@ from models import (
28
  from environment import CodeReviewEnv
29
  from graders import grade_episode
30
  from tasks import get_all_tasks
 
31
 
32
  # ── App setup ─────────────────────────────────────────────────────────────
33
  app = FastAPI(
@@ -251,6 +252,67 @@ def baseline(request: Optional[BaselineRequest] = None):
251
  inference.parse_llm_response = original_parse
252
 
253
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  # ── Debug Route ─────────────────────────────────────────────────────────
255
 
256
  @app.post("/debug-baseline", tags=["Debug"])
 
28
  from environment import CodeReviewEnv
29
  from graders import grade_episode
30
  from tasks import get_all_tasks
31
+ from free_review import review_free_code
32
 
33
  # ── App setup ─────────────────────────────────────────────────────────────
34
  app = FastAPI(
 
252
  inference.parse_llm_response = original_parse
253
 
254
 
255
+ # ── Free Review Route ───────────────────────────────────────────────────
256
+
257
+ class FreeReviewRequest(BaseModel):
258
+ code: str
259
+ language: Optional[str] = "python"
260
+ context: Optional[str] = ""
261
+
262
+ class FreeReviewResponse(BaseModel):
263
+ issues: list
264
+ overall_verdict: str
265
+ summary: str
266
+ positive_aspects: list
267
+ total_issues: int
268
+ critical_count: int
269
+ major_count: int
270
+ minor_count: int
271
+ error: Optional[str] = None
272
+
273
+ @app.post("/review/free", tags=["Free Review"])
274
+ def free_review(request: FreeReviewRequest):
275
+ """
276
+ Review any arbitrary code using AI.
277
+ No grading — works on any code, any language.
278
+ Perfect for ad-hoc reviews and demos.
279
+ """
280
+ result = review_free_code(
281
+ code=request.code,
282
+ language=request.language,
283
+ context=request.context
284
+ )
285
+
286
+ if "error" in result:
287
+ return FreeReviewResponse(
288
+ issues=[],
289
+ overall_verdict="error",
290
+ summary=result["error"],
291
+ positive_aspects=[],
292
+ total_issues=0,
293
+ critical_count=0,
294
+ major_count=0,
295
+ minor_count=0,
296
+ error=result["error"]
297
+ )
298
+
299
+ issues = result.get("issues", [])
300
+ return FreeReviewResponse(
301
+ issues=issues,
302
+ overall_verdict=result.get("overall_verdict", "comment"),
303
+ summary=result.get("summary", ""),
304
+ positive_aspects=result.get("positive_aspects", []),
305
+ total_issues=len(issues),
306
+ critical_count=sum(1 for i in issues
307
+ if i.get("severity") == "critical"),
308
+ major_count=sum(1 for i in issues
309
+ if i.get("severity") == "major"),
310
+ minor_count=sum(1 for i in issues
311
+ if i.get("severity") == "minor"),
312
+ error=None
313
+ )
314
+
315
+
316
  # ── Debug Route ─────────────────────────────────────────────────────────
317
 
318
  @app.post("/debug-baseline", tags=["Debug"])
dashboard.html CHANGED
@@ -1609,6 +1609,248 @@
1609
  stroke-width: 2;
1610
  }
1611
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1612
  /* ── TOAST ──────────────────────────────────────────────────────────── */
1613
  #toast-container {
1614
  position: fixed;
@@ -1927,8 +2169,13 @@
1927
  <label class="field-label" for="task-select">Task Difficulty</label>
1928
  <select id="task-select" aria-label="Select task difficulty">
1929
  <option value="easy">Easy — Basic Bug Detection</option>
 
1930
  <option value="medium">Medium — Security Review</option>
 
 
1931
  <option value="hard">Hard — Concurrency Hunt</option>
 
 
1932
  </select>
1933
 
1934
  <button class="btn btn-purple btn-full" id="start-btn" style="margin-top:16px;" onclick="startReview()">
@@ -2074,6 +2321,112 @@
2074
  </div>
2075
  </section>
2076
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2077
  <hr class="divider" />
2078
 
2079
  <!-- ══════════════════════════════════════════════════════════════════════ -->
@@ -2167,6 +2520,126 @@
2167
  <button class="task-try-btn" id="try-hard-btn" onclick="jumpToDemo('hard')" aria-label="Try hard task">Try
2168
  this task →</button>
2169
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2170
  </div>
2171
  </div>
2172
  </section>
 
1609
  stroke-width: 2;
1610
  }
1611
 
1612
+ /* ── FREE REVIEW ────────────────────────────────────────────────────── */
1613
+ .free-review-container {
1614
+ display: grid;
1615
+ grid-template-columns: 45% 1fr;
1616
+ gap: 32px;
1617
+ margin-top: 32px;
1618
+ }
1619
+
1620
+ .free-input-panel {
1621
+ display: flex;
1622
+ flex-direction: column;
1623
+ gap: 16px;
1624
+ }
1625
+
1626
+ .free-editor-wrapper {
1627
+ position: relative;
1628
+ background: #0d1117;
1629
+ border: 1px solid var(--border);
1630
+ border-radius: var(--radius-md);
1631
+ overflow: hidden;
1632
+ flex: 1;
1633
+ display: flex;
1634
+ flex-direction: column;
1635
+ min-height: 300px;
1636
+ }
1637
+
1638
+ .free-editor-wrapper:focus-within {
1639
+ border-color: var(--purple);
1640
+ box-shadow: 0 0 0 1px var(--purple);
1641
+ }
1642
+
1643
+ #free-code-input {
1644
+ background: transparent;
1645
+ border: none;
1646
+ color: #e6edf3;
1647
+ font-family: 'JetBrains Mono', monospace;
1648
+ font-size: 13px;
1649
+ padding: 16px;
1650
+ width: 100%;
1651
+ height: 100%;
1652
+ resize: vertical;
1653
+ min-height: 300px;
1654
+ outline: none;
1655
+ }
1656
+
1657
+ #free-code-input::placeholder {
1658
+ color: var(--muted);
1659
+ }
1660
+
1661
+ .free-output-panel {
1662
+ background: var(--surface);
1663
+ border: 1px solid var(--border);
1664
+ border-radius: var(--radius-md);
1665
+ overflow: hidden;
1666
+ display: flex;
1667
+ flex-direction: column;
1668
+ }
1669
+
1670
+ .empty-state {
1671
+ display: flex;
1672
+ flex-direction: column;
1673
+ align-items: center;
1674
+ justify-content: center;
1675
+ padding: 64px 32px;
1676
+ color: var(--muted);
1677
+ text-align: center;
1678
+ height: 100%;
1679
+ }
1680
+
1681
+ .empty-state svg {
1682
+ width: 48px;
1683
+ height: 48px;
1684
+ margin-bottom: 16px;
1685
+ stroke: var(--border2);
1686
+ fill: none;
1687
+ stroke-width: 1.5;
1688
+ }
1689
+
1690
+ .loading-state {
1691
+ display: none;
1692
+ flex-direction: column;
1693
+ align-items: center;
1694
+ justify-content: center;
1695
+ padding: 64px 32px;
1696
+ height: 100%;
1697
+ }
1698
+
1699
+ .results-state {
1700
+ display: none;
1701
+ flex-direction: column;
1702
+ height: 100%;
1703
+ }
1704
+
1705
+ .results-summary-bar {
1706
+ padding: 16px 24px;
1707
+ border-bottom: 1px solid var(--border);
1708
+ display: flex;
1709
+ align-items: center;
1710
+ justify-content: space-between;
1711
+ flex-wrap: wrap;
1712
+ gap: 16px;
1713
+ }
1714
+
1715
+ .verdict-badge {
1716
+ padding: 4px 10px;
1717
+ border-radius: 4px;
1718
+ font-size: 12px;
1719
+ font-weight: 700;
1720
+ text-transform: uppercase;
1721
+ letter-spacing: 0.5px;
1722
+ }
1723
+
1724
+ .verdict-approve { background: rgba(35, 134, 54, 0.15); color: #3fb950; border: 1px solid rgba(63, 185, 80, 0.3); }
1725
+ .verdict-request_changes { background: rgba(218, 54, 51, 0.15); color: #ff7b72; border: 1px solid rgba(255, 123, 114, 0.3); }
1726
+ .verdict-comment { background: rgba(210, 153, 34, 0.15); color: #e3b341; border: 1px solid rgba(227, 179, 65, 0.3); }
1727
+ .verdict-error { background: rgba(218, 54, 51, 0.15); color: #ff7b72; border: 1px solid rgba(255, 123, 114, 0.3); }
1728
+
1729
+ .severity-counts {
1730
+ display: flex;
1731
+ gap: 12px;
1732
+ font-size: 13px;
1733
+ }
1734
+
1735
+ .scount { display: flex; align-items: center; gap: 4px; }
1736
+ .sc-crit { color: #ff7b72; }
1737
+ .sc-maj { color: #e3b341; }
1738
+ .sc-min { color: var(--muted); }
1739
+
1740
+ .results-body {
1741
+ padding: 24px;
1742
+ overflow-y: auto;
1743
+ flex: 1;
1744
+ }
1745
+
1746
+ .ai-summary {
1747
+ font-size: 14px;
1748
+ line-height: 1.6;
1749
+ margin-bottom: 24px;
1750
+ padding: 16px;
1751
+ background: rgba(142, 68, 173, 0.05);
1752
+ border-left: 3px solid var(--purple);
1753
+ border-radius: 0 4px 4px 0;
1754
+ }
1755
+
1756
+ .positives-list {
1757
+ margin-bottom: 32px;
1758
+ }
1759
+
1760
+ .positives-list h4 {
1761
+ font-size: 13px;
1762
+ color: var(--muted);
1763
+ text-transform: uppercase;
1764
+ letter-spacing: 0.5px;
1765
+ margin-bottom: 12px;
1766
+ display: flex;
1767
+ align-items: center;
1768
+ gap: 8px;
1769
+ }
1770
+
1771
+ .positives-list ul {
1772
+ list-style: none;
1773
+ padding: 0;
1774
+ margin: 0;
1775
+ display: flex;
1776
+ flex-direction: column;
1777
+ gap: 8px;
1778
+ }
1779
+
1780
+ .positives-list li {
1781
+ display: flex;
1782
+ align-items: flex-start;
1783
+ gap: 8px;
1784
+ font-size: 13px;
1785
+ }
1786
+
1787
+ .positives-list li::before {
1788
+ content: '✓';
1789
+ color: #3fb950;
1790
+ font-weight: bold;
1791
+ }
1792
+
1793
+ .issues-list {
1794
+ display: flex;
1795
+ flex-direction: column;
1796
+ gap: 16px;
1797
+ }
1798
+
1799
+ .issue-card {
1800
+ padding: 16px;
1801
+ border: 1px solid var(--border);
1802
+ border-radius: var(--radius-sm);
1803
+ background: var(--bg);
1804
+ }
1805
+
1806
+ .ic-critical { border-left: 3px solid #ff7b72; }
1807
+ .ic-major { border-left: 3px solid #e3b341; }
1808
+ .ic-minor { border-left: 3px solid var(--border2); }
1809
+
1810
+ .issue-header {
1811
+ display: flex;
1812
+ align-items: center;
1813
+ gap: 12px;
1814
+ margin-bottom: 8px;
1815
+ }
1816
+
1817
+ .issue-line {
1818
+ font-family: 'JetBrains Mono', monospace;
1819
+ font-size: 12px;
1820
+ color: var(--muted);
1821
+ background: var(--surface);
1822
+ padding: 2px 6px;
1823
+ border-radius: 4px;
1824
+ }
1825
+
1826
+ .issue-badge {
1827
+ font-size: 11px;
1828
+ padding: 2px 6px;
1829
+ border-radius: 4px;
1830
+ font-weight: 600;
1831
+ text-transform: uppercase;
1832
+ }
1833
+
1834
+ .ib-type { background: var(--surface); border: 1px solid var(--border2); }
1835
+ .ib-crit { background: rgba(218, 54, 51, 0.1); color: #ff7b72; }
1836
+ .ib-maj { background: rgba(210, 153, 34, 0.1); color: #e3b341; }
1837
+ .ib-min { background: var(--surface); color: var(--muted); }
1838
+
1839
+ .issue-desc {
1840
+ font-size: 14px;
1841
+ margin-bottom: 8px;
1842
+ }
1843
+
1844
+ .issue-fix {
1845
+ font-size: 13px;
1846
+ color: #3fb950;
1847
+ background: rgba(63, 185, 80, 0.05);
1848
+ padding: 8px 12px;
1849
+ border-radius: 4px;
1850
+ margin-top: 8px;
1851
+ font-family: 'JetBrains Mono', monospace;
1852
+ }
1853
+
1854
  /* ── TOAST ──────────────────────────────────────────────────────────── */
1855
  #toast-container {
1856
  position: fixed;
 
2169
  <label class="field-label" for="task-select">Task Difficulty</label>
2170
  <select id="task-select" aria-label="Select task difficulty">
2171
  <option value="easy">Easy — Basic Bug Detection</option>
2172
+ <option value="js-async">Easy-Medium — JS Async Flow</option>
2173
  <option value="medium">Medium — Security Review</option>
2174
+ <option value="sql-injection">Medium — SQL Injection Hunt</option>
2175
+ <option value="react-security">Medium — React Security</option>
2176
  <option value="hard">Hard — Concurrency Hunt</option>
2177
+ <option value="django-auth">Hard — Django Auth Logic</option>
2178
+ <option value="node-race">Hard — Node.js Race Condition</option>
2179
  </select>
2180
 
2181
  <button class="btn btn-purple btn-full" id="start-btn" style="margin-top:16px;" onclick="startReview()">
 
2321
  </div>
2322
  </section>
2323
 
2324
+ <!-- ══════════════════════════════════════════════════════════════════════ -->
2325
+ <!-- FREE CODE REVIEW -->
2326
+ <!-- ══════════════════════════════════════════════════════════════════════ -->
2327
+ <section class="section" id="free-review">
2328
+ <div class="container">
2329
+ <div class="section-header fade-up">
2330
+ <div style="display:flex;align-items:center;gap:12px;">
2331
+ <div class="section-label">Beta Feature</div>
2332
+ <span style="background:rgba(210,153,34,0.15);color:#e3b341;border:1px solid rgba(227,179,65,0.3);padding:2px 8px;border-radius:12px;font-size:11px;font-weight:bold;text-transform:uppercase;">BETA</span>
2333
+ </div>
2334
+ <h2 class="section-title">Free Code Review</h2>
2335
+ <p class="section-sub">Paste any code — get instant AI feedback</p>
2336
+ </div>
2337
+
2338
+ <div class="free-review-container fade-up delay-1">
2339
+ <!-- LEFT: Input -->
2340
+ <div class="free-input-panel">
2341
+ <div style="display:flex;gap:12px;">
2342
+ <div style="flex:1;">
2343
+ <select id="free-language" aria-label="Language selector" style="width:100%;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);padding:10px 14px;outline:none;">
2344
+ <option value="python">Python</option>
2345
+ <option value="javascript">JavaScript</option>
2346
+ <option value="typescript">TypeScript</option>
2347
+ <option value="sql">SQL</option>
2348
+ <option value="go">Go</option>
2349
+ <option value="java">Java</option>
2350
+ <option value="cpp">C++</option>
2351
+ </select>
2352
+ </div>
2353
+ <div style="flex:2;">
2354
+ <input type="text" id="free-context" placeholder="Context: What does this code do? (optional)"
2355
+ style="width:100%;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);padding:10px 14px;outline:none;">
2356
+ </div>
2357
+ </div>
2358
+
2359
+ <div class="free-editor-wrapper">
2360
+ <textarea id="free-code-input" placeholder="Paste your code here...&#10;Supports Python, JS, SQL, Go, Java, C++ and more"></textarea>
2361
+ </div>
2362
+
2363
+ <button class="btn btn-purple btn-full" id="run-free-btn" onclick="runFreeReview()" style="height:48px;font-size:15px;font-weight:600;box-shadow:0 0 16px rgba(142,68,173,0.3);">
2364
+ <svg viewBox="0 0 24 24" style="width:18px;height:18px;margin-right:8px;stroke:currentColor;fill:none;stroke-width:2;vertical-align:middle;margin-top:-2px;">
2365
+ <circle cx="11" cy="11" r="8"></circle>
2366
+ <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
2367
+ </svg>
2368
+ Review My Code
2369
+ </button>
2370
+
2371
+ <div style="font-size:11px;color:var(--muted);text-align:center;">
2372
+ AI-powered review. No ground truth scoring. For exploration and demos only.
2373
+ </div>
2374
+ </div>
2375
+
2376
+ <!-- RIGHT: Output -->
2377
+ <div class="free-output-panel">
2378
+
2379
+ <!-- Empty State -->
2380
+ <div class="empty-state" id="free-empty">
2381
+ <svg viewBox="0 0 24 24">
2382
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
2383
+ <polyline points="14 2 14 8 20 8"></polyline>
2384
+ <line x1="16" y1="13" x2="8" y2="13"></line>
2385
+ <line x1="16" y1="17" x2="8" y2="17"></line>
2386
+ <polyline points="10 9 9 9 8 9"></polyline>
2387
+ </svg>
2388
+ <div>Paste code and click <strong>Review</strong> to get AI feedback</div>
2389
+ </div>
2390
+
2391
+ <!-- Loading State -->
2392
+ <div class="loading-state" id="free-loading">
2393
+ <div class="spinner" style="width:32px;height:32px;border-width:3px;margin-bottom:16px;"></div>
2394
+ <div style="color:var(--text);font-weight:600;">AI is reviewing your code...</div>
2395
+ <div style="color:var(--muted);font-size:13px;margin-top:8px;">This usually takes 5-10 seconds depending on code length.</div>
2396
+ </div>
2397
+
2398
+ <!-- Results State -->
2399
+ <div class="results-state" id="free-results">
2400
+ <div class="results-summary-bar">
2401
+ <div style="display:flex;align-items:center;gap:12px;">
2402
+ <span id="free-verdict" class="verdict-badge verdict-comment">COMMENT</span>
2403
+ <span style="font-weight:600;font-size:15px;" id="free-issue-count">0 issues found</span>
2404
+ </div>
2405
+ <div class="severity-counts">
2406
+ <div class="scount"><span class="sc-crit">🔴</span> <span id="sc-c">0</span> critical</div>
2407
+ <div class="scount"><span class="sc-maj">🟡</span> <span id="sc-m">0</span> major</div>
2408
+ <div class="scount"><span class="sc-min">⚪</span> <span id="sc-mi">0</span> minor</div>
2409
+ </div>
2410
+ </div>
2411
+
2412
+ <div class="results-body">
2413
+ <div class="ai-summary" id="free-summary"></div>
2414
+
2415
+ <div class="positives-list" id="free-positives">
2416
+ <h4>What looks good</h4>
2417
+ <ul id="free-pos-ul"></ul>
2418
+ </div>
2419
+
2420
+ <div class="issues-list" id="free-issues">
2421
+ <!-- Issues populated by JS -->
2422
+ </div>
2423
+ </div>
2424
+ </div>
2425
+ </div>
2426
+ </div>
2427
+ </div>
2428
+ </section>
2429
+
2430
  <hr class="divider" />
2431
 
2432
  <!-- ══════════════════════════════════════════════════════════════════════ -->
 
2520
  <button class="task-try-btn" id="try-hard-btn" onclick="jumpToDemo('hard')" aria-label="Try hard task">Try
2521
  this task →</button>
2522
  </div>
2523
+
2524
+ <!-- JS Async -->
2525
+ <div class="task-card easy fade-up delay-1" id="task-card-js-async">
2526
+ <div class="task-card-top">
2527
+ <span class="diff-badge">EASY-MEDIUM</span>
2528
+ </div>
2529
+ <h3 class="task-name">JavaScript Async Flow Review</h3>
2530
+ <p class="task-file">api/fetcher.js</p>
2531
+ <p class="task-desc">Review a JavaScript module handling data fetching and state updates. Find async/await pitfalls and race conditions.</p>
2532
+ <div class="task-tags">
2533
+ <span class="tag">Missing Await</span>
2534
+ <span class="tag">Promise Errors</span>
2535
+ <span class="tag">Race Condition</span>
2536
+ <span class="tag">Caching Logic</span>
2537
+ </div>
2538
+ <div class="task-meta">
2539
+ <span>Max 4 steps</span>
2540
+ <span class="sep">•</span>
2541
+ <span>3 issues hidden</span>
2542
+ <span class="sep">•</span>
2543
+ <span>Score: 0.92</span>
2544
+ </div>
2545
+ <button class="task-try-btn" id="try-js-async-btn" onclick="jumpToDemo('js-async')" aria-label="Try JS async task">Try this task →</button>
2546
+ </div>
2547
+
2548
+ <!-- SQL Injection -->
2549
+ <div class="task-card medium fade-up delay-2" id="task-card-sql-injection">
2550
+ <div class="task-card-top">
2551
+ <span class="diff-badge">MEDIUM</span>
2552
+ </div>
2553
+ <h3 class="task-name">Advanced SQL Injection Hunt</h3>
2554
+ <p class="task-file">db/reports.js</p>
2555
+ <p class="task-desc">Review a Node.js database service. Identify multiple sophisticated SQL injection patterns in dynamic queries.</p>
2556
+ <div class="task-tags">
2557
+ <span class="tag">Order By Injection</span>
2558
+ <span class="tag">Limit Injection</span>
2559
+ <span class="tag">Template Literals</span>
2560
+ <span class="tag">Parameterization</span>
2561
+ </div>
2562
+ <div class="task-meta">
2563
+ <span>Max 5 steps</span>
2564
+ <span class="sep">•</span>
2565
+ <span>4 vulnerabilities</span>
2566
+ <span class="sep">•</span>
2567
+ <span>Score: 0.88</span>
2568
+ </div>
2569
+ <button class="task-try-btn" id="try-sql-injection-btn" onclick="jumpToDemo('sql-injection')" aria-label="Try SQL injection task">Try this task →</button>
2570
+ </div>
2571
+
2572
+ <!-- React Security -->
2573
+ <div class="task-card medium fade-up delay-3" id="task-card-react-security">
2574
+ <div class="task-card-top">
2575
+ <span class="diff-badge">MEDIUM</span>
2576
+ </div>
2577
+ <h3 class="task-name">React Component Security</h3>
2578
+ <p class="task-file">components/UserProfile.jsx</p>
2579
+ <p class="task-desc">Review a React component for XSS risks (dangerouslySetInnerHTML) and sensitive data leaks in console/URLs.</p>
2580
+ <div class="task-tags">
2581
+ <span class="tag">XSS</span>
2582
+ <span class="tag">Data Leaks</span>
2583
+ <span class="tag">Sanitization</span>
2584
+ <span class="tag">React Hooks</span>
2585
+ </div>
2586
+ <div class="task-meta">
2587
+ <span>Max 4 steps</span>
2588
+ <span class="sep">•</span>
2589
+ <span>3 issues hidden</span>
2590
+ <span class="sep">•</span>
2591
+ <span>Score: 0.90</span>
2592
+ </div>
2593
+ <button class="task-try-btn" id="try-react-security-btn" onclick="jumpToDemo('react-security')" aria-label="Try React task">Try this task →</button>
2594
+ </div>
2595
+
2596
+ <!-- Django Auth -->
2597
+ <div class="task-card hard fade-up delay-1" id="task-card-django-auth">
2598
+ <div class="task-card-top">
2599
+ <span class="diff-badge">HARD</span>
2600
+ </div>
2601
+ <h3 class="task-name">Django Auth Logic Review</h3>
2602
+ <p class="task-file">auth/middleware.py</p>
2603
+ <p class="task-desc">Review Django middleware and auth backends for bypasses, timing attacks, and improper exception handling.</p>
2604
+ <div class="task-tags">
2605
+ <span class="tag">Auth Bypass</span>
2606
+ <span class="tag">Timing Attack</span>
2607
+ <span class="tag">Middleware Logic</span>
2608
+ <span class="tag">Security Logic</span>
2609
+ </div>
2610
+ <div class="task-meta">
2611
+ <span>Max 7 steps</span>
2612
+ <span class="sep">•</span>
2613
+ <span>3 critical issues</span>
2614
+ <span class="sep">•</span>
2615
+ <span>Score: 0.85</span>
2616
+ </div>
2617
+ <button class="task-try-btn" id="try-django-auth-btn" onclick="jumpToDemo('django-auth')" aria-label="Try Django task">Try this task →</button>
2618
+ </div>
2619
+
2620
+ <!-- Node Race -->
2621
+ <div class="task-card hard fade-up delay-2" id="task-card-node-race">
2622
+ <div class="task-card-top">
2623
+ <span class="diff-badge">HARD</span>
2624
+ </div>
2625
+ <h3 class="task-name">Node.js Concurrency Issues</h3>
2626
+ <p class="task-file">services/inventory.js</p>
2627
+ <p class="task-desc">Identify race conditions and stale state updates in a Node.js singleton service responsible for inventory tracking.</p>
2628
+ <div class="task-tags">
2629
+ <span class="tag">Race Condition</span>
2630
+ <span class="tag">Stale State</span>
2631
+ <span class="tag">Atomicity</span>
2632
+ <span class="tag">Singleton Pattern</span>
2633
+ </div>
2634
+ <div class="task-meta">
2635
+ <span>Max 6 steps</span>
2636
+ <span class="sep">•</span>
2637
+ <span>2 critical loops</span>
2638
+ <span class="sep">•</span>
2639
+ <span>Score: 0.82</span>
2640
+ </div>
2641
+ <button class="task-try-btn" id="try-node-race-btn" onclick="jumpToDemo('node-race')" aria-label="Try Node race task">Try this task →</button>
2642
+ </div>
2643
  </div>
2644
  </div>
2645
  </section>
free_review.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from openai import OpenAI
2
+ import os
3
+ import json
4
+
5
+ import inference
6
+
7
+ GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
8
+ GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/"
9
+ DEFAULT_MODEL = "gemini-2.0-flash"
10
+ # use whatever model name is working in inference.py currently
11
+
12
+ SYSTEM_PROMPT = """You are an expert code reviewer with 15+ years
13
+ of experience. Review the provided code and identify ALL issues.
14
+
15
+ For each issue found, provide:
16
+ - line_number: exact line where issue occurs (integer)
17
+ - issue_type: one of bug/security/performance/style/logic
18
+ - severity: one of critical/major/minor
19
+ - description: clear explanation of the problem
20
+ - suggested_fix: how to fix it
21
+
22
+ Also provide:
23
+ - overall_verdict: approve/request_changes/comment
24
+ - summary: 2-3 sentence overall assessment
25
+ - positive_aspects: list of 2-3 things done well
26
+
27
+ Respond ONLY with valid JSON, no markdown:
28
+ {
29
+ "issues": [
30
+ {
31
+ "line_number": <int>,
32
+ "issue_type": "<type>",
33
+ "severity": "<severity>",
34
+ "description": "<description>",
35
+ "suggested_fix": "<fix>"
36
+ }
37
+ ],
38
+ "overall_verdict": "<verdict>",
39
+ "summary": "<summary>",
40
+ "positive_aspects": ["<aspect1>", "<aspect2>"]
41
+ }"""
42
+
43
+ def review_free_code(code: str, language: str = "python",
44
+ context: str = "") -> dict:
45
+ """
46
+ Review any arbitrary code using Gemini.
47
+ Returns structured findings without a grader score.
48
+ """
49
+ api_key_to_use = GEMINI_API_KEY if GEMINI_API_KEY else inference._api_key
50
+ base_url_to_use = GEMINI_BASE_URL if GEMINI_API_KEY else inference.API_BASE_URL
51
+
52
+ if not api_key_to_use:
53
+ return {"error": "GEMINI_API_KEY not set and inference proxy key unavailable"}
54
+
55
+ client = OpenAI(
56
+ api_key=api_key_to_use,
57
+ base_url=base_url_to_use
58
+ )
59
+
60
+ user_prompt = f"""Language: {language}
61
+ Context: {context if context else "General code review"}
62
+
63
+ Code to review:
64
+ ```{language}
65
+ {code}
66
+ ```
67
+
68
+ Review this code thoroughly and return JSON only."""
69
+
70
+ try:
71
+ response = client.chat.completions.create(
72
+ model=DEFAULT_MODEL,
73
+ messages=[
74
+ {"role": "system", "content": SYSTEM_PROMPT},
75
+ {"role": "user", "content": user_prompt}
76
+ ],
77
+ temperature=0.1,
78
+ max_tokens=3000,
79
+ )
80
+ content = response.choices[0].message.content.strip()
81
+ if content.startswith("```"):
82
+ lines = content.split("\n")
83
+ content = "\n".join(lines[1:])
84
+ if content.strip().endswith("```"):
85
+ content = content.strip()[:-3].strip()
86
+ return json.loads(content)
87
+ except json.JSONDecodeError:
88
+ return {"error": "Failed to parse AI response",
89
+ "raw": content[:500]}
90
+ except Exception as e:
91
+ return {"error": str(e)}
tasks.py CHANGED
@@ -19,38 +19,38 @@ TASKS: Dict[str, Dict[str, Any]] = {
19
  "file_name": "utils/statistics.py",
20
  "diff": """\
21
  --- a/utils/statistics.py
22
- +++ b/utils/statistics.py
23
  @@ -0,0 +1,30 @@
24
- +def calculate_average(numbers):
25
- + total = 0
26
- + for num in numbers:
27
- + total += num
28
- + return total / len(numbers)
29
- +
30
- +def calculate_max(numbers):
31
- + max_val = numbers[0]
32
- + for num in numbers:
33
- + if num > max_val:
34
- + max_val = num
35
- + return max_val
36
- +
37
- +def get_percentage(value, total):
38
- + return (value / total) * 100
39
- +
40
- +def find_duplicates(items):
41
- + seen = []
42
- + duplicates = []
43
- + for item in items:
44
- + if item in seen:
45
- + duplicates.append(item)
46
- + seen.append(item)
47
- + return duplicates
48
- +
49
- +def safe_divide(a, b):
50
- + if b != 0:
51
- + return a / b
52
- + else:
53
- + return 0
54
  """,
55
  "known_issues": [
56
  {
@@ -104,51 +104,51 @@ TASKS: Dict[str, Dict[str, Any]] = {
104
  "file_name": "auth/user_manager.py",
105
  "diff": """\
106
  --- a/auth/user_manager.py
107
- +++ b/auth/user_manager.py
108
  @@ -0,0 +1,52 @@
109
- +import hashlib
110
- +
111
- +DB_PASSWORD = "admin123"
112
- +API_SECRET = "supersecret_key_do_not_share"
113
- +
114
- +def get_user(user_id):
115
- + query = f"SELECT * FROM users WHERE id = {user_id}"
116
- + return execute_query(query)
117
- +
118
- +def login(username, password):
119
- + hashed = hashlib.md5(password.encode()).hexdigest()
120
- + query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{hashed}'"
121
- + user = execute_query(query)
122
- + if user:
123
- + return {"status": "success", "user": user}
124
- + return {"status": "failed"}
125
- +
126
- +def reset_password(email, new_password):
127
- + if len(new_password) > 6:
128
- + query = f"UPDATE users SET password = '{new_password}' WHERE email = '{email}'"
129
- + execute_query(query)
130
- + return True
131
- + return False
132
- +
133
- +def delete_user(user_id):
134
- + execute_query(f"DELETE FROM users WHERE id = {user_id}")
135
- + return True
136
- +
137
- +def serialize_user(user_data):
138
- + import pickle
139
- + return pickle.dumps(user_data)
140
- +
141
- +def deserialize_user(data):
142
- + import pickle
143
- + return pickle.loads(data)
144
- +
145
- +def log_action(user, action):
146
- + log_entry = f"[{action}] User: {user}"
147
- + print(log_entry)
148
- +
149
- +def check_permission(user_role, required_role):
150
- + roles = ["user", "moderator", "admin"]
151
- + return roles.index(user_role) == roles.index(required_role)
152
  """,
153
  "known_issues": [
154
  {
@@ -230,98 +230,98 @@ TASKS: Dict[str, Dict[str, Any]] = {
230
  "file_name": "core/rate_limiter.py",
231
  "diff": """\
232
  --- a/core/rate_limiter.py
233
- +++ b/core/rate_limiter.py
234
  @@ -0,0 +1,82 @@
235
- +import time
236
- +import threading
237
- +from collections import defaultdict
238
- +
239
- +class RateLimiter:
240
- + def __init__(self, max_requests, window_seconds):
241
- + self.max_requests = max_requests
242
- + self.window_seconds = window_seconds
243
- + self.requests = defaultdict(list)
244
- + self.lock = threading.Lock()
245
- +
246
- + def is_allowed(self, user_id):
247
- + now = time.time()
248
- + window_start = now - self.window_seconds
249
- + with self.lock:
250
- + self.requests[user_id] = [
251
- + t for t in self.requests[user_id] if t > window_start
252
- + ]
253
- + if len(self.requests[user_id]) < self.max_requests:
254
- + self.requests[user_id].append(now)
255
- + return True
256
- + return False
257
- +
258
- +class TaskQueue:
259
- + def __init__(self, max_workers=4):
260
- + self.queue = []
261
- + self.max_workers = max_workers
262
- + self.workers = []
263
- + self.running = False
264
- +
265
- + def add_task(self, task_fn, *args):
266
- + self.queue.append((task_fn, args))
267
- +
268
- + def _worker(self):
269
- + while self.running:
270
- + if self.queue:
271
- + task_fn, args = self.queue.pop(0)
272
- + try:
273
- + task_fn(*args)
274
- + except Exception:
275
- + pass
276
- + time.sleep(0.01)
277
- +
278
- + def start(self):
279
- + self.running = True
280
- + for _ in range(self.max_workers):
281
- + t = threading.Thread(target=self._worker)
282
- + t.start()
283
- + self.workers.append(t)
284
- +
285
- + def stop(self):
286
- + self.running = False
287
- +
288
- +class RetryQueue:
289
- + def __init__(self, max_retries=3):
290
- + self.max_retries = max_retries
291
- + self.failed_tasks = {}
292
- + self.retry_counts = defaultdict(int)
293
- +
294
- + def add_failed(self, task_id, task_fn):
295
- + self.failed_tasks[task_id] = task_fn
296
- +
297
- + def retry_all(self):
298
- + for task_id, task_fn in self.failed_tasks.items():
299
- + if self.retry_counts[task_id] < self.max_retries:
300
- + try:
301
- + task_fn()
302
- + del self.failed_tasks[task_id]
303
- + except Exception:
304
- + self.retry_counts[task_id] += 1
305
- +
306
- + def get_stats(self):
307
- + return {
308
- + "pending": len(self.failed_tasks),
309
- + "total_retries": sum(self.retry_counts.values())
310
- + }
311
- +
312
- +def process_batch(items, batch_size=100):
313
- + results = []
314
- + for i in range(0, len(items), batch_size):
315
- + batch = items[i:i+batch_size]
316
- + result = process_items(batch)
317
- + results.append(result)
318
- + return results
319
- +
320
- +def merge_configs(base_config, override_config):
321
- + merged = base_config
322
- + for key, value in override_config.items():
323
- + merged[key] = value
324
- + return merged
325
  """,
326
  "known_issues": [
327
  {
@@ -384,12 +384,344 @@ TASKS: Dict[str, Dict[str, Any]] = {
384
  "required_verdict": "request_changes",
385
  "success_threshold": 0.35,
386
  },
387
- }
388
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
 
390
  def get_task(task_id: str) -> Dict[str, Any]:
391
  return TASKS.get(task_id, {})
392
 
393
-
394
  def get_all_tasks() -> List[Dict[str, Any]]:
395
  return list(TASKS.values())
 
19
  "file_name": "utils/statistics.py",
20
  "diff": """\
21
  --- a/utils/statistics.py
22
+ ++ b/utils/statistics.py
23
  @@ -0,0 +1,30 @@
24
+ def calculate_average(numbers):
25
+ total = 0
26
+ for num in numbers:
27
+ total += num
28
+ return total / len(numbers)
29
+
30
+ def calculate_max(numbers):
31
+ max_val = numbers[0]
32
+ for num in numbers:
33
+ if num > max_val:
34
+ max_val = num
35
+ return max_val
36
+
37
+ def get_percentage(value, total):
38
+ return (value / total) * 100
39
+
40
+ def find_duplicates(items):
41
+ seen = []
42
+ duplicates = []
43
+ for item in items:
44
+ if item in seen:
45
+ duplicates.append(item)
46
+ seen.append(item)
47
+ return duplicates
48
+
49
+ def safe_divide(a, b):
50
+ if b != 0:
51
+ return a / b
52
+ else:
53
+ return 0
54
  """,
55
  "known_issues": [
56
  {
 
104
  "file_name": "auth/user_manager.py",
105
  "diff": """\
106
  --- a/auth/user_manager.py
107
+ ++ b/auth/user_manager.py
108
  @@ -0,0 +1,52 @@
109
+ import hashlib
110
+
111
+ DB_PASSWORD = "admin123"
112
+ API_SECRET = "supersecret_key_do_not_share"
113
+
114
+ def get_user(user_id):
115
+ query = f"SELECT * FROM users WHERE id = {user_id}"
116
+ return execute_query(query)
117
+
118
+ def login(username, password):
119
+ hashed = hashlib.md5(password.encode()).hexdigest()
120
+ query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{hashed}'"
121
+ user = execute_query(query)
122
+ if user:
123
+ return {"status": "success", "user": user}
124
+ return {"status": "failed"}
125
+
126
+ def reset_password(email, new_password):
127
+ if len(new_password) > 6:
128
+ query = f"UPDATE users SET password = '{new_password}' WHERE email = '{email}'"
129
+ execute_query(query)
130
+ return True
131
+ return False
132
+
133
+ def delete_user(user_id):
134
+ execute_query(f"DELETE FROM users WHERE id = {user_id}")
135
+ return True
136
+
137
+ def serialize_user(user_data):
138
+ import pickle
139
+ return pickle.dumps(user_data)
140
+
141
+ def deserialize_user(data):
142
+ import pickle
143
+ return pickle.loads(data)
144
+
145
+ def log_action(user, action):
146
+ log_entry = f"[{action}] User: {user}"
147
+ print(log_entry)
148
+
149
+ def check_permission(user_role, required_role):
150
+ roles = ["user", "moderator", "admin"]
151
+ return roles.index(user_role) == roles.index(required_role)
152
  """,
153
  "known_issues": [
154
  {
 
230
  "file_name": "core/rate_limiter.py",
231
  "diff": """\
232
  --- a/core/rate_limiter.py
233
+ ++ b/core/rate_limiter.py
234
  @@ -0,0 +1,82 @@
235
+ import time
236
+ import threading
237
+ from collections import defaultdict
238
+
239
+ class RateLimiter:
240
+ def __init__(self, max_requests, window_seconds):
241
+ self.max_requests = max_requests
242
+ self.window_seconds = window_seconds
243
+ self.requests = defaultdict(list)
244
+ self.lock = threading.Lock()
245
+
246
+ def is_allowed(self, user_id):
247
+ now = time.time()
248
+ window_start = now - self.window_seconds
249
+ with self.lock:
250
+ self.requests[user_id] = [
251
+ t for t in self.requests[user_id] if t > window_start
252
+ ]
253
+ if len(self.requests[user_id]) < self.max_requests:
254
+ self.requests[user_id].append(now)
255
+ return True
256
+ return False
257
+
258
+ class TaskQueue:
259
+ def __init__(self, max_workers=4):
260
+ self.queue = []
261
+ self.max_workers = max_workers
262
+ self.workers = []
263
+ self.running = False
264
+
265
+ def add_task(self, task_fn, *args):
266
+ self.queue.append((task_fn, args))
267
+
268
+ def _worker(self):
269
+ while self.running:
270
+ if self.queue:
271
+ task_fn, args = self.queue.pop(0)
272
+ try:
273
+ task_fn(*args)
274
+ except Exception:
275
+ pass
276
+ time.sleep(0.01)
277
+
278
+ def start(self):
279
+ self.running = True
280
+ for _ in range(self.max_workers):
281
+ t = threading.Thread(target=self._worker)
282
+ t.start()
283
+ self.workers.append(t)
284
+
285
+ def stop(self):
286
+ self.running = False
287
+
288
+ class RetryQueue:
289
+ def __init__(self, max_retries=3):
290
+ self.max_retries = max_retries
291
+ self.failed_tasks = {}
292
+ self.retry_counts = defaultdict(int)
293
+
294
+ def add_failed(self, task_id, task_fn):
295
+ self.failed_tasks[task_id] = task_fn
296
+
297
+ def retry_all(self):
298
+ for task_id, task_fn in self.failed_tasks.items():
299
+ if self.retry_counts[task_id] < self.max_retries:
300
+ try:
301
+ task_fn()
302
+ del self.failed_tasks[task_id]
303
+ except Exception:
304
+ self.retry_counts[task_id] += 1
305
+
306
+ def get_stats(self):
307
+ return {
308
+ "pending": len(self.failed_tasks),
309
+ "total_retries": sum(self.retry_counts.values())
310
+ }
311
+
312
+ def process_batch(items, batch_size=100):
313
+ results = []
314
+ for i in range(0, len(items), batch_size):
315
+ batch = items[i:i+batch_size]
316
+ result = process_items(batch)
317
+ results.append(result)
318
+ return results
319
+
320
+ def merge_configs(base_config, override_config):
321
+ merged = base_config
322
+ for key, value in override_config.items():
323
+ merged[key] = value
324
+ return merged
325
  """,
326
  "known_issues": [
327
  {
 
384
  "required_verdict": "request_changes",
385
  "success_threshold": 0.35,
386
  },
 
387
 
388
+ "js-async": {
389
+ "id": "js-async",
390
+ "name": "JavaScript Async Flow Review",
391
+ "description": "Review a JavaScript module handling data fetching and state updates. Find async/await pitfalls and race conditions.",
392
+ "difficulty": "easy-medium",
393
+ "max_steps": 4,
394
+ "pr_title": "Fix data loading and caching logic",
395
+ "pr_description": "Implements an async data Fetcher with internal cache.",
396
+ "file_name": "api/fetcher.js",
397
+ "diff": """\
398
+ --- a/api/fetcher.js
399
+ +++ b/api/fetcher.js
400
+ @@ -0,0 +1,35 @@
401
+ +class DataFetcher {
402
+ + constructor() {
403
+ + this.cache = {};
404
+ + this.loading = false;
405
+ + }
406
+ +
407
+ + async fetchData(url) {
408
+ + if (this.cache[url]) return this.cache[url];
409
+ +
410
+ + this.loading = true;
411
+ + try {
412
+ + const response = fetch(url);
413
+ + const data = await response.json();
414
+ + this.cache[url] = data;
415
+ + return data;
416
+ + } catch (err) {
417
+ + console.error("Fetch failed");
418
+ + } finally {
419
+ + this.loading = false;
420
+ + }
421
+ + }
422
+ +
423
+ + updateUI(data) {
424
+ + this.fetchData('/api/user').then(user => {
425
+ + document.getElementById('user').innerText = user.name;
426
+ + });
427
+ + }
428
+ +
429
+ + async loadItems(urls) {
430
+ + urls.forEach(async (url) => {
431
+ + await this.fetchData(url);
432
+ + });
433
+ + }
434
+ +}
435
+ """,
436
+ "known_issues": [
437
+ {
438
+ "line_number": 11,
439
+ "issue_type": "bug",
440
+ "severity": "critical",
441
+ "description": "Missing await on fetch(url) — response will be a Promise, not the result",
442
+ "keywords": ["await", "fetch", "promise"],
443
+ },
444
+ {
445
+ "line_number": 16,
446
+ "issue_type": "bug",
447
+ "severity": "major",
448
+ "description": "Silent error swallowing in catch block without rethrowing or user notification",
449
+ "keywords": ["catch", "swallow", "error", "rethrow"],
450
+ },
451
+ {
452
+ "line_number": 30,
453
+ "issue_type": "performance",
454
+ "severity": "major",
455
+ "description": "forEach with async callback doesn't await the loop — use Promise.all or for...of",
456
+ "keywords": ["forEach", "async", "Promise.all", "loop"],
457
+ },
458
+ ],
459
+ "required_verdict": "request_changes",
460
+ "success_threshold": 0.5,
461
+ },
462
+
463
+ "sql-injection": {
464
+ "id": "sql-injection",
465
+ "name": "Advanced SQL Injection Hunt",
466
+ "description": "Review a Node.js database service. Identify multiple sophisticated SQL injection patterns.",
467
+ "difficulty": "medium",
468
+ "max_steps": 5,
469
+ "pr_title": "Enhance reporting queries with dynamic sorting",
470
+ "pr_description": "Adds support for custom sort orders and limits in reports.",
471
+ "file_name": "db/reports.js",
472
+ "diff": """\
473
+ --- a/db/reports.js
474
+ +++ b/db/reports.js
475
+ @@ -0,0 +1,28 @@
476
+ +const db = require('./connection');
477
+ +
478
+ +async function getReport(type, sortBy = 'created_at', limit = 10) {
479
+ + const query = `
480
+ + SELECT * FROM reports
481
+ + WHERE type = '${type}'
482
+ + ORDER BY ${sortBy}
483
+ + LIMIT ${limit}
484
+ + `;
485
+ + return db.query(query);
486
+ +}
487
+ +
488
+ +async function getUserSummary(userId) {
489
+ + const sql = "SELECT * FROM summaries WHERE user_id = " + userId;
490
+ + return db.query(sql);
491
+ +}
492
+ +
493
+ +async function searchLogs(term) {
494
+ + const sql = `SELECT * FROM logs WHERE message LIKE '%${term}%'`;
495
+ + return db.query(sql);
496
+ +}
497
+ """,
498
+ "known_issues": [
499
+ {
500
+ "line_number": 6,
501
+ "issue_type": "security",
502
+ "severity": "critical",
503
+ "description": "Classic SQL Injection in WHERE clause via template literal",
504
+ "keywords": ["sql injection", "injection", "template literal"],
505
+ },
506
+ {
507
+ "line_number": 7,
508
+ "issue_type": "security",
509
+ "severity": "critical",
510
+ "description": "SQL Injection in ORDER BY clause — cannot be parameterized, must use whitelist",
511
+ "keywords": ["order by", "injection", "whitelist"],
512
+ },
513
+ {
514
+ "line_number": 8,
515
+ "issue_type": "security",
516
+ "severity": "critical",
517
+ "description": "SQL Injection in LIMIT clause — ensure limit is a number",
518
+ "keywords": ["limit", "injection", "number"],
519
+ },
520
+ {
521
+ "line_number": 14,
522
+ "issue_type": "security",
523
+ "severity": "critical",
524
+ "description": "Simple string concatenation SQL Injection",
525
+ "keywords": ["concatenation", "injection", "sql"],
526
+ },
527
+ ],
528
+ "required_verdict": "request_changes",
529
+ "success_threshold": 0.6,
530
+ },
531
+
532
+ "react-security": {
533
+ "id": "react-security",
534
+ "name": "React Component Security",
535
+ "description": "Review a React component for XSS risks and sensitive data leaks.",
536
+ "difficulty": "medium",
537
+ "max_steps": 4,
538
+ "pr_title": "Implement UserProfile component with markdown support",
539
+ "pr_description": "Adds a profile page that renders user-provided bio content.",
540
+ "file_name": "components/UserProfile.jsx",
541
+ "diff": """\
542
+ --- a/components/UserProfile.jsx
543
+ +++ b/components/UserProfile.jsx
544
+ @@ -0,0 +1,25 @@
545
+ +import React from 'react';
546
+ +
547
+ +export const UserProfile = ({ user, authToken }) => {
548
+ + console.log("Loading profile for user:", user.id, "Token:", authToken);
549
+ +
550
+ + const renderBio = (bio) => {
551
+ + return <div dangerouslySetInnerHTML={{ __html: bio }} />;
552
+ + };
553
+ +
554
+ + return (
555
+ + <div className="profile-card">
556
+ + <h1>{user.name}</h1>
557
+ + <div className="bio">
558
+ + {renderBio(user.bio)}
559
+ + </div>
560
+ + <button onClick={() => window.location.href = `/edit?token=${authToken}`}>
561
+ + Edit Profile
562
+ + </button>
563
+ + </div>
564
+ + );
565
+ +};
566
+ """,
567
+ "known_issues": [
568
+ {
569
+ "line_number": 4,
570
+ "issue_type": "security",
571
+ "severity": "major",
572
+ "description": "Sensitive data (authToken) leaked to browser console",
573
+ "keywords": ["console", "leak", "token", "sensitive"],
574
+ },
575
+ {
576
+ "line_number": 7,
577
+ "issue_type": "security",
578
+ "severity": "critical",
579
+ "description": "XSS vulnerability via dangerouslySetInnerHTML without sanitization",
580
+ "keywords": ["dangerouslySetInnerHTML", "xss", "sanitize", "cross-site scripting"],
581
+ },
582
+ {
583
+ "line_number": 16,
584
+ "issue_type": "security",
585
+ "severity": "major",
586
+ "description": "Sensitive token leaked in URL query parameters",
587
+ "keywords": ["url", "query parameter", "token", "leak"],
588
+ },
589
+ ],
590
+ "required_verdict": "request_changes",
591
+ "success_threshold": 0.5,
592
+ },
593
+
594
+ "django-auth": {
595
+ "id": "django-auth",
596
+ "name": "Django Auth Logic Review",
597
+ "description": "Review Django middleware and auth backends for bypasses and timing attacks.",
598
+ "difficulty": "hard",
599
+ "max_steps": 7,
600
+ "pr_title": "Custom Authentication and Security Middleware",
601
+ "pr_description": "Customizes Django auth to support legacy hash formats and IP-based restrictions.",
602
+ "file_name": "auth/middleware.py",
603
+ "diff": """\
604
+ --- a/auth/middleware.py
605
+ +++ b/auth/middleware.py
606
+ @@ -0,0 +1,40 @@
607
+ +from django.shortcuts import redirect
608
+ +from django.conf import settings
609
+ +
610
+ +class IPBlockMiddleware:
611
+ + def __init__(self, get_response):
612
+ + self.get_response = get_response
613
+ +
614
+ + def __call__(self, request):
615
+ + ip = request.META.get('REMOTE_ADDR')
616
+ + if ip in settings.BLOCKED_IPS:
617
+ + return redirect('/blocked/')
618
+ + return self.get_response(request)
619
+ +
620
+ +class LegacyBackend:
621
+ + def authenticate(self, request, username=None, password=None):
622
+ + user = User.objects.get(username=username)
623
+ + if user.legacy_password == password:
624
+ + return user
625
+ + return None
626
+ +
627
+ +def validate_token(token):
628
+ + if token == settings.SUPER_SECRET_TOKEN:
629
+ + return True
630
+ + return False
631
+ +
632
+ +def secure_view(request):
633
+ + if not request.user.is_authenticated:
634
+ + return redirect('/login/')
635
+ + # Sensitive data processing here
636
+ + pass
637
+ """,
638
+ "known_issues": [
639
+ {
640
+ "line_number": 17,
641
+ "issue_type": "security",
642
+ "severity": "critical",
643
+ "description": "Plaintext password comparison for legacy accounts",
644
+ "keywords": ["plaintext", "password", "comparison"],
645
+ },
646
+ {
647
+ "line_number": 21,
648
+ "issue_type": "security",
649
+ "severity": "critical",
650
+ "description": "Timing attack vulnerability in string comparison for secret token",
651
+ "keywords": ["timing attack", "comparison", "constant time"],
652
+ },
653
+ {
654
+ "line_number": 16,
655
+ "issue_type": "bug",
656
+ "severity": "major",
657
+ "description": "User.objects.get() raises DoesNotExist if user not found; should use filter().first() or try/except",
658
+ "keywords": ["DoesNotExist", "exception", "crash"],
659
+ },
660
+ ],
661
+ "required_verdict": "request_changes",
662
+ "success_threshold": 0.4,
663
+ },
664
+
665
+ "node-race": {
666
+ "id": "node-race",
667
+ "name": "Node.js Concurrency Issues",
668
+ "description": "Identify race conditions in a Node.js singleton service responsible for inventory tracking.",
669
+ "difficulty": "hard",
670
+ "max_steps": 6,
671
+ "pr_title": "Inventory Management Singleton",
672
+ "pr_description": "Initial implementation of in-memory inventory tracking for fast access.",
673
+ "file_name": "services/inventory.js",
674
+ "diff": """\
675
+ --- a/services/inventory.js
676
+ +++ b/services/inventory.js
677
+ @@ -0,0 +1,30 @@
678
+ +let inventory = {
679
+ + 'item_1': 100,
680
+ + 'item_2': 50
681
+ +};
682
+ +
683
+ +async function purchaseItem(itemId, quantity) {
684
+ + const currentStock = inventory[itemId];
685
+ +
686
+ + if (currentStock >= quantity) {
687
+ + // Simulate DB delay
688
+ + await new Promise(resolve => setTimeout(resolve, 100));
689
+ +
690
+ + inventory[itemId] = currentStock - quantity;
691
+ + return true;
692
+ + }
693
+ + return false;
694
+ +}
695
+ +
696
+ +async function restockItem(itemId, amount) {
697
+ + inventory[itemId] += amount;
698
+ +}
699
+ +
700
+ +module.exports = { purchaseItem, restockItem };
701
+ """,
702
+ "known_issues": [
703
+ {
704
+ "line_number": 7,
705
+ "issue_type": "bug",
706
+ "severity": "critical",
707
+ "description": "Race condition: stock checked at line 7 but updated at line 12 after await. Parallel calls can lead to over-selling.",
708
+ "keywords": ["race condition", "atomic", "oversell"],
709
+ },
710
+ {
711
+ "line_number": 12,
712
+ "issue_type": "bug",
713
+ "severity": "major",
714
+ "description": "Inventory update uses stale 'currentStock' variable instead of incrementing current value.",
715
+ "keywords": ["stale", "atomic", "increment"],
716
+ },
717
+ ],
718
+ "required_verdict": "request_changes",
719
+ "success_threshold": 0.3,
720
+ },
721
+ }
722
 
723
  def get_task(task_id: str) -> Dict[str, Any]:
724
  return TASKS.get(task_id, {})
725
 
 
726
  def get_all_tasks() -> List[Dict[str, Any]]:
727
  return list(TASKS.values())
tmpclaude-46e1-cwd ADDED
@@ -0,0 +1 @@
 
 
1
+ /c/Users/psaru/OneDrive/Desktop/code-review-env
tmpclaude-5602-cwd ADDED
@@ -0,0 +1 @@
 
 
1
+ /c/Users/psaru/OneDrive/Desktop/code-review-env
tmpclaude-ee5f-cwd ADDED
@@ -0,0 +1 @@
 
 
1
+ /c/Users/psaru/OneDrive/Desktop/code-review-env