Python Security — Protecting Your Code
Security is critical for any application. Python provides tools and patterns to protect against common vulnerabilities like injection attacks, data leaks, and authentication bypass.
Learning Objectives
- Prevent SQL injection, XSS, and CSRF attacks
- Manage secrets and credentials securely
- Validate and sanitize all user input
- Implement proper password hashing and authentication
SQL Injection Prevention
Never use string formatting for SQL queries:
import sqlite3
# DANGEROUS — never do this
# cursor.execute(f"SELECT * FROM users WHERE name = '{user_input}'")
# This allows: ' OR '1'='1' --
# SAFE — use parameterized queries
cursor.execute("SELECT * FROM users WHERE name = ?", (user_input,))
# With SQLAlchemy ORM (also safe)
user = session.query(User).filter(User.name == user_input).first()
Attack example:
# Attacker submits: ' OR '1'='1' --
# SQL becomes: SELECT * FROM users WHERE name = '' OR '1'='1' --'
# This returns ALL users!
Secrets Management
Never hardcode secrets in source code:
import os
from pathlib import Path
# BAD — never commit secrets
# API_KEY = "sk-1234567890"
# GOOD — use environment variables
API_KEY = os.environ.get("API_KEY")
if not API_KEY:
raise ValueError("API_KEY environment variable not set")
# BETTER — use .env files with python-dotenv
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.environ["API_KEY"]
# BEST — use a secrets manager (AWS Secrets Manager, HashiCorp Vault)
.env file (never commit):
DATABASE_URL=postgresql://user:pass@localhost/db
API_KEY=sk-1234567890
SECRET_KEY=my-secret-key
Input Validation
Validate and sanitize all user input:
import re
from pydantic import BaseModel, validator
# Email validation
def validate_email(email: str) -> bool:
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
# Pydantic validation (recommended)
class UserInput(BaseModel):
name: str
email: str
age: int
@validator('name')
def name_must_be_safe(cls, v):
if not re.match(r'^[a-zA-Z\s]{2,50}$', v):
raise ValueError('Invalid name')
return v
@validator('age')
def age_must_be_reasonable(cls, v):
if not 0 <= v <= 150:
raise ValueError('Invalid age')
return v
Password Hashing
Always hash passwords before storing:
import bcrypt
# Hash a password
password = "my_secret_password".encode()
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
# Verify a password
if bcrypt.checkpw(password, hashed):
print("Password matches")
# Store hashed password in database
# NEVER store plain text passwords!
Why bcrypt?
- Automatically generates salt
- Configurable work factor (rounds)
- Resistant to rainbow table attacks
- Slow by design (brute-force protection)
HTTPS and TLS
Always encrypt data in transit:
import requests
# Always use HTTPS for sensitive requests
response = requests.get("https://api.example.com/data", verify=True)
# For development with self-signed certs (NOT in production)
# response = requests.get("https://localhost:8000", verify=False)
CORS Configuration
Control who can access your API:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# Allow specific origins (NOT * in production)
app.add_middleware(
CORSMiddleware,
allow_origins=["https://myapp.com"],
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["*"],
)
Rate Limiting
Prevent abuse and DoS attacks:
from fastapi import FastAPI, Request
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
@app.get("/api/data")
@limiter.limit("100/minute")
async def get_data(request: Request):
return {"data": "value"}
Security Checklist
| Check | Action |
|---|---|
| SQL Injection | Use parameterized queries, never f-strings |
| XSS | Escape output, use CSP headers |
| CSRF | Use tokens, SameSite cookies |
| Secrets | Environment variables, never in code |
| Passwords | bcrypt/argon2 with high rounds |
| HTTPS | Always in production |
| Input | Validate with Pydantic or marshmallow |
| Rate Limiting | Protect endpoints from abuse |
Common Vulnerabilities
| Vulnerability | Risk | Prevention |
|---|---|---|
| SQL Injection | Data theft/modification | Parameterized queries |
| XSS | Session hijacking | Output escaping, CSP |
| CSRF | Unauthorized actions | CSRF tokens |
| Weak passwords | Account compromise | bcrypt with rounds=12 |
| Hardcoded secrets | Credential leakage | Environment variables |
| No rate limiting | DoS attacks | Rate limit middleware |
Key Takeaways
- Always use parameterized queries — never use f-strings for SQL
- Store secrets in environment variables, never in source code
- Validate and sanitize all user input — use Pydantic for validation
- Use bcrypt for password hashing — never store plain text passwords
- Always use HTTPS in production — encrypt data in transit
- Implement rate limiting to prevent abuse and DoS attacks