Security Best Practices

Python 3.14 · ✓ verified by execution on 2026-07-31

Writing functional code is only half the battle. If your code is deployed to the internet, it will be attacked. Understanding Python’s specific security footguns is critical.

Secure Randomness: secrets vs random

The standard random module uses the Mersenne Twister algorithm. It is fast, but it is deterministic. If an attacker observes a sequence of outputs, they can calculate the internal state and predict every future “random” number.

Never use random for passwords, tokens, or cryptography. Always use secrets, which hooks directly into your operating system’s cryptographically secure random number generator (e.g., /dev/urandom).

python · visualize
import secrets

# Generate a secure 32-byte hexadecimal token
api_key = secrets.token_hex(32)
print(len(api_key) == 64)
True

The Pickle RCE Vulnerability

The pickle module serializes Python objects into byte streams. However, it is fundamentally designed to execute code during deserialization. An attacker can craft a payload using the __reduce__ method to execute arbitrary system commands when you call pickle.loads().

import pickle
import os

class Exploit:
    def __reduce__(self):
        # When unpickled, this executes a shell command!
        return (os.system, ("echo You have been hacked!",))

payload = pickle.dumps(Exploit())

# VULNERABILITY TRAP:
# If this payload came from a user over the network, your server is now compromised.
# pickle.loads(payload) 

Rule of thumb: Never unpickle data you do not explicitly trust. Use json for network communications instead.

SQL Injection (SQLi)

SQL Injection happens when you construct database queries using string formatting (like f-strings) instead of parameterized queries.

import sqlite3

# VULNERABLE: String formatting allows SQL injection
user_input = "admin' OR '1'='1"
query = f"SELECT * FROM users WHERE username = '{user_input}'"

# SECURE: Parameterized queries isolate the syntax from the data
query = "SELECT * FROM users WHERE username = ?"
# cursor.execute(query, (user_input,))

Check yourself

Why must the `secrets` module be used over `random` for generating security tokens or passwords?

Reveal answer

Because `random` is based on the Mersenne Twister, a deterministic algorithm whose future outputs can be mathematically predicted if its state is observed. — The `random` module is pseudo-random and strictly designed for modeling and simulation, not security. `secrets` uses the OS's cryptographically secure random number generator (e.g., /dev/urandom).

What makes the `pickle` module inherently vulnerable to Remote Code Execution (RCE)?

Reveal answer

Its `__reduce__` method allows serialized payloads to instruct the interpreter to execute arbitrary callable objects (like `os.system`) during the unpickling process. — Pickle is a Python-specific protocol that serializes objects. Through `__reduce__`, an attacker can craft a payload that tells `pickle.loads` to execute an arbitrary system command, completely taking over the host.

How do you prevent SQL Injection when executing database queries?

Reveal answer

By using parameterized queries supplied by the DB-API driver, which safely isolates the query structure from the user-provided data. — String concatenation and sanitization are error-prone and fundamentally flawed. Parameterized queries send the SQL command structure and the data parameters separately to the database engine, neutralizing any malicious SQL syntax.

Challenges

Challenge 1 +100 XP

Import the `secrets` module. Generate a secure random integer between 0 (inclusive) and 100 (exclusive) using `secrets.randbelow()`. Print `True` if the number is valid.

python
  • Test 1 — expects "True\n"
Show solution (0 XP)
import secrets
x = secrets.randbelow(100)
print(0 <= x < 100)

Challenge 2 +100 XP

Import `secrets`. Use the `compare_digest` function to securely compare the strings `'secret_token'` and `'secret_token'`. Print the boolean result.

python
  • Test 1 — expects "True\n"
Show solution (0 XP)
import secrets
print(secrets.compare_digest('secret_token', 'secret_token'))