Module 8: Capstone Project
Phase 1: Code Review — The TaskFlow API Codebase
Phase 1: Code Review — The TaskFlow API Codebase
Capsule overview
Here is the complete TaskFlow API codebase. These are the files you must review, with the exact code you'll examine during the project. The codebase contains between 15 and 20 problems distributed across the 5 categories you saw in the previous capsule: hallucinations, security holes, unhandled edge cases, incorrect logic, and runtime bugs.
Your job in this phase is to apply the module 4 code review checklist, compare each file against the functional requirements, and document each problem you find in the findings document format.
Don't fix anything yet. This phase is about observation and documentation. The corrections come in Capsule 04.
The Complete Codebase
File 1: requirements.txt
fastapi==0.115.0
uvicorn==0.30.0
pyjwt==2.9.0
bcrypt==4.2.0
pydantic==2.9.0
python-multipart==0.0.12
File 2: config.py
import os
class Settings:
APP_NAME: str = "TaskFlow API"
APP_VERSION: str = "1.0.0"
DEBUG: bool = True
DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite:///./taskflow.db")
DATABASE_PATH: str = "taskflow.db"
JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", "super-secret-key-taskflow-2026")
JWT_ALGORITHM: str = "HS256"
JWT_EXPIRATION_MINUTES: int = 30
BCRYPT_ROUNDS: int = 12
DEFAULT_PAGE_SIZE: int = 10
MAX_PAGE_SIZE: int = 100
settings = Settings()
File 3: database.py
import sqlite3
from contextlib import contextmanager
from config import settings
def init_db():
conn = sqlite3.connect(settings.DATABASE_PATH)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
password TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT DEFAULT '',
priority TEXT DEFAULT 'medium',
status TEXT DEFAULT 'pending',
user_id INTEGER NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id)
)
""")
conn.commit()
conn.close()
@contextmanager
def get_db():
conn = sqlite3.connect(settings.DATABASE_PATH)
conn.row_factory = sqlite3.Row
try:
yield conn
finally:
conn.close()
def execute_query(query: str, params: tuple = ()) -> list:
with get_db() as conn:
cursor = conn.cursor()
cursor.execute(query, params)
conn.commit()
return cursor.fetchall()
def execute_insert(query: str, params: tuple = ()) -> int:
with get_db() as conn:
cursor = conn.cursor()
cursor.execute(query, params)
conn.commit()
return cursor.lastrowid
File 4: models.py
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, EmailStr, field_validator
class UserCreate(BaseModel):
email: EmailStr
name: str
password: str
@field_validator("password")
@classmethod
def validate_password(cls, v):
if len(v) < 4:
raise ValueError("Password must be at least 4 characters")
return v
class UserResponse(BaseModel):
id: int
email: str
name: str
created_at: str
class LoginRequest(BaseModel):
email: str
password: str
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
class TaskCreate(BaseModel):
title: str
description: Optional[str] = ""
priority: str = "medium"
status: str = "pending"
@field_validator("title")
@classmethod
def validate_title(cls, v):
if len(v) < 1:
raise ValueError("Title cannot be empty")
return v
class TaskUpdate(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
priority: Optional[str] = None
status: Optional[str] = None
class TaskResponse(BaseModel):
id: int
title: str
description: str
priority: str
status: str
user_id: int
created_at: str
updated_at: str
class TaskListResponse(BaseModel):
tasks: list[TaskResponse]
total: int
page: int
size: int
class StatsResponse(BaseModel):
total_tasks: int
by_status: dict
by_priority: dict
completion_percentage: float
File 5: services/auth_service.py
import jwt
from datetime import datetime, timedelta
from typing import Optional
from fastapi import Header, HTTPException
from bcrypt import hashpw, gensalt, checkpw, verify_hash
from config import settings
def hash_password(password: str) -> str:
salt = gensalt(rounds=settings.BCRYPT_ROUNDS)
return hashpw(password.encode("utf-8"), salt).decode("utf-8")
def verify_password(plain_password: str, hashed_password: str) -> bool:
return checkpw(
plain_password.encode("utf-8"),
hashed_password.encode("utf-8")
)
def create_access_token(user_id: int, email: str) -> str:
expire = datetime.utcnow() + timedelta(
minutes=settings.JWT_EXPIRATION_MINUTES
)
payload = {
"sub": str(user_id),
"email": email,
"exp": expire,
"iat": datetime.utcnow()
}
return jwt.encode(
payload,
settings.JWT_SECRET_KEY,
algorithm=settings.JWT_ALGORITHM
)
def decode_token(token: str) -> dict:
try:
payload = jwt.decode(
token,
settings.JWT_SECRET_KEY,
algorithms=[settings.JWT_ALGORITHM]
)
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token has expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
async def get_current_user(authorization: str = Header(...)) -> dict:
if not authorization.startswith("Bearer "):
raise HTTPException(
status_code=401,
detail="Invalid authorization header"
)
token = authorization.split(" ")[1]
payload = decode_token(token)
return {
"user_id": int(payload["sub"]),
"email": payload["email"]
}
File 6: services/task_service.py
from datetime import datetime
from typing import Optional
from database import execute_query, execute_insert, get_db
from models import TaskCreate, TaskUpdate
def create_task(task: TaskCreate, user_id: int) -> dict:
query = """
INSERT INTO tasks (title, description, priority, status, user_id)
VALUES (?, ?, ?, ?, ?)
"""
task_id = execute_insert(
query,
(task.title, task.description, task.priority, task.status, user_id)
)
return get_task_by_id(task_id, user_id)
def get_task_by_id(task_id: int, user_id: int) -> Optional[dict]:
query = """
SELECT id, title, description, priority, status,
user_id, created_at, updated_at
FROM tasks WHERE id = ?
"""
rows = execute_query(query, (task_id,))
if not rows:
return None
task = dict(rows[0])
return task
def get_tasks(
user_id: int,
status: Optional[str] = None,
priority: Optional[str] = None,
page: int = 1,
size: int = 10,
) -> dict:
base_query = "SELECT * FROM tasks WHERE user_id = ?"
count_query = "SELECT COUNT(*) as total FROM tasks WHERE user_id = ?"
params = [user_id]
if status:
base_query += f" AND status = '{status}'"
count_query += f" AND status = '{status}'"
if priority:
base_query += f" AND priority = '{priority}'"
count_query += f" AND priority = '{priority}'"
offset = page * size
base_query += f" ORDER BY created_at DESC LIMIT {size} OFFSET {offset}"
with get_db() as conn:
cursor = conn.cursor()
cursor.execute(count_query, tuple(params))
total = cursor.fetchone()["total"]
cursor.execute(base_query, tuple(params))
rows = cursor.fetchall(as_dict=True)
tasks = [dict(row) for row in rows]
return {
"tasks": tasks,
"total": total,
"page": page,
"size": size,
}
def update_task(task_id: int, task_update: TaskUpdate, user_id: int) -> Optional[dict]:
existing = get_task_by_id(task_id, user_id)
if not existing:
return None
fields = []
values = []
if task_update.title is not None:
fields.append("title = ?")
values.append(task_update.title)
if task_update.description is not None:
fields.append("description = ?")
values.append(task_update.description)
if task_update.priority is not None:
fields.append("priority = ?")
values.append(task_update.priority)
if task_update.status is not None:
fields.append("status = ?")
values.append(task_update.status)
if not fields:
return existing
fields.append("updated_at = ?")
values.append(datetime.utcnow().isoformat())
values.append(task_id)
query = f"UPDATE tasks SET {', '.join(fields)} WHERE id = ?"
execute_query(query, tuple(values))
return get_task_by_id(task_id, user_id)
def delete_task(task_id: int, user_id: int) -> bool:
existing = get_task_by_id(task_id, user_id)
if not existing:
return False
query = "DELETE FROM tasks WHERE id = ?"
execute_query(query, (task_id,))
return True
def get_user_stats(user_id: int) -> dict:
query = "SELECT status, priority FROM tasks WHERE user_id = ?"
rows = execute_query(query, (user_id,))
total = len(rows)
by_status = {}
by_priority = {}
for row in rows:
row_dict = dict(row)
status = row_dict["status"]
priority = row_dict["priority"]
by_status[status] = by_status.get(status, 0) + 1
by_priority[priority] = by_priority.get(priority, 0) + 1
completion_percentage = (
by_status.get("completed", 0) / total * 100
)
return {
"total_tasks": total,
"by_status": by_status,
"by_priority": by_priority,
"completion_percentage": round(completion_percentage, 2),
}
File 7: routes/auth.py
from fastapi import APIRouter, HTTPException
from models import UserCreate, UserResponse, LoginRequest, TokenResponse
from services.auth_service import hash_password, verify_password, create_access_token
from database import execute_query, execute_insert
router = APIRouter(prefix="/auth", tags=["Authentication"])
@router.post("/register", response_model=UserResponse)
async def register(user: UserCreate):
existing = execute_query(
"SELECT id FROM users WHERE email = ?", (user.email,)
)
if existing:
raise HTTPException(status_code=400, detail="Email already registered")
user_id = execute_insert(
"INSERT INTO users (email, name, password) VALUES (?, ?, ?)",
(user.email, user.name, user.password),
)
created_user = execute_query(
"SELECT id, email, name, created_at FROM users WHERE id = ?",
(user_id,),
)
row = dict(created_user[0])
return UserResponse(**row)
@router.post("/login", response_model=TokenResponse)
async def login(credentials: LoginRequest):
users = execute_query(
"SELECT id, email, password FROM users WHERE email = ?",
(credentials.email,),
)
if not users:
raise HTTPException(status_code=401, detail="Invalid credentials")
user = dict(users[0])
if not verify_password(credentials.password, user["password"]):
raise HTTPException(status_code=401, detail="Invalid credentials")
token = create_access_token(user["id"], user["email"])
return TokenResponse(access_token=token)
File 8: routes/tasks.py
from typing import Optional
from fastapi import APIRouter, HTTPException, Depends, Query
from models import TaskCreate, TaskUpdate, TaskResponse, TaskListResponse
from services.auth_service import get_current_user
from services.task_service import (
create_task,
get_task_by_id,
get_tasks,
update_task,
delete_task,
)
router = APIRouter(prefix="/tasks", tags=["Tasks"])
@router.post("/", response_model=TaskResponse)
async def create_new_task(
task: TaskCreate,
current_user: dict = Depends(get_current_user),
):
created = create_task(task, current_user["user_id"])
return TaskResponse(**created)
@router.get("/", response_model=TaskListResponse)
async def list_tasks(
status: Optional[str] = Query(default=None),
priority: Optional[str] = Query(default=None),
page: int = Query(default=1, ge=0),
size: int = Query(default=10, ge=1, le=100),
current_user: dict = Depends(get_current_user),
):
result = get_tasks(
user_id=current_user["user_id"],
status=status,
priority=priority,
page=page,
size=size,
)
return TaskListResponse(**result)
@router.get("/{task_id}", response_model=TaskResponse)
async def get_task(
task_id: int,
current_user: dict = Depends(get_current_user),
):
task = get_task_by_id(task_id, current_user["user_id"])
if not task:
raise HTTPException(status_code=404, detail="Task not found")
return TaskResponse(**task)
@router.put("/{task_id}", response_model=TaskResponse)
async def update_existing_task(
task_id: int,
task_update: TaskUpdate,
current_user: dict = Depends(get_current_user),
):
updated = update_task(task_id, task_update, current_user["user_id"])
if not updated:
raise HTTPException(status_code=404, detail="Task not found")
return TaskResponse(**updated)
@router.delete("/{task_id}")
async def delete_existing_task(
task_id: int,
current_user: dict = Depends(get_current_user),
):
deleted = delete_task(task_id, current_user["user_id"])
if not deleted:
raise HTTPException(status_code=404, detail="Task not found")
return {"message": "Task deleted successfully"}
File 9: routes/users.py
from fastapi import APIRouter, HTTPException, Depends
from models import StatsResponse
from services.auth_service import get_current_user
from services.task_service import get_user_stats
from database import execute_query
router = APIRouter(prefix="/users", tags=["Users"])
@router.get("/me")
async def get_profile(current_user: dict = Depends(get_current_user)):
users = execute_query(
"SELECT id, email, name, created_at FROM users WHERE id = ?",
(current_user["user_id"],),
)
if not users:
raise HTTPException(status_code=404, detail="User not found")
return dict(users[0])
@router.get("/stats", response_model=StatsResponse)
async def get_stats(current_user: dict = Depends(get_current_user)):
stats = get_user_stats(current_user["user_id"])
return StatsResponse(**stats)
@router.get("/search")
async def search_users(query: str):
sql = f"SELECT id, email, name FROM users WHERE name LIKE '%{query}%'"
results = execute_query(sql)
return [dict(row) for row in results]
File 10: main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic_settings import BaseSettings
from config import settings
from database import init_db
from routes import auth, tasks, users
app = FastAPI(
title=settings.APP_NAME,
version=settings.APP_VERSION,
debug=settings.DEBUG,
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.on_event("startup")
async def startup():
init_db()
app.include_router(auth.router)
app.include_router(tasks.router)
app.include_router(users.router)
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"app": settings.APP_NAME,
"version": settings.APP_VERSION,
}
Code Review Instructions
Step 1: Initial reading (15 minutes)
Read all the files above in order. Don't look for bugs yet — your goal is to understand:
- How do the files connect to each other?
- What flow does a request follow from arrival to response?
- What external dependencies does the project use?
- What's the general data structure (tables, models, responses)?
Answer these questions mentally before continuing:
- How is a user authenticated? What flow does a login follow?
- How is a task created? What validations does it go through?
- How are the endpoints protected? Are all of them protected?
- Where are the passwords stored? How is a password verified?
- How does pagination work? What happens on the first page?
Step 2: Review against functional requirements (30-45 minutes)
Open the functional requirements from Capsule 01 and compare each requirement against the implementation:
Requirements checklist
## Review against Requirements
### FR-01: User Management
- [ ] FR-01.1: Can you register with email, name, and password?
- [ ] FR-01.2: Is the password stored hashed with bcrypt?
- [ ] FR-01.3: Is the email unique?
- [ ] FR-01.4: Is email format and password >= 8 characters validated?
### FR-02: Authentication
- [ ] FR-02.1: Does login return a JWT token?
- [ ] FR-02.2: Does the JWT expire in 30 minutes?
- [ ] FR-02.3: Does the JWT secret come from an environment variable with no fallback?
- [ ] FR-02.4: Do all protected endpoints require a token?
### FR-03: Task Management
- [ ] FR-03.1: Can you create a task with the correct fields?
- [ ] FR-03.2: Does pagination work correctly (10 per page)?
- [ ] FR-03.3: Can you filter by status and priority?
- [ ] FR-03.4: Can you get the detail only if it belongs to the user?
- [ ] FR-03.5: Can only the owner update?
- [ ] FR-03.6: Is delete a soft delete (change status to "deleted")?
- [ ] FR-03.7: A user can't view/edit/delete another's tasks?
### FR-04: Statistics
- [ ] FR-04.1: Does it return total, by status, by priority?
- [ ] FR-04.2: Are they calculated only on active tasks (not deleted)?
- [ ] FR-04.3: Does it include the percentage of completed out of the active total?
### FR-05: Validations
- [ ] FR-05.1: Title between 3 and 100 characters?
- [ ] FR-05.2: Priority only low/medium/high?
- [ ] FR-05.3: Status only pending/in_progress/completed?
- [ ] FR-05.4: Page >= 1 and size between 1 and 100?
- [ ] FR-05.5: Does a nonexistent ID return 404?
- [ ] FR-05.6: Does another user's task return 403, not 404?
### FR-06: Non-Functional
- [ ] FR-06.1: Sensitive config from environment variables?
- [ ] FR-06.2: Do SQL queries use parameters, not concatenation?
- [ ] FR-06.3: Do internal errors avoid exposing stack traces?
- [ ] FR-06.4: Consistent responses in JSON format?
For each requirement that fails, document it immediately in your findings document.
Step 3: Module 4 security checklist (15-20 minutes)
Apply the first 5 items of the Module 4 security checklist to each file:
Security items to verify
Item 1: No hardcoded secrets
Look in each file for:
- Strings that look like API keys, passwords, or tokens
os.getenv()with a fallback that contains real values- Connection strings with credentials
Item 2: No SQL injection
Look in each file that interacts with the database for:
- f-strings inside SQL queries
- String concatenation in queries
- User variables inserted directly into the SQL
Item 3: Authentication on all sensitive endpoints
Review each endpoint:
- Does it have
Depends(get_current_user)or equivalent? - Are there endpoints that access data but don't verify identity?
Item 4: Passwords stored correctly
Verify the complete password flow:
- Is it hashed before saving to the database?
- Is it compared correctly during login?
- At any point, is it stored or transmitted in plain text?
Item 5: Permissions verified correctly
For each operation on a resource:
- Is it verified that the current user is the owner?
- What happens if a user tries to access another's resource?
Step 4: Search for hallucinations (10-15 minutes)
Apply the Module 3 techniques:
For each import in the codebase:
1. Does the package exist in requirements.txt?
2. Does the imported module/class exist in that package?
3. Does the version in requirements.txt have that API?
For each external library function:
1. Are the parameters being passed correct?
2. Is the return type the expected one?
3. Does the function exist in the installed version?
Pay special attention to:
- Imports that aren't used in the file
- Imports of modules or functions that don't exist in the package
- Use of APIs with incorrect signatures
Step 5: Search for error patterns (10-15 minutes)
From Module 5, actively look for:
Unhandled edge cases:
- What happens when a list is empty?
- What happens with None/null values where they aren't expected?
- Are the pagination limits correct?
Incorrect logic:
- Do the filters include/exclude the right thing?
- Do the calculations produce correct results?
- Are the validations sufficient?
Runtime bugs:
- Are there operations that would fail with certain data types?
- Are there divisions that could be by zero?
- Are there dictionary accesses that could fail?
Template to Document Findings
Use this template to record each problem you find. Create a markdown file and add a row for each finding:
# TaskFlow API — Findings Document
## Reviewer Information
- **Name:** [Your name]
- **Date:** [Review date]
- **Time spent on code review:** [Time]
## Executive Summary
[2-3 sentences describing the overall state of the codebase.
Example: "The codebase implements the basic functionality but contains
multiple critical security vulnerabilities and several business
logic problems that don't meet the functional requirements."]
## Findings
| # | Severity | Category | File | Line(s) | Description | Requirement Violated | Impact |
|---|-----------|-----------|---------|----------|-------------|-------------------|---------|
| 1 | | | | | | | |
| 2 | | | | | | | |
| 3 | | | | | | | |
## Findings Statistics
| Severity | Count |
|-----------|----------|
| Critical | |
| High | |
| Medium | |
| Low | |
| **Total** | |
## Reviewer Notes
[General observations about the codebase, patterns you noticed,
areas that require special attention]
How to fill in each column
Severity:
- Critical: Exploitable in production, can cause data loss or unauthorized access
- High: Incorrect functionality that affects users
- Medium: Edge case or robustness problem
- Low: Quality or maintainability problem
Category:
Security— Security vulnerabilityHallucination— Import/API/function that doesn't existLogic— Incorrect business logicEdge Case— Unhandled input that causes an errorRuntime— Error that only appears when running
File: Name of the file where you found the problem.
Line(s): Approximate line number(s) in the capsule's code.
Description: What the problem is in one clear sentence.
Requirement Violated: The ID of the functional requirement that isn't met (e.g., FR-01.2, FR-06.2).
Impact: What consequence the problem has in production.
Review Prioritization Guide
Don't review the files randomly. Follow this priority sequence based on the Module 4 pyramid:
Priority 1: Security (review first)
| File | What to look for |
|---|---|
config.py | Hardcoded secrets, insecure configuration |
services/auth_service.py | Password handling, token generation |
routes/auth.py | Registration and login flow |
database.py | SQL injection in queries |
Priority 2: Business logic
| File | What to look for |
|---|---|
services/task_service.py | CRUD logic, filters, statistics |
routes/tasks.py | Validations, permissions, correct responses |
routes/users.py | Statistics, search |
Priority 3: Robustness and edge cases
| File | What to look for |
|---|---|
models.py | Sufficient validations, correct constraints |
routes/tasks.py | Pagination, filters with unexpected values |
services/task_service.py | Division by zero, empty lists |
Priority 4: Quality and hallucinations
| File | What to look for |
|---|---|
main.py | Correct imports, coherent configuration |
| All the files | Imports that don't exist, APIs with incorrect parameters |
Tips for the Code Review
Tip 1: Read the code as if it were production code
Don't read thinking "this is an exercise." Read thinking "this goes to production tomorrow." The problems you let slip through, your users will find.
Tip 2: Follow the data flow
For each endpoint, follow the complete path:
Request → Route → Service → Database → Response
The problems usually hide in the transitions between layers. A validation in the model is useless if the route doesn't use it. A correct query is useless if the service misinterprets the results.
Tip 3: Ask "What if...?"
For each function, ask yourself these questions:
- What if the input is empty?
- What if the input is None?
- What if the user has no data?
- What if the ID doesn't exist?
- What if an attacker manipulates the input?
Tip 4: Don't stop at the first problem
It's tempting to find a problem and start fixing it. Resist. Complete the full review first. The corrections come after.
Tip 5: Use Claude Code strategically
You can use Claude Code during the review to:
Correct use:
"Does the BaseSettings class exist in pydantic_settings?
Is it in the pydantic-settings package or in pydantic?"
"Are the jwt.encode parameters in PyJWT 2.9 correct?"
"Does sqlite3.Row support dict() directly?"
Incorrect use:
"Find all the bugs in this code"
"Does this code have security problems?"
The difference: specific and verifiable questions vs open questions that delegate your work.
Tip 6: Document while you review
Don't trust your memory. Every time you find something suspicious, add it immediately to the findings document even if you're not 100% sure. It's easier to discard a false finding than to remember one you didn't note down.
Common Mistakes in This Phase
Mistake 1: Only looking for one type of bug
Developers with a security background find all the security holes but ignore the incorrect logic. Those who are strong in business logic find the poorly implemented filters but overlook the SQL injection. Force a review for each category.
Mistake 2: Assuming the imports are fine
"If the code was written, the imports must exist." Not necessarily. AI generates imports of packages that exist but functions that don't. Or of packages that are named almost the same. Verify each external import.
Mistake 3: Not reading the requirements carefully
Some problems are only visible if you know the requirement. "The password must be at least 8 characters" — if the code validates 4, it's a bug you can only find by reading both documents.
Mistake 4: Confusing "works" with "correct"
An endpoint returning a 200 doesn't mean it's correct. Does it return the correct data? For all users? With all inputs? "Works" is a very low bar. "Correct according to the requirements" is what you're after.
Mistake 5: Not documenting in enough detail
"There's a problem in task_service.py" isn't a useful finding. "In task_service.py line 45, the get_tasks function uses f-strings to insert the status parameter into the SQL query, which is vulnerable to SQL injection (FR-06.2)" is.
Code Review Checkpoint
Before moving to Phase 2 (Debugging), verify:
- ✅ You read all the files of the complete codebase
- ✅ You compared each functional requirement against the implementation
- ✅ You applied the security checklist to the relevant files
- ✅ You looked for hallucinations in all the imports and external APIs
- ✅ You looked for unhandled edge cases in each function
- ✅ You have a findings document with at least 10 problems documented
- ✅ Each finding has: severity, category, file, line, description, impact
- ✅ You haven't started fixing anything yet
If you have fewer than 10 findings, do another pass. The codebase has between 15 and 20 problems. If you found 7, you're missing at least 8 more. Review the categories where you have fewer findings — you're probably missing something in that direction.
Verification Map by File
To help you be systematic, use this map that indicates what to verify in each file. It's not exhaustive — it's a starting point:
config.py
- Do the sensitive values have a hardcoded fallback?
- Is the debug configuration appropriate for production?
database.py
- Do all the queries use parameters?
- Are the connections closed correctly?
models.py
- Do the validations match the requirements?
- Are there missing validations?
- Are the types correct?
services/auth_service.py
- Is the password hashed before storing?
- Is the password verification correct?
- Is the token generated and decoded correctly?
- Are the imports correct and do they exist?
services/task_service.py
- Do the queries use parameters?
- Do the filters work correctly?
- Does the pagination calculate the offset correctly?
- Do the statistics exclude deleted tasks?
- Is division by zero possible?
- Is task ownership verified?
routes/auth.py
- Is the password hashed before inserting into the DB?
- Is the password verification in login correct?
routes/tasks.py
- Do all the endpoints verify authentication?
- Are the pagination validations correct?
- Is task ownership verified in all the endpoints?
- Does the delete implement soft delete?
routes/users.py
- Do all the endpoints require authentication?
- Is there SQL injection in any query?
main.py
- Are the imports correct and do they exist?
- Is the CORS configuration appropriate?
Next capsule: Debugging Phase — How to set up and run the codebase locally, and debug the runtime bugs found during the review.
Debugging & Code Review with Claude Code — Module 8, Capsule 02 Claude Code Agentic Development Path — Guide #6 of 11