← Back to Blog

Python Security Best Practices: Protect Your Code and Data

Python Security Best Practices · 1176 words

---

title: "Python Security Best Practices: Protect Your Code and Data"

description: "Essential Python security practices covering input validation, secrets management, rate limiting, HTTPS, and vulnerability prevention. Code examples included."

date: "2026-07-03"

category: "python"

tags: ["python", "security", "best-practices", "secrets", "validation", "production"]

---

Security vulnerabilities in Python applications lead to data breaches, financial loss, and reputational damage. This guide covers the essential security practices every Python developer must implement.

What You'll Build

A comprehensive security toolkit with input validation, secrets management, rate limiting, secure authentication, and vulnerability scanning. You'll learn to protect your applications from the most common attack vectors.

Why Python Security Matters

Python's simplicity can mask security risks:

Full Tutorial

1. Input Validation and Sanitization


# security/validation.py
from pydantic import BaseModel, validator, EmailStr
import re
from typing import Optional

class UserInput(BaseModel):
    username: str
    email: EmailStr
    age: int
    bio: Optional[str] = None

    @validator("username")
    def validate_username(cls, v):
        if not re.match(r"^[a-zA-Z0-9_]{3,20}$", v):
            raise ValueError(
                "Username must be 3-20 chars, alphanumeric and underscore only"
            )
        return v

    @validator("age")
    def validate_age(cls, v):
        if not 0 <= v <= 150:
            raise ValueError("Invalid age")
        return v

    @validator("bio", pre=True)
    def sanitize_bio(cls, v):
        if v:
            # Remove HTML tags
            v = re.sub(r"<[^>]+>", "", v)
            # Remove JavaScript
            v = re.sub(r"javascript:", "", v, flags=re.IGNORECASE)
            # Limit length
            v = v[:500]
        return v

class SQLInputSanitizer:
    DANGEROUS_PATTERNS = [
        r"(\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION|ALTER)\b)",
        r"(--|;|/\*|\*/)",
        r"(\bOR\b\s+\b1\b\s*=\s*\b1\b)",
    ]

    @classmethod
    def is_safe(cls, input_str: str) -> bool:
        for pattern in cls.DANGEROUS_PATTERNS:
            if re.search(pattern, input_str, re.IGNORECASE):
                return False
        return True

    @classmethod
    def sanitize(cls, input_str: str) -> str:
        # Escape single quotes
        input_str = input_str.replace("'", "''")
        # Remove null bytes
        input_str = input_str.replace("\x00", "")
        return input_str

2. Secrets Management


# security/secrets.py
import os
from pathlib import Path
from cryptography.fernet import Fernet
import hashlib
import base64

class SecretsManager:
    def __init__(self, key_file: str = ".secret_key"):
        self.key_file = Path(key_file)
        self._ensure_key()

    def _ensure_key(self):
        if not self.key_file.exists():
            key = Fernet.generate_key()
            self.key_file.write_bytes(key)
            os.chmod(self.key_file, 0o600)
        self.key = Fernet(self.key_file.read_bytes())

    def encrypt(self, secret: str) -> str:
        return self.key.encrypt(secret.encode()).decode()

    def decrypt(self, encrypted: str) -> str:
        return self.key.decrypt(encrypted.encode()).decode()

class EnvironmentValidator:
    REQUIRED_VARS = [
        "DATABASE_URL",
        "SECRET_KEY",
        "API_KEY",
        "STRIPE_SECRET_KEY",
    ]

    @classmethod
    def validate(cls) -> dict:
        missing = []
        values = {}

        for var in cls.REQUIRED_VARS:
            value = os.environ.get(var)
            if not value:
                missing.append(var)
            else:
                # Mask sensitive values
                values[var] = value[:4] + "..." if len(value) > 4 else "***"

        if missing:
            raise EnvironmentError(
                f"Missing required environment variables: {', '.join(missing)}"
            )

        return values

    @classmethod
    def mask_value(cls, value: str, visible: int = 4) -> str:
        if len(value) <= visible:
            return "***"
        return value[:visible] + "*" * min(20, len(value) - visible)

3. Rate Limiting


# security/rate_limiter.py
import time
from collections import defaultdict
from dataclasses import dataclass
from typing import Optional
from functools import wraps

@dataclass
class RateLimit:
    requests: int
    window_seconds: int

class InMemoryRateLimiter:
    def __init__(self):
        self.requests: dict[str, list[float]] = defaultdict(list)

    def is_allowed(self, key: str, limit: RateLimit) -> bool:
        now = time.time()
        window_start = now - limit.window_seconds

        # Remove old requests
        self.requests[key] = [
            t for t in self.requests[key] if t > window_start
        ]

        if len(self.requests[key]) >= limit.requests:
            return False

        self.requests[key].append(now)
        return True

    def get_remaining(self, key: str, limit: RateLimit) -> int:
        now = time.time()
        window_start = now - limit.window_seconds
        recent = [t for t in self.requests[key] if t > window_start]
        return max(0, limit.requests - len(recent))

def rate_limit(requests: int, window: int = 60):
    """Decorator for rate limiting functions."""
    limiter = InMemoryRateLimiter()
    limit = RateLimit(requests, window)

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            key = kwargs.get("client_id", "default")
            if not limiter.is_allowed(key, limit):
                remaining = limiter.get_remaining(key, limit)
                raise RateLimitError(
                    f"Rate limit exceeded. Try again in {window} seconds."
                )
            return func(*args, **kwargs)
        return wrapper
    return decorator

class RateLimitError(Exception):
    pass

# Usage
@rate_limit(requests=100, window=60)
def api_endpoint(client_id: str):
    return {"status": "ok"}

4. Secure Authentication


# security/auth.py
import jwt
import bcrypt
from datetime import datetime, timedelta
from typing import Optional
from dataclasses import dataclass

@dataclass
class TokenPayload:
    user_id: str
    exp: datetime
    iat: datetime
    role: str = "user"

class SecureAuth:
    def __init__(self, secret_key: str, algorithm: str = "HS256"):
        self.secret_key = secret_key
        self.algorithm = algorithm

    def hash_password(self, password: str) -> str:
        salt = bcrypt.gensalt(rounds=12)
        return bcrypt.hashpw(password.encode(), salt).decode()

    def verify_password(self, password: str, hashed: str) -> bool:
        return bcrypt.checkpw(password.encode(), hashed.encode())

    def create_token(self, user_id: str, role: str = "user",
                     expires_hours: int = 24) -> str:
        payload = {
            "user_id": user_id,
            "role": role,
            "exp": datetime.utcnow() + timedelta(hours=expires_hours),
            "iat": datetime.utcnow()
        }
        return jwt.encode(payload, self.secret_key, algorithm=self.algorithm)

    def verify_token(self, token: str) -> Optional[TokenPayload]:
        try:
            payload = jwt.decode(
                token, self.secret_key, algorithms=[self.algorithm]
            )
            return TokenPayload(
                user_id=payload["user_id"],
                exp=datetime.fromtimestamp(payload["exp"]),
                iat=datetime.fromtimestamp(payload["iat"]),
                role=payload.get("role", "user")
            )
        except jwt.ExpiredSignatureError:
            return None
        except jwt.InvalidTokenError:
            return None

5. HTTPS and Transport Security


# security/transport.py
import ssl
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.ssl_ import create_urllib3_context

class SecureHTTPAdapter(HTTPAdapter):
    def init_poolmanager(self, *args, **kwargs):
        context = create_urllib3_context()
        context.set_ciphers("ECDHE+AESGCM")
        context.minimum_version = ssl.TLSVersion.TLSv1_2
        kwargs["ssl_context"] = context
        return super().init_poolmanager(*args, **kwargs)

def create_secure_session() -> requests.Session:
    session = requests.Session()
    session.mount("https://", SecureHTTPAdapter())
    session.headers.update({
        "User-Agent": "SecureApp/1.0",
        "Accept": "application/json",
    })
    return session

def verify_ssl(url: str) -> bool:
    try:
        response = requests.get(url, timeout=10, verify=True)
        return True
    except requests.exceptions.SSLError:
        return False

6. Vulnerability Scanning


# security/scanner.py
import subprocess
import json
from pathlib import Path

class VulnerabilityScanner:
    @staticmethod
    def scan_dependencies() -> dict:
        """Run safety check on dependencies."""
        result = subprocess.run(
            ["pip", "list", "--format=json"],
            capture_output=True, text=True
        )
        packages = json.loads(result.stdout)
        return {"packages": len(packages), "status": "scanned"}

    @staticmethod
    def scan_code(patterns: list[str] = None) -> list:
        """Scan code for common vulnerabilities."""
        if patterns is None:
            patterns = [
                r"eval\(",
                r"exec\(",
                r"__import__\(",
                r"subprocess\.call\(.*shell=True",
                r"pickle\.loads?\(",
                r"yaml\.load\(.*Loader=None",
            ]

        findings = []
        for py_file in Path(".").rglob("*.py"):
            content = py_file.read_text(errors="ignore")
            for pattern in patterns:
                if re.search(pattern, content):
                    findings.append({
                        "file": str(py_file),
                        "pattern": pattern,
                        "severity": "high"
                    })

        return findings

    @staticmethod
    def check_secrets_exposure() -> list:
        """Check for accidentally committed secrets."""
        secret_patterns = [
            r"(?:api[_-]?key|secret|password|token)\s*=\s*['\"][^'\"]+['\"]",
            r"-----BEGIN (?:RSA |DSA )?PRIVATE KEY-----",
            r"AKIA[0-9A-Z]{16}",  # AWS access key
        ]

        exposed = []
        for file in Path(".").rglob("*"):
            if file.is_file() and file.suffix in [".py", ".env", ".txt", ".yml"]:
                try:
                    content = file.read_text(errors="ignore")
                    for pattern in secret_patterns:
                        if re.search(pattern, content, re.IGNORECASE):
                            exposed.append(str(file))
                except Exception:
                    pass

        return list(set(exposed))

Security Checklist

Get the Code

Ready to use these tools? Browse our collection of tested, production-ready Python scripts:

🔗 Browse Products: [Anna's Digital Products](https://petroleum-board-hawaii-lol.trycloudflare.com)

All products include:

---

Get the Production-Ready Version

Don't want to build it yourself? We have production-ready versions of these tools at [https://petroleum-board-hawaii-lol.trycloudflare.com](https://petroleum-board-hawaii-lol.trycloudflare.com).

What you get:

[Browse the collection →](https://petroleum-board-hawaii-lol.trycloudflare.com)

🛒 Ready to deploy?

Browse 120+ Python tools with crypto payments and instant delivery.

Browse Products →