1. Core Security Foundations#
- Treat all input as untrusted. Validate strictly (whitelists over blacklists), normalize before checks, and enforce types and sizes.
- Use framework security features instead of writing your own.
- Least privilege: minimize DB, filesystem, and network permissions.
- Secrets management: use environment variables or secret stores, never hardcode.
- Dependency hygiene: pin and audit dependencies with
pip-auditorSafety. - Secure HTTP headers: add HSTS, X-Frame-Options, CSP, and others.
- Logging & monitoring: log relevant events, but never credentials.
- Testing: integrate Bandit and Semgrep in CI.
2. Preventing SQL Injection (SQLi)#
Principle: Never build queries using string concatenation.
✅ Safe Examples#
psycopg2:
cur.execute("SELECT id, email FROM users WHERE id = %s", (user_id,))
sqlite3:
cur.execute("SELECT * FROM products WHERE category = ?", (category,))
SQLAlchemy ORM:
user = session.query(User).filter(User.email == email).one_or_none()
❌ Avoid#
- f-strings,
%formatting, or.format()in SQL statements. - Unvalidated dynamic identifiers (table names, order-by fields).
Use a whitelist map for dynamic values:
ORDER_BY_MAP = {"name": User.name, "created": User.created_at}
order_col = ORDER_BY_MAP.get(request.args.get("order_by"), User.created_at)
q = session.query(User).order_by(order_col)
Extra Hardening#
- Use read-only or least privilege DB users.
- Disable multiple statements per execute.
- Apply database constraints (
CHECK,NOT NULL, etc.).
3. Preventing Cross-Site Scripting (XSS)#
Principle: Encode on output, not input.
Template Autoescaping#
- Django and Jinja2 autoescape by default. Keep it that way.
- Avoid
mark_safeunless sanitized with a library likebleach.
Example:
import bleach
clean_html = bleach.clean(user_html, tags=['b','i','a','ul','li'], attributes={'a': ['href','title']})
Content Security Policy (CSP)#
Apply a strict CSP to block inline scripts:
default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:;
connect-src 'self'; base-uri 'self'; frame-ancestors 'none';
Use Flask-Talisman or similar middleware to set headers automatically.
Encoding in Scripts#
<script>
const data = {{ payload | tojson }};
</script>
4. Preventing Server-Side Request Forgery (SSRF)#
Principle: Don’t allow user-controlled URLs to trigger server requests.
Core Defenses#
- Allowlist destinations where possible.
- Enforce
httpsonly, rejectfile://,gopher://,data:, etc. - Validate host resolution:
- Reject internal/private IPs (
127.0.0.0/8,10.0.0.0/8, etc.). - Block cloud metadata endpoints (
169.254.169.254). - Prevent redirects to unvalidated hosts.
- Reject internal/private IPs (
Example Safe Fetch#
from urllib.parse import urlparse
import ipaddress, socket, requests
BLOCKED_NETS = [
ipaddress.ip_network("127.0.0.0/8"),
ipaddress.ip_network("10.0.0.0/8"),
ipaddress.ip_network("172.16.0.0/12"),
ipaddress.ip_network("192.168.0.0/16"),
ipaddress.ip_network("169.254.0.0/16"),
ipaddress.ip_network("::1/128"),
ipaddress.ip_network("fc00::/7"),
ipaddress.ip_network("fe80::/10"),
]
def is_safe_remote(url: str, allow_hosts=None):
u = urlparse(url)
if u.scheme != "https":
return False, "Only https allowed"
host = u.hostname
if not host:
return False, "Missing host"
if allow_hosts and host not in allow_hosts:
return False, "Not in allowlist"
infos = socket.getaddrinfo(host, None)
for fam, *_ in infos:
ip = ipaddress.ip_address(infos[0][4][0])
if any(ip in net for net in BLOCKED_NETS):
return False, f"Blocked IP: {ip}"
return True, host
def safe_fetch(url: str):
ok, msg = is_safe_remote(url)
if not ok:
raise ValueError(msg)
return requests.get(url, allow_redirects=False, timeout=5, stream=True)
Additional SSRF Tips#
- Never expose a raw “fetch this URL” API.
- Limit response size and validate MIME type.
- Block IMDS endpoints at network layer (IMDSv2 for AWS).
5. Python-Specific Secure Patterns#
- Deserialization: use
yaml.safe_load; never unpickle untrusted data. - Command execution: use
subprocess.run([...], shell=False). - File uploads: generate safe filenames, store outside web root, restrict types.
- Authentication: use secure cookies with
HttpOnly,Secure, andSameSite=Strict. - Crypto: use
cryptographyorpasslib(bcrypt/Argon2).
6. Final Security Checklist#
- No unparameterized SQL.
- Autoescape enabled for templates.
- SSRF-safe outbound requests.
- CSRF protection on all state-changing requests.
- Strong CSP and headers.
- Dependencies pinned and scanned.
- Secrets never logged or hardcoded.
- Least privilege enforced across DB, filesystem, and network.
In summary:
Combine strict input validation, contextual output encoding, and robust network egress control with a least-privilege architecture. Use the framework’s built-in defenses, modern security headers, and scanning tools in CI to keep Python applications resilient against SSRF, SQLi, and XSS.