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 use shell=True with fully trusted command strings. Treat any user-controlled fragments as unsafe.
  • Generate security tokens with the secrets module rather than with random. Use secrets.token_urlsafe() for password resets, session identifiers, and API tokens.
  • Compare secrets using constant-time helpers. Use secrets.compare_digest() or hmac.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:

Python
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:

Python
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.

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.

intermediate best-practices databases

For additional information on related topics, take a look at the following resources:


By Leodanis Pozo Ramos • Updated Feb. 3, 2026