{ "summary": { "verdict": "block", "one_line": "SQL injection on user_id and an N+1 query pattern — both must be fixed before this endpoint goes anywhere near production." }, "findings": [ { "severity": "CRITICAL", "location": "line 3", "problem": "user_id is interpolated directly into the SQL string via f-string. Any caller can inject arbitrary SQL.", "why_it_matters": "An attacker hitting /orders?user_id=1' OR '1'='1 dumps every order in the database. This is the textbook SQL injection vector and it is exposed on a public endpoint.", "fix": "```python\nquery = \"SELECT * FROM orders WHERE user_id = :uid ORDER BY created_at DESC\"\nresults = db.execute(query, {\"uid\": user_id}).fetchall()\n```" }, { "severity": "CRITICAL", "location": "line 6", "problem": "Same SQL injection vector inside the loop — r['id'] comes from the DB so it is safer, but the pattern is wrong and will be copy-pasted into unsafe code later.", "why_it_matters": "Even when the immediate input is trusted, raw string interpolation in SQL is a habit that produces vulnerabilities. Use parameter binding everywhere, no exceptions.", "fix": "```python\nitems = db.execute(\n \"SELECT * FROM order_items WHERE order_id = :oid\",\n {\"oid\": r['id']}\n).fetchall()\n```" }, { "severity": "HIGH", "location": "lines 5-7", "problem": "N+1 query: one query for orders, then one query per order for items. A user with 200 orders triggers 201 round-trips.", "why_it_matters": "Latency scales linearly with order count. P99 will degrade quietly as power users accumulate history. This is the single most common cause of slow endpoints in production.", "fix": "```python\nquery = \"\"\"\n SELECT o.*, i.id as item_id, i.product_id, i.quantity\n FROM orders o\n LEFT JOIN order_items i ON i.order_id = o.id\n WHERE o.user_id = :uid\n ORDER BY o.created_at DESC\n\"\"\"\nrows = db.execute(query, {\"uid\": user_id}).fetchall()\n# then group rows by order_id in Python\n```" }, { "severity": "HIGH", "location": "line 2", "problem": "No authorization check. Any authenticated user can request another user's orders by passing a different user_id.", "why_it_matters": "This is an IDOR vulnerability. The endpoint trusts the query parameter as identity. user_id should come from the auth context, not the request.", "fix": "```python\n@app.get(\"/orders\")\nasync def get_orders(current_user: User = Depends(get_current_user), db=Depends(get_db)):\n # use current_user.id, never accept user_id from the client\n```" }, { "severity": "MEDIUM", "location": "line 2", "problem": "Function is declared async but uses a synchronous db.execute. The event loop is blocked on every query.", "why_it_matters": "Defeats the purpose of FastAPI's async runtime. Under load, throughput collapses because workers sit idle waiting for blocking I/O.", "fix": "```python\n# either use an async driver (asyncpg, databases) and await the call,\n# or declare the route as `def` (not `async def`) so FastAPI runs it in a threadpool\n```" } ], "what_is_good": [ "Endpoint shape is clear and the response structure (order + items nested) is reasonable for the client." ], "follow_up_questions": [ "Is there pagination upstream of this endpoint? Returning unbounded orders is a separate scaling risk.", "What auth middleware is in place? The fix for IDOR depends on how current_user is resolved." ] }