| name | security-checklist |
| description | Load when reviewing code for security issues, writing authentication or authorization logic, or scaffolding new endpoints. Provides a systematic checklist covering injection, authentication, authorization, data exposure, secrets, and input validation. Language-agnostic โ applies to Python, Go, Rust, C#.
|
Security Checklist
Work through each section systematically. State what you checked and what you found
โ even when clean. A section marked "checked, clean" is evidence of a review;
silence is not.
1. Injection (SQL, Command, Template)
The most critical category. Any instance is severity CRITICAL.
SQL injection โ look for string construction in query positions:
query = f"SELECT * FROM users WHERE email = '{email}'"
session.execute(text(query))
cursor.execute("SELECT * FROM users WHERE id = %s" % user_id)
session.execute(select(User).where(User.email == email))
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))
Command injection โ look for subprocess, os.system, exec, eval with user input:
os.system(f"convert {filename} output.png")
subprocess.run(f"grep {pattern} logs.txt", shell=True)
subprocess.run(["convert", filename, "output.png"])
Template injection โ Jinja2 render_template_string with user content is injection.
Always use render_template with static template files.
2. Authentication: Is the Endpoint Protected?
For every endpoint, ask: can an unauthenticated request reach this handler?
@router.get("/admin/users")
async def list_all_users(session: SessionDep):
...
@router.get("/admin/users")
async def list_all_users(current_user: CurrentUser, session: SessionDep):
...
Check:
3. Authorization: Does the User Own This Resource?
Authentication answers "who are you?" Authorization answers "are you allowed to do this?"
Missing authorization is severity HIGH.
@router.get("/orders/{order_id}", response_model=OrderResponse)
async def get_order(order_id: UUID, current_user: CurrentUser, service: OrderServiceDep):
return await service.get_by_id(order_id)
async def get_by_id(self, order_id: UUID, user_id: UUID) -> Order:
order = await self.repo.get_by_id(order_id)
if order is None or order.user_id != user_id:
raise NotFoundError("Order", str(order_id))
return order
Check:
4. Data Exposure: What Are You Returning?
@router.get("/users/{id}")
async def get_user(user_id: UUID) -> User:
return await session.get(User, user_id)
@router.get("/users/{id}", response_model=UserResponse)
async def get_user(user_id: UUID) -> UserResponse:
...
class UserResponse(BaseModel):
id: UUID
email: str
name: str
Check:
5. Secrets: No Hardcoded Credentials
Any hardcoded secret is severity CRITICAL โ it's already compromised if it's in git.
DATABASE_URL = "postgresql://admin:password123@prod-db.example.com/mydb"
STRIPE_KEY = "sk_live_abc123..."
JWT_SECRET = "my-secret-key"
import os
DATABASE_URL = os.environ["DATABASE_URL"]
JWT_SECRET = settings.jwt_secret
Check:
6. Input Validation: Validate at the Boundary
All external input (request bodies, query params, path params, file uploads) is untrusted.
@router.post("/upload")
async def upload(file: UploadFile):
if file.content_type == "image/jpeg":
process_image(await file.read())
import imghdr
content = await file.read()
actual_type = imghdr.what(None, h=content)
if actual_type not in ("jpeg", "png", "gif"):
raise HTTPException(status_code=400, detail="Only JPEG, PNG, GIF allowed")
Check:
7. CORS and Headers
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.allowed_origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
In production, also set:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Strict-Transport-Security (HTTPS only)
Content-Security-Policy (if serving HTML)