The comprehensive WSGI web application library providing essential utilities and components for building Python web applications.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Security functions for password hashing, secure filename handling, and safe filesystem operations. These utilities provide essential security primitives for web applications including cryptographically secure password storage and path traversal protection.
Functions for securely hashing and verifying passwords using modern cryptographic methods.
def generate_password_hash(password, method="scrypt", salt_length=16):
"""
Securely hash a password for storage using strong key derivation functions.
Parameters:
- password: Plaintext password to hash
- method: Key derivation method and parameters
- "scrypt" (default): scrypt:n:r:p format, default scrypt:32768:8:1
- "pbkdf2": pbkdf2:hash_method:iterations format, default pbkdf2:sha256:1000000
- salt_length: Number of characters for random salt
Returns:
Hashed password string in format: method$salt$hash
Examples:
- generate_password_hash("secret") # Uses scrypt with default params
- generate_password_hash("secret", "pbkdf2:sha256:1200000")
- generate_password_hash("secret", "scrypt:65536:16:2") # Custom scrypt params
"""
def check_password_hash(pwhash, password):
"""
Verify a password against a stored hash.
Parameters:
- pwhash: Previously generated hash from generate_password_hash()
- password: Plaintext password to verify
Returns:
True if password matches hash, False otherwise
Note: Uses constant-time comparison to prevent timing attacks.
"""
def gen_salt(length):
"""
Generate a cryptographically secure random salt.
Parameters:
- length: Length of salt in characters
Returns:
Random salt string using characters from SALT_CHARS
Raises:
ValueError: If length is less than 1
"""
# Constants
SALT_CHARS: str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
DEFAULT_PBKDF2_ITERATIONS: int = 1_000_000Functions for safe filesystem operations to prevent path traversal and other file-related attacks.
def secure_filename(filename):
"""
Make a filename safe for filesystem storage by removing dangerous characters.
Parameters:
- filename: Original filename (may contain unsafe characters)
Returns:
ASCII-only filename safe for filesystem storage
Note: May return empty string for extremely unsafe filenames.
It's your responsibility to handle empty results and ensure uniqueness.
Examples:
- secure_filename("My cool movie.mov") → "My_cool_movie.mov"
- secure_filename("../../../etc/passwd") → "etc_passwd"
- secure_filename("file with unicode.txt") → "file_with_unicode.txt"
"""
def safe_join(directory, *pathnames):
"""
Safely join path components to prevent directory traversal attacks.
Parameters:
- directory: Trusted base directory path
- *pathnames: Untrusted path components to join
Returns:
Safe combined path or None if any component is unsafe
Security checks:
- Prevents ".." directory traversal
- Prevents absolute paths in pathnames
- Prevents OS-specific path separators in unsafe contexts
- Normalizes paths to prevent bypass attempts
Examples:
- safe_join("/var/www", "uploads", "file.txt") → "/var/www/uploads/file.txt"
- safe_join("/var/www", "../etc/passwd") → None (unsafe)
- safe_join("/var/www", "/etc/passwd") → None (absolute path)
"""from werkzeug.security import generate_password_hash, check_password_hash
class User:
def __init__(self, username, password):
self.username = username
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
# User registration
def register_user(username, password):
# Validate password strength first (your code)
if len(password) < 8:
raise ValueError("Password must be at least 8 characters")
# Hash password securely
user = User(username, password)
# Store user in database (your code)
save_user_to_database(user)
return user
# User login
def authenticate_user(username, password):
user = get_user_from_database(username)
if user and user.check_password(password):
return user
return None
# Example usage
user = register_user("john", "mySecretPassword123")
print(user.password_hash) # scrypt:32768:8:1$randomSalt$hashedPassword
# Later authentication
if authenticate_user("john", "mySecretPassword123"):
print("Login successful")
else:
print("Invalid credentials")from werkzeug.security import generate_password_hash, check_password_hash
# Using different hashing methods
def hash_with_different_methods(password):
# Default scrypt (recommended for new applications)
scrypt_hash = generate_password_hash(password)
print(f"Scrypt: {scrypt_hash}")
# PBKDF2 with SHA-256 (for compatibility/regulations requiring PBKDF2)
pbkdf2_hash = generate_password_hash(password, method="pbkdf2")
print(f"PBKDF2: {pbkdf2_hash}")
# Custom PBKDF2 iterations
strong_pbkdf2 = generate_password_hash(password, method="pbkdf2:sha256:1500000")
print(f"Strong PBKDF2: {strong_pbkdf2}")
# Custom scrypt parameters (n=65536, r=16, p=2)
strong_scrypt = generate_password_hash(password, method="scrypt:65536:16:2")
print(f"Strong Scrypt: {strong_scrypt}")
# Longer salt
long_salt_hash = generate_password_hash(password, salt_length=32)
print(f"Long salt: {long_salt_hash}")
return [scrypt_hash, pbkdf2_hash, strong_pbkdf2, strong_scrypt, long_salt_hash]
# Verify all hashes work
password = "testPassword123"
hashes = hash_with_different_methods(password)
for i, hash_value in enumerate(hashes):
if check_password_hash(hash_value, password):
print(f"Hash {i+1} verified successfully")
else:
print(f"Hash {i+1} verification failed")from werkzeug.security import generate_password_hash, check_password_hash
class UserAccount:
def __init__(self, username, password_hash):
self.username = username
self.password_hash = password_hash
def verify_and_upgrade_password(self, password):
"""Verify password and upgrade hash if using old method."""
if not check_password_hash(self.password_hash, password):
return False
# Check if using old/weak hashing method
if self.needs_password_upgrade():
# Rehash with current strong defaults
self.password_hash = generate_password_hash(password)
# Save updated hash to database
self.save_to_database()
print(f"Password hash upgraded for user {self.username}")
return True
def needs_password_upgrade(self):
"""Check if password hash should be upgraded."""
# Upgrade if using old PBKDF2 with low iterations
if self.password_hash.startswith('pbkdf2:'):
parts = self.password_hash.split('$')[0].split(':')
if len(parts) >= 3:
iterations = int(parts[2])
if iterations < 1_000_000: # Below current minimum
return True
# Upgrade if using deprecated methods
if self.password_hash.startswith('md5') or self.password_hash.startswith('sha1'):
return True
return False
def save_to_database(self):
"""Save user to database (implement your database logic)."""
pass
# Example migration during login
def login_with_migration(username, password):
user = get_user_from_database(username)
if user and user.verify_and_upgrade_password(password):
return user
return Nonefrom werkzeug.security import secure_filename, safe_join
from werkzeug.utils import secure_filename # Alternative import
import os
class FileUploadHandler:
def __init__(self, upload_directory):
self.upload_directory = os.path.abspath(upload_directory)
os.makedirs(self.upload_directory, exist_ok=True)
def save_uploaded_file(self, file_storage, subdirectory=None):
"""Safely save an uploaded file."""
if not file_storage.filename:
raise ValueError("No filename provided")
# Secure the filename
filename = secure_filename(file_storage.filename)
if not filename:
# Generate fallback filename if original was completely unsafe
import uuid
ext = os.path.splitext(file_storage.filename)[1]
filename = f"{uuid.uuid4().hex}{ext}"
filename = secure_filename(filename) # Double-check
# Build safe path
if subdirectory:
# Use safe_join to prevent directory traversal
target_dir = safe_join(self.upload_directory, subdirectory)
if target_dir is None:
raise ValueError(f"Unsafe subdirectory: {subdirectory}")
else:
target_dir = self.upload_directory
# Ensure target directory exists
os.makedirs(target_dir, exist_ok=True)
# Create final safe path
file_path = safe_join(target_dir, filename)
if file_path is None:
raise ValueError(f"Unsafe filename: {filename}")
# Handle filename conflicts
file_path = self.get_unique_filename(file_path)
# Save file
file_storage.save(file_path)
# Return relative path from upload directory
return os.path.relpath(file_path, self.upload_directory)
def get_unique_filename(self, file_path):
"""Generate unique filename if file exists."""
if not os.path.exists(file_path):
return file_path
base, ext = os.path.splitext(file_path)
counter = 1
while True:
new_path = f"{base}_{counter}{ext}"
if not os.path.exists(new_path):
return new_path
counter += 1
def serve_file(self, requested_path):
"""Safely serve a file from the upload directory."""
# Use safe_join to prevent directory traversal
file_path = safe_join(self.upload_directory, requested_path)
if file_path is None:
raise ValueError("Unsafe file path")
if not os.path.exists(file_path):
raise FileNotFoundError("File not found")
# Additional security: ensure file is within upload directory
if not file_path.startswith(self.upload_directory):
raise ValueError("File outside upload directory")
return file_path
# Example usage
upload_handler = FileUploadHandler('/var/uploads')
# Test secure filename
def test_secure_filenames():
test_cases = [
"normal_file.txt",
"file with spaces.pdf",
"../../../etc/passwd",
"con.txt", # Windows reserved name
"file<>with|invalid:chars.doc",
"résumé.pdf", # Unicode characters
".hidden_file",
"file.tar.gz",
]
for original in test_cases:
secured = secure_filename(original)
print(f"'{original}' → '{secured}'")
# Test safe path joining
def test_safe_joining():
base_dir = "/var/uploads"
test_cases = [
["user1", "document.pdf"], # Safe
["user1", "../../../etc/passwd"], # Unsafe - traversal
["/etc/passwd"], # Unsafe - absolute
["user1", "subdir", "file.txt"], # Safe - nested
["user1", ""], # Safe - empty
["user1", ".."], # Unsafe - parent
]
for pathnames in test_cases:
result = safe_join(base_dir, *pathnames)
safe_status = "SAFE" if result else "UNSAFE"
print(f"{pathnames} → {result} ({safe_status})")
if __name__ == '__main__':
test_secure_filenames()
print()
test_safe_joining()from werkzeug.wrappers import Request, Response
from werkzeug.security import generate_password_hash, check_password_hash, safe_join
from werkzeug.utils import secure_filename
from werkzeug.exceptions import BadRequest, Forbidden
import os
import json
class SecureWebApp:
def __init__(self):
self.users = {} # In production, use proper database
self.upload_dir = './secure_uploads'
os.makedirs(self.upload_dir, exist_ok=True)
def register(self, request):
"""Secure user registration."""
if not request.is_json:
raise BadRequest("Content-Type must be application/json")
data = request.get_json()
username = data.get('username', '').strip()
password = data.get('password', '')
# Basic validation
if len(username) < 3:
return Response('{"error": "Username too short"}', status=400)
if len(password) < 8:
return Response('{"error": "Password too weak"}', status=400)
if username in self.users:
return Response('{"error": "Username already exists"}', status=409)
# Secure password hashing
password_hash = generate_password_hash(password)
# Store user (in production, use proper database)
self.users[username] = {
'password_hash': password_hash,
'files': []
}
return Response('{"message": "Registration successful"}',
mimetype='application/json')
def login(self, request):
"""Secure user authentication."""
if not request.is_json:
raise BadRequest("Content-Type must be application/json")
data = request.get_json()
username = data.get('username', '')
password = data.get('password', '')
user = self.users.get(username)
# Use constant-time comparison
if user and check_password_hash(user['password_hash'], password):
# In production, use proper session management
return Response('{"message": "Login successful"}',
mimetype='application/json')
# Don't leak information about whether username exists
return Response('{"error": "Invalid credentials"}', status=401)
def upload_file(self, request):
"""Secure file upload."""
if 'file' not in request.files:
raise BadRequest("No file provided")
uploaded_file = request.files['file']
username = request.form.get('username', '') # In production, use sessions
if not username or username not in self.users:
raise Forbidden("Authentication required")
if not uploaded_file.filename:
raise BadRequest("No filename provided")
# Secure filename
filename = secure_filename(uploaded_file.filename)
if not filename:
raise BadRequest("Invalid filename")
# Create user subdirectory safely
user_dir = safe_join(self.upload_dir, username)
if user_dir is None:
raise BadRequest("Invalid username")
os.makedirs(user_dir, exist_ok=True)
# Create safe file path
file_path = safe_join(user_dir, filename)
if file_path is None:
raise BadRequest("Invalid file path")
# Save file
uploaded_file.save(file_path)
# Track user's files
relative_path = os.path.join(username, filename)
self.users[username]['files'].append(relative_path)
return Response(f'{{"message": "File {filename} uploaded successfully"}}',
mimetype='application/json')
def get_file(self, request, filepath):
"""Secure file serving."""
username = request.args.get('username', '') # In production, use sessions
if not username or username not in self.users:
raise Forbidden("Authentication required")
# Verify file belongs to user
if filepath not in self.users[username]['files']:
raise Forbidden("File not accessible")
# Safe file path construction
full_path = safe_join(self.upload_dir, filepath)
if full_path is None or not os.path.exists(full_path):
raise BadRequest("File not found")
# Additional security check
if not full_path.startswith(os.path.abspath(self.upload_dir)):
raise Forbidden("Access denied")
# In production, use proper file serving (X-Sendfile, etc.)
with open(full_path, 'rb') as f:
content = f.read()
return Response(content, mimetype='application/octet-stream')
def __call__(self, environ, start_response):
"""WSGI application."""
request = Request(environ)
try:
if request.path == '/register' and request.method == 'POST':
response = self.register(request)
elif request.path == '/login' and request.method == 'POST':
response = self.login(request)
elif request.path == '/upload' and request.method == 'POST':
response = self.upload_file(request)
elif request.path.startswith('/files/'):
filepath = request.path[7:] # Remove '/files/'
response = self.get_file(request, filepath)
else:
response = Response('Not Found', status=404)
except (BadRequest, Forbidden) as e:
response = Response(str(e), status=e.code)
except Exception as e:
# Don't leak internal errors
response = Response('Internal Server Error', status=500)
return response(environ, start_response)
if __name__ == '__main__':
from werkzeug.serving import run_simple
app = SecureWebApp()
print("Secure web app running on http://localhost:8000")
print("\nExample usage:")
print("POST /register with JSON: {'username': 'test', 'password': 'password123'}")
print("POST /login with JSON: {'username': 'test', 'password': 'password123'}")
print("POST /upload with form data: file + username")
print("GET /files/username/filename.txt?username=test")
run_simple('localhost', 8000, app, use_reloader=True)from werkzeug.security import generate_password_hash, check_password_hash, safe_join, secure_filename
import secrets
import hmac
class SecurityBestPractices:
"""Examples of security best practices using Werkzeug utilities."""
@staticmethod
def strong_password_policy(password):
"""Implement strong password policy."""
if len(password) < 12:
return False, "Password must be at least 12 characters"
if not any(c.isupper() for c in password):
return False, "Password must contain uppercase letters"
if not any(c.islower() for c in password):
return False, "Password must contain lowercase letters"
if not any(c.isdigit() for c in password):
return False, "Password must contain numbers"
if not any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password):
return False, "Password must contain special characters"
return True, "Password meets requirements"
@staticmethod
def secure_session_token():
"""Generate cryptographically secure session token."""
return secrets.token_urlsafe(32)
@staticmethod
def constant_time_compare(a, b):
"""Use constant-time comparison for security-sensitive data."""
return hmac.compare_digest(str(a), str(b))
@staticmethod
def validate_file_upload(file_storage, allowed_extensions=None, max_size=None):
"""Comprehensive file upload validation."""
if not file_storage.filename:
raise ValueError("No filename provided")
# Secure filename
filename = secure_filename(file_storage.filename)
if not filename:
raise ValueError("Invalid filename")
# Check file extension
if allowed_extensions:
ext = filename.rsplit('.', 1)[-1].lower()
if ext not in allowed_extensions:
raise ValueError(f"File type .{ext} not allowed")
# Check file size
if max_size:
file_storage.seek(0, 2) # Seek to end
size = file_storage.tell()
file_storage.seek(0) # Reset position
if size > max_size:
raise ValueError(f"File too large ({size} bytes)")
return filename
@staticmethod
def secure_file_path(base_dir, *path_components):
"""Create secure file path with multiple safety checks."""
# Initial safe join
path = safe_join(base_dir, *path_components)
if path is None:
return None
# Ensure path is within base directory
abs_base = os.path.abspath(base_dir)
abs_path = os.path.abspath(path)
if not abs_path.startswith(abs_base + os.sep):
return None
return path
# Example usage of security best practices
def secure_application_example():
security = SecurityBestPractices()
# Test password policy
passwords = ["weak", "StrongPassword123!", "short"]
for pwd in passwords:
valid, msg = security.strong_password_policy(pwd)
print(f"'{pwd}': {msg}")
# Generate secure tokens
token = security.secure_session_token()
print(f"Session token: {token}")
# File upload validation
class MockFileStorage:
def __init__(self, filename, size=1000):
self.filename = filename
self._size = size
self._pos = 0
def seek(self, pos, whence=0):
if whence == 2: # SEEK_END
self._pos = self._size
else:
self._pos = pos
def tell(self):
return self._pos
# Test file validation
test_files = [
("document.pdf", 1000),
("script.exe", 500),
("large_file.jpg", 5000000),
]
allowed_extensions = {'pdf', 'jpg', 'png', 'txt'}
max_size = 1024 * 1024 # 1MB
for filename, size in test_files:
mock_file = MockFileStorage(filename, size)
try:
secure_name = security.validate_file_upload(
mock_file, allowed_extensions, max_size
)
print(f"'{filename}' → '{secure_name}' (valid)")
except ValueError as e:
print(f"'{filename}' → Error: {e}")
if __name__ == '__main__':
secure_application_example()Install with Tessl CLI
npx tessl i tessl/pypi-werkzeug