security
Security best practices help you reduce the risk that untrusted input, leaked secrets, or vulnerable dependencies turn into incidents.
Most security wins come from fundamental practices like the following:
- Validate any input
- Avoid injection primitives
- Handle secrets correctly
- Keep your supply chain healthy
When it comes to writing more secure Python code, you can follow the best practices below:
- Validate and normalize untrusted input early. Treat any external input from users, files, network, and environment variables as untrusted. Prefer allowlists over denylists, enforce reasonable length limits, and watch out for costly regular expressions that can lead to ReDoS attacks.
- Use parameterized queries for database access. Never build SQL queries by concatenating strings. Instead, use your database driver’s parameter binding so inputs are treated as data rather than executable SQL.
- Avoid invoking shell commands with untrusted input. Prefer
subprocess.run([...], shell=False)with argument lists. Only useshell=Truewith fully trusted command strings. Treat any user-controlled fragments as unsafe. - Generate security tokens with the
secretsmodule rather than withrandom. Usesecrets.token_urlsafe()for password resets, session identifiers, and API tokens. - Compare secrets using constant-time helpers. Use
secrets.compare_digest()orhmac.compare_digest()for comparing tokens and digests to reduce timing-attack risk. - Store passwords using a dedicated password-hashing algorithm. Don’t store passwords in plaintext, and don’t roll your own crypto. Use a library that implements a modern password hashing scheme.
- Keep secrets out of source control or version control. Store credentials and API keys outside your codebase. For example, environment-injected configuration or a secrets manager. Plan for rotation.
- Avoid insecure deserialization. Never unpickle data from untrusted or unauthenticated sources. Prefer safer formats such as JSON when processing untrusted data.
- Scan dependencies for known vulnerabilities. Automate checks in CI for your requirements and lockfiles so you catch vulnerable packages early.
- Lint for common security issues. Add a security-focused linter like Bandit alongside your normal linters to flag risky patterns during development and review.
To see one of these ideas in practice, consider the following function that looks up a stock symbol in a SQLite database:
🔴 Avoid this:
import sqlite3
def get_stock(conn, symbol):
cur = conn.cursor()
# Dangerous: building SQL with string operations invites SQL injection.
sql = f"SELECT * FROM stocks WHERE symbol = '{symbol}'"
cur.execute(sql)
return cur.fetchall()
This code works, but it’s risky because an attacker can craft input that changes the meaning of your SQL and run arbitrary code on your database.
✅ Favor this:
import sqlite3
def get_stock(conn, symbol):
cur = conn.cursor()
# Safe: bind parameters so user input stays data.
cur.execute("SELECT * FROM stocks WHERE symbol = ?", (symbol,))
return cur.fetchall()
In this improved version, the ? placeholder tells SQLite to treat the symbol value as data rather than executable SQL.
Related Resources
Tutorial
Preventing SQL Injection Attacks With Python
SQL injection attacks are one of the most common web application security risks. In this step-by-step tutorial, you'll learn how you can prevent Python SQL injection. You'll learn how to compose SQL queries with parameters, as well as how to safely execute those queries in your database.
For additional information on related topics, take a look at the following resources:
- The subprocess Module: Wrapping Programs With Python (Tutorial)
- The Python pickle Module: How to Persist Objects in Python (Tutorial)
- Python and MySQL Database: A Practical Introduction (Tutorial)
- Using the Python subprocess Module (Course)
- Serializing Objects With the Python pickle Module (Course)
- MySQL Databases and Python (Course)
By Leodanis Pozo Ramos • Updated Feb. 3, 2026