Security Best Practices
Write secure Python applications — input validation, secrets management, SQL injection prevention, dependency scanning, and OWASP guidelines.
Security is not optional. These practices protect your applications and users from common vulnerabilities.
Never Hardcode Secrets
# BAD
API_KEY = "sk-abc123secret"
# GOOD — load from environment
import os
API_KEY = os.environ["API_KEY"]
Use .env files locally (never commit them) and secret managers in production (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault).
from dotenv import load_dotenv
load_dotenv()
database_url = os.environ["DATABASE_URL"]
Input Validation
Never trust user input. Validate and sanitize everything:
from pydantic import BaseModel, EmailStr, Field
class CreateUser(BaseModel):
username: str = Field(min_length=3, max_length=50, pattern=r"^[a-zA-Z0-9_]+$")
email: EmailStr
age: int = Field(ge=0, le=150)
# Raises ValidationError on bad input
user = CreateUser(username="alice", email="[email protected]", age=30)
Prevent SQL Injection
Always use parameterized queries:
import sqlite3
# BAD — vulnerable to SQL injection
username = request.form["username"]
cursor.execute(f"SELECT * FROM users WHERE name = '{username}'")
# GOOD — parameterized query
cursor.execute("SELECT * FROM users WHERE name = ?", (username,))
With ORMs (Django ORM, SQLAlchemy), use their query APIs instead of raw SQL.
Avoid Command Injection
import subprocess
# BAD
subprocess.run(f"convert {user_filename} output.png", shell=True)
# GOOD
subprocess.run(["convert", user_filename, "output.png"], check=True)
Never pass unsanitized user input to shell=True.
Safe Deserialization
import json
# SAFE
data = json.loads(user_input)
# DANGEROUS — never deserialize untrusted pickle/yaml
import pickle
pickle.loads(user_input) # Can execute arbitrary code!
Use json for data exchange. If you must use YAML, use yaml.safe_load().
Dependency Security
Scan dependencies for known vulnerabilities:
pip install pip-audit
pip-audit
# Or with safety
pip install safety
safety check
Pin dependencies and update regularly. Automate scanning in CI.
Authentication & Authorization
- Hash passwords with bcrypt or argon2 — never store plaintext
- Use HTTPS everywhere in production
- Implement rate limiting on login and API endpoints
- Apply least privilege — users should only access what they need
from passlib.hash import bcrypt
hashed = bcrypt.hash("user_password")
bcrypt.verify("user_password", hashed) # True
Security Headers (Web Apps)
# Flask example
@app.after_request
def set_security_headers(response):
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["Strict-Transport-Security"] = "max-age=31536000"
return response
Security Checklist
- No secrets in source code or git history
- All user input validated
- Parameterized database queries
- Dependencies scanned for CVEs
- HTTPS enforced in production
- Authentication and authorization on all endpoints
- Error messages don’t leak internal details
- Logging excludes sensitive data (passwords, tokens)
Security is a continuous practice, not a one-time task.
OWASP Top 10 — Python Developer View
| Risk | Python Context | Mitigation |
|---|---|---|
| Broken Access Control | Missing permission checks on views | Decorators, RBAC, test authorization |
| Cryptographic Failures | Weak hashing, hardcoded keys | bcrypt/argon2, env vars, TLS |
| Injection | SQL, shell, template injection | Parameterized queries, no shell=True |
| Insecure Design | No threat modeling | Security requirements in design phase |
| Security Misconfiguration | DEBUG=True in production |
Environment-based settings |
| Vulnerable Components | Outdated dependencies | pip-audit, Dependabot, pin versions |
| Auth Failures | Weak session handling | Secure cookies, JWT expiry, MFA |
| Data Integrity Failures | Unsigned packages | Verify hashes, use lock files |
| Logging Failures | No audit trail | Log auth events, monitor anomalies |
| SSRF | Unvalidated URL fetching | Allowlist domains, block internal IPs |
Web-Specific Threats
Cross-Site Scripting (XSS)
Escape user content in HTML templates:
# Django — auto-escapes by default in templates
# {{ user_input }} is safe
# Jinja2 / manual HTML
from markupsafe import escape
safe_html = f"<p>{escape(user_comment)}</p>"
Never use |safe or Markup() on untrusted input.
Cross-Site Request Forgery (CSRF)
Ensure state-changing requests require a CSRF token:
# Django — enabled by default for POST forms
# {% csrf_token %} in templates
# Flask
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
CORS Misconfiguration
Restrict API origins in production:
# FastAPI
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://yourapp.com"], # not "*"
allow_credentials=True,
allow_methods=["GET", "POST"],
)
JWT Security
# Set short expiry
token = jwt.encode(
{"sub": user_id, "exp": datetime.utcnow() + timedelta(hours=1)},
SECRET_KEY,
algorithm="HS256",
)
# Validate on every request — never trust client-side claims alone
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
- Use strong
SECRET_KEYfrom environment - Prefer short-lived access tokens + refresh tokens
- Never store JWTs in localStorage if XSS is a concern — use httpOnly cookies
Logging Without Leaking Secrets
# BAD
logger.info(f"Connecting with password={password}")
# GOOD
logger.info("Database connection established")
logger.debug("Auth attempt for user_id=%s", user_id) # no passwords/tokens
Filter sensitive fields before logging request bodies in APIs.
Related
- Interview: Web & Backend — auth and API security questions
- DevOps & CI/CD — secure deployment pipelines
- FastAPI Auth — production authentication patterns