Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:51:02 +08:00
commit ff1f4bd119
252 changed files with 72682 additions and 0 deletions

View File

@@ -0,0 +1,157 @@
# Bandit Test to CWE and OWASP Mapping
Complete mapping between Bandit test IDs, Common Weakness Enumeration (CWE), and OWASP Top 10 2021 categories.
## Table of Contents
- [Cryptographic Issues](#cryptographic-issues)
- [Injection Vulnerabilities](#injection-vulnerabilities)
- [Security Misconfiguration](#security-misconfiguration)
- [Insecure Deserialization](#insecure-deserialization)
- [Access Control Issues](#access-control-issues)
## Cryptographic Issues
### OWASP A02:2021 - Cryptographic Failures
| Test ID | Description | CWE | Severity |
|---------|-------------|-----|----------|
| B302 | Use of insecure MD2, MD4, MD5, or SHA1 hash function | CWE-327 | MEDIUM |
| B303 | Use of insecure MD2, MD4, or MD5 hash function | CWE-327 | MEDIUM |
| B304 | Use of insecure MD2, MD4, MD5, or SHA1 hash function | CWE-327 | MEDIUM |
| B305 | Use of insecure cipher mode | CWE-327 | MEDIUM |
| B306 | Use of insecure and deprecated function (mktemp) | CWE-377 | MEDIUM |
| B307 | Use of possibly insecure function (eval) | CWE-78 | MEDIUM |
| B311 | Standard pseudo-random generators are not suitable for security | CWE-330 | LOW |
| B323 | Unverified context with insecure default | CWE-327 | MEDIUM |
| B324 | Use of insecure hash functions in hashlib | CWE-327 | HIGH |
| B401 | Use of insecure telnet protocol | CWE-319 | HIGH |
| B402 | Use of insecure FTP protocol | CWE-319 | HIGH |
| B403 | Use of insecure pickle import | CWE-502 | LOW |
| B404 | Use of insecure subprocess import | CWE-78 | LOW |
| B413 | Use of pycrypto | CWE-327 | HIGH |
| B501 | Use of weak cryptographic key | CWE-326 | HIGH |
| B502 | Use of weak SSL/TLS protocol | CWE-327 | HIGH |
| B503 | Use of insecure SSL/TLS cipher | CWE-327 | MEDIUM |
| B504 | SSL with no version specified | CWE-327 | LOW |
| B505 | Use of weak cryptographic hash | CWE-327 | MEDIUM |
**Remediation Strategy**: Replace weak cryptographic algorithms with strong alternatives. Use SHA-256 or SHA-512 for hashing, AES-256 for encryption, and TLS 1.2+ for transport security. For password hashing, use bcrypt, scrypt, or Argon2.
## Injection Vulnerabilities
### OWASP A03:2021 - Injection
| Test ID | Description | CWE | Severity |
|---------|-------------|-----|----------|
| B308 | Use of mark_safe | CWE-80 | MEDIUM |
| B313 | XML bad element tree | CWE-611 | MEDIUM |
| B314 | XML bad element tree (lxml) | CWE-611 | MEDIUM |
| B315 | XML bad element tree (expat) | CWE-611 | MEDIUM |
| B316 | XML bad element tree (sax) | CWE-611 | MEDIUM |
| B317 | XML bad element tree (expatreader) | CWE-611 | MEDIUM |
| B318 | XML bad element tree (expatbuilder) | CWE-611 | MEDIUM |
| B319 | XML bad element tree (xmlrpc) | CWE-611 | HIGH |
| B320 | XML bad element tree (pulldom) | CWE-611 | HIGH |
| B321 | FTP-related functions are being called | CWE-319 | HIGH |
| B405 | XML mini DOM import | CWE-611 | LOW |
| B406 | XML etree import | CWE-611 | LOW |
| B407 | XML expat import | CWE-611 | LOW |
| B408 | XML minidom import | CWE-611 | LOW |
| B410 | XML etree import (lxml) | CWE-611 | LOW |
| B411 | XML standard library imports | CWE-611 | LOW |
| B412 | Deprecated httpoxy vulnerability | CWE-807 | LOW |
| B601 | Paramiko call with shell=True | CWE-78 | HIGH |
| B602 | subprocess call with shell=True | CWE-78 | HIGH |
| B603 | subprocess without shell=True | CWE-78 | LOW |
| B604 | Function call with shell=True | CWE-78 | HIGH |
| B605 | Starting a process with a shell | CWE-78 | HIGH |
| B606 | Starting a process without shell | CWE-78 | LOW |
| B607 | Starting a process with a partial path | CWE-78 | LOW |
| B608 | Possible SQL injection vector through string formatting | CWE-89 | MEDIUM |
| B609 | Use of wildcard injection | CWE-78 | MEDIUM |
| B610 | Potential SQL injection via Django raw SQL | CWE-89 | MEDIUM |
| B611 | Potential SQL injection via Django extra | CWE-89 | MEDIUM |
**Remediation Strategy**: Never concatenate user input into commands, queries, or markup. Use parameterized queries for SQL, safe XML parsers with DTD processing disabled, and avoid `shell=True` in subprocess calls. Use `shlex.split()` for argument parsing.
## Security Misconfiguration
### OWASP A05:2021 - Security Misconfiguration
| Test ID | Description | CWE | Severity |
|---------|-------------|-----|----------|
| B201 | Flask app run with debug=True | CWE-489 | HIGH |
| B310 | Audit URL open for permitted schemes | CWE-939 | MEDIUM |
| B506 | Test for use of yaml load | CWE-20 | MEDIUM |
| B507 | SSH with no host key verification | CWE-295 | MEDIUM |
| B701 | jinja2 autoescape false | CWE-94 | HIGH |
| B702 | Test for use of mako templates | CWE-94 | MEDIUM |
| B703 | Django autoescape false | CWE-94 | MEDIUM |
**Remediation Strategy**: Disable debug mode in production, validate and sanitize all inputs, enable autoescape in template engines, use safe YAML loaders (`yaml.safe_load()`), and enforce strict host key verification for SSH connections.
## Insecure Deserialization
### OWASP A08:2021 - Software and Data Integrity Failures
| Test ID | Description | CWE | Severity |
|---------|-------------|-----|----------|
| B301 | Pickle and modules that wrap it can be unsafe | CWE-502 | MEDIUM |
**Remediation Strategy**: Avoid using pickle for untrusted data. Use JSON, MessagePack, or Protocol Buffers with strict schema validation. If pickle is necessary, implement cryptographic signing and validation of serialized data.
## Access Control Issues
### OWASP A01:2021 - Broken Access Control
| Test ID | Description | CWE | Severity |
|---------|-------------|-----|----------|
| B506 | Test for use of yaml load (arbitrary code execution) | CWE-20 | MEDIUM |
**Remediation Strategy**: Use `yaml.safe_load()` instead of `yaml.load()` to prevent arbitrary code execution. Implement proper access controls and input validation for all YAML processing.
## Hardcoded Credentials
### OWASP A02:2021 - Cryptographic Failures
| Test ID | Description | CWE | Severity |
|---------|-------------|-----|----------|
| B105 | Possible hardcoded password string | CWE-259 | LOW |
| B106 | Possible hardcoded password function argument | CWE-259 | LOW |
| B107 | Possible hardcoded password default argument | CWE-259 | LOW |
**Remediation Strategy**: Never hardcode credentials. Use environment variables, secret management services (HashiCorp Vault, AWS Secrets Manager), or encrypted configuration files with proper key management.
## Priority Matrix
Use this matrix to prioritize remediation efforts:
| Priority | Criteria | Action |
|----------|----------|--------|
| **CRITICAL** | HIGH Severity + HIGH Confidence | Immediate remediation required |
| **HIGH** | HIGH Severity OR MEDIUM Severity + HIGH Confidence | Remediate within 1 sprint |
| **MEDIUM** | MEDIUM Severity + MEDIUM Confidence | Remediate within 2 sprints |
| **LOW** | LOW Severity OR LOW Confidence | Address during refactoring |
| **INFORMATIONAL** | Review only | Document and monitor |
## OWASP Top 10 2021 Coverage
| OWASP Category | Bandit Coverage | Notes |
|----------------|-----------------|-------|
| A01:2021 Broken Access Control | Partial | Covers YAML deserialization |
| A02:2021 Cryptographic Failures | Excellent | Comprehensive crypto checks |
| A03:2021 Injection | Excellent | SQL, command, XML injection |
| A04:2021 Insecure Design | None | Requires manual review |
| A05:2021 Security Misconfiguration | Good | Debug mode, templating |
| A06:2021 Vulnerable Components | None | Use Safety or pip-audit |
| A07:2021 Authentication Failures | Partial | Hardcoded credentials only |
| A08:2021 Data Integrity Failures | Good | Deserialization issues |
| A09:2021 Security Logging Failures | None | Requires manual review |
| A10:2021 SSRF | Partial | URL scheme validation |
## References
- [OWASP Top 10 2021](https://owasp.org/Top10/)
- [CWE Database](https://cwe.mitre.org/)
- [Bandit Documentation](https://bandit.readthedocs.io/)

View File

@@ -0,0 +1,622 @@
# Bandit Finding Remediation Guide
Comprehensive secure coding patterns and remediation strategies for common Bandit findings.
## Table of Contents
- [Hardcoded Credentials](#hardcoded-credentials)
- [SQL Injection](#sql-injection)
- [Command Injection](#command-injection)
- [Weak Cryptography](#weak-cryptography)
- [Insecure Deserialization](#insecure-deserialization)
- [XML External Entity (XXE)](#xml-external-entity-xxe)
- [Security Misconfiguration](#security-misconfiguration)
---
## Hardcoded Credentials
### B105, B106, B107: Hardcoded Passwords
**Vulnerable Code:**
```python
# B105: Hardcoded password string
DATABASE_PASSWORD = "admin123"
# B106: Hardcoded password in function call
db.connect(host="localhost", password="secret_password")
# B107: Hardcoded password default argument
def connect_db(password="default_pass"):
pass
```
**Secure Solution:**
```python
import os
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Use environment variables
DATABASE_PASSWORD = os.environ.get("DATABASE_PASSWORD")
if not DATABASE_PASSWORD:
raise ValueError("DATABASE_PASSWORD environment variable not set")
# Use environment variables in function calls
db.connect(
host=os.environ.get("DB_HOST", "localhost"),
password=os.environ.get("DB_PASSWORD")
)
# Use secret management service (example with AWS Secrets Manager)
import boto3
from botocore.exceptions import ClientError
def get_secret(secret_name):
session = boto3.session.Session()
client = session.client(service_name='secretsmanager', region_name='us-east-1')
try:
response = client.get_secret_value(SecretId=secret_name)
return response['SecretString']
except ClientError as e:
raise Exception(f"Failed to retrieve secret: {e}")
DATABASE_PASSWORD = get_secret("prod/db/password")
```
**Best Practices:**
- Use environment variables with `.env` files (never commit `.env` to version control)
- Use secret management services (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault)
- Implement secret rotation policies
- Use configuration management tools (Ansible Vault, Kubernetes Secrets)
---
## SQL Injection
### B608: SQL Injection via String Formatting
**Vulnerable Code:**
```python
# String formatting (UNSAFE)
user_id = request.GET['id']
query = f"SELECT * FROM users WHERE id = {user_id}"
cursor.execute(query)
# String concatenation (UNSAFE)
query = "SELECT * FROM users WHERE username = '" + username + "'"
cursor.execute(query)
# Percent formatting (UNSAFE)
query = "SELECT * FROM users WHERE email = '%s'" % email
cursor.execute(query)
```
**Secure Solution with psycopg2:**
```python
import psycopg2
# Parameterized queries (SAFE)
user_id = request.GET['id']
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))
# Multiple parameters
query = "SELECT * FROM users WHERE username = %s AND active = %s"
cursor.execute(query, (username, True))
# Named parameters
query = "SELECT * FROM users WHERE username = %(username)s AND email = %(email)s"
cursor.execute(query, {'username': username, 'email': email})
```
**Secure Solution with SQLAlchemy ORM:**
```python
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
# Using ORM (SAFE)
with Session(engine) as session:
stmt = select(User).where(User.username == username)
user = session.execute(stmt).scalar_one_or_none()
# Using bound parameters with raw SQL (SAFE)
with Session(engine) as session:
result = session.execute(
text("SELECT * FROM users WHERE username = :username"),
{"username": username}
)
```
**Secure Solution with Django ORM:**
```python
from django.db.models import Q
# Django ORM (SAFE)
users = User.objects.filter(username=username)
# Complex queries (SAFE)
users = User.objects.filter(Q(username=username) | Q(email=email))
# Raw SQL with parameters (SAFE)
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("SELECT * FROM users WHERE username = %s", [username])
```
**Best Practices:**
- Always use parameterized queries or prepared statements
- Never concatenate user input into SQL queries
- Use ORM when possible for automatic escaping
- Validate and sanitize inputs at application boundaries
- Apply least privilege principle to database accounts
---
## Command Injection
### B602, B604, B605: Shell Injection in Subprocess
**Vulnerable Code:**
```python
import subprocess
import os
# shell=True with user input (VERY UNSAFE)
filename = request.GET['file']
subprocess.call(f"cat {filename}", shell=True)
# os.system with user input (VERY UNSAFE)
os.system(f"ping -c 1 {hostname}")
# String concatenation (UNSAFE)
cmd = "curl " + user_url
subprocess.call(cmd, shell=True)
```
**Secure Solution:**
```python
import subprocess
import shlex
from pathlib import Path
# Use list of arguments without shell=True (SAFE)
filename = request.GET['file']
subprocess.run(["cat", filename], check=True, capture_output=True)
# Validate input before use
def validate_filename(filename):
"""Validate filename to prevent path traversal."""
# Allow only alphanumeric, dash, underscore, and dot
if not re.match(r'^[a-zA-Z0-9_.-]+$', filename):
raise ValueError("Invalid filename")
# Resolve to absolute path and check it's within allowed directory
file_path = Path(UPLOAD_DIR) / filename
if not file_path.resolve().is_relative_to(Path(UPLOAD_DIR).resolve()):
raise ValueError("Path traversal detected")
return file_path
filename = validate_filename(request.GET['file'])
subprocess.run(["cat", str(filename)], check=True, capture_output=True)
# Use shlex.split() for complex commands
import shlex
command_string = "ping -c 1 example.com"
subprocess.run(shlex.split(command_string), check=True, capture_output=True)
# Whitelist approach for restricted commands
ALLOWED_COMMANDS = {
'ping': ['ping', '-c', '1'],
'traceroute': ['traceroute', '-m', '10'],
}
command_type = request.GET['command']
target = request.GET['target']
if command_type not in ALLOWED_COMMANDS:
raise ValueError("Command not allowed")
# Validate target (e.g., IP address or hostname)
if not re.match(r'^[a-zA-Z0-9.-]+$', target):
raise ValueError("Invalid target")
cmd = ALLOWED_COMMANDS[command_type] + [target]
subprocess.run(cmd, check=True, capture_output=True, timeout=10)
```
**Best Practices:**
- Never use `shell=True` with user input
- Pass arguments as list, not string
- Validate and whitelist all user inputs
- Use `shlex.split()` for parsing command strings
- Implement timeouts to prevent DoS
- Run subprocesses with minimal privileges
---
## Weak Cryptography
### B303, B304, B324: Weak Hash Functions
**Vulnerable Code:**
```python
import hashlib
import md5 # Deprecated
# MD5 (WEAK)
password_hash = hashlib.md5(password.encode()).hexdigest()
# SHA1 (WEAK)
token = hashlib.sha1(user_data.encode()).hexdigest()
```
**Secure Solution:**
```python
import hashlib
import secrets
import bcrypt
from argon2 import PasswordHasher
# SHA-256 for general hashing (ACCEPTABLE for non-password data)
data_hash = hashlib.sha256(data.encode()).hexdigest()
# SHA-512 (BETTER for general hashing)
data_hash = hashlib.sha512(data.encode()).hexdigest()
# bcrypt for password hashing (RECOMMENDED)
def hash_password(password: str) -> bytes:
"""Hash password using bcrypt with salt."""
salt = bcrypt.gensalt(rounds=12) # Cost factor 12
return bcrypt.hashpw(password.encode(), salt)
def verify_password(password: str, hashed: bytes) -> bool:
"""Verify password against bcrypt hash."""
return bcrypt.checkpw(password.encode(), hashed)
# Argon2 for password hashing (BEST - winner of Password Hashing Competition)
ph = PasswordHasher(
time_cost=2, # Number of iterations
memory_cost=65536, # Memory usage in KiB (64 MB)
parallelism=4, # Number of parallel threads
)
def hash_password_argon2(password: str) -> str:
"""Hash password using Argon2."""
return ph.hash(password)
def verify_password_argon2(password: str, hashed: str) -> bool:
"""Verify password against Argon2 hash."""
try:
ph.verify(hashed, password)
return True
except:
return False
# HMAC for message authentication
import hmac
def create_signature(message: str, secret_key: bytes) -> str:
"""Create HMAC-SHA256 signature."""
return hmac.new(
secret_key,
message.encode(),
hashlib.sha256
).hexdigest()
```
### B501, B502, B503: Weak SSL/TLS Configuration
**Vulnerable Code:**
```python
import ssl
import requests
# Weak SSL version (UNSAFE)
context = ssl.SSLContext(ssl.PROTOCOL_SSLv3)
# Disabling certificate verification (VERY UNSAFE)
requests.get('https://example.com', verify=False)
```
**Secure Solution:**
```python
import ssl
import requests
# Strong SSL/TLS configuration (SAFE)
context = ssl.create_default_context()
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.maximum_version = ssl.TLSVersion.TLSv1_3
# Restrict cipher suites
context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS')
# Enable certificate verification (default in requests)
response = requests.get('https://example.com', verify=True)
# Custom CA bundle
response = requests.get('https://example.com', verify='/path/to/ca-bundle.crt')
# For urllib
import urllib.request
import certifi
url = 'https://example.com'
response = urllib.request.urlopen(url, context=context, cafile=certifi.where())
```
**Best Practices:**
- Use TLS 1.2 or TLS 1.3 only
- Disable weak cipher suites
- Always verify certificates in production
- Use certificate pinning for critical connections
- Regularly update SSL/TLS libraries
---
## Insecure Deserialization
### B301: Pickle Usage
**Vulnerable Code:**
```python
import pickle
# Deserializing untrusted data (VERY UNSAFE)
user_data = pickle.loads(request.body)
# Loading from file (UNSAFE if file is from untrusted source)
with open('user_session.pkl', 'rb') as f:
session = pickle.load(f)
```
**Secure Solution:**
```python
import json
import msgpack
from cryptography.fernet import Fernet
# Use JSON for simple data (SAFE)
user_data = json.loads(request.body)
# Use MessagePack for binary efficiency (SAFE)
user_data = msgpack.unpackb(request.body)
# If pickle is absolutely necessary, use cryptographic signing
import hmac
import hashlib
import pickle
SECRET_KEY = os.environ['SECRET_KEY'].encode()
def secure_pickle_dumps(obj):
"""Serialize with HMAC signature."""
pickled = pickle.dumps(obj)
signature = hmac.new(SECRET_KEY, pickled, hashlib.sha256).digest()
return signature + pickled
def secure_pickle_loads(data):
"""Deserialize with signature verification."""
signature = data[:32] # SHA256 is 32 bytes
pickled = data[32:]
expected_signature = hmac.new(SECRET_KEY, pickled, hashlib.sha256).digest()
if not hmac.compare_digest(signature, expected_signature):
raise ValueError("Invalid signature - data may be tampered")
return pickle.loads(pickled)
# Better: Use itsdangerous for secure serialization
from itsdangerous import URLSafeSerializer
serializer = URLSafeSerializer(SECRET_KEY)
# Serialize (signed and safe)
token = serializer.dumps({'user_id': 123, 'role': 'admin'})
# Deserialize (verified)
data = serializer.loads(token)
```
**Best Practices:**
- Avoid pickle for untrusted data
- Use JSON, MessagePack, or Protocol Buffers
- If pickle is required, implement cryptographic signing
- Use `itsdangerous` library for secure token serialization
- Restrict pickle to internal, trusted data only
---
## XML External Entity (XXE)
### B313-B320, B405-B412: XML Parsing Vulnerabilities
**Vulnerable Code:**
```python
import xml.etree.ElementTree as ET
from lxml import etree
# Unsafe XML parsing (VULNERABLE to XXE)
tree = ET.parse(user_xml_file)
root = tree.getroot()
# lxml unsafe parsing
parser = etree.XMLParser()
tree = etree.parse(user_xml_file, parser)
```
**Secure Solution:**
```python
import xml.etree.ElementTree as ET
from lxml import etree
import defusedxml.ElementTree as defusedET
# Use defusedxml (RECOMMENDED)
tree = defusedET.parse(user_xml_file)
root = tree.getroot()
# Disable external entities in ElementTree
ET.XMLParser.entity = {} # Disable entity expansion
# Secure lxml configuration
parser = etree.XMLParser(
resolve_entities=False, # Disable entity resolution
no_network=True, # Disable network access
dtd_validation=False, # Disable DTD validation
load_dtd=False # Don't load DTD
)
tree = etree.parse(user_xml_file, parser)
# Alternative: Use JSON instead of XML when possible
import json
data = json.loads(request.body)
```
**Best Practices:**
- Use `defusedxml` library for all XML parsing
- Disable DTD processing and external entity resolution
- Validate XML against strict schema (XSD)
- Consider using JSON instead of XML for APIs
- Never parse XML from untrusted sources without defusedxml
---
## Security Misconfiguration
### B201: Flask Debug Mode
**Vulnerable Code:**
```python
from flask import Flask
app = Flask(__name__)
# Debug mode in production (VERY UNSAFE)
app.run(debug=True, host='0.0.0.0')
```
**Secure Solution:**
```python
from flask import Flask
import os
app = Flask(__name__)
# Use environment-based configuration
DEBUG = os.environ.get('FLASK_DEBUG', 'false').lower() == 'true'
ENV = os.environ.get('FLASK_ENV', 'production')
if ENV == 'production' and DEBUG:
raise ValueError("Debug mode cannot be enabled in production")
app.config['DEBUG'] = DEBUG
app.config['ENV'] = ENV
app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
# Use production WSGI server
if ENV == 'production':
# Deploy with gunicorn or uwsgi, not app.run()
# gunicorn -w 4 -b 0.0.0.0:8000 app:app
pass
else:
app.run(debug=DEBUG, host='127.0.0.1', port=5000)
```
### B506: YAML Load
**Vulnerable Code:**
```python
import yaml
# Arbitrary code execution (VERY UNSAFE)
config = yaml.load(user_input, Loader=yaml.Loader)
```
**Secure Solution:**
```python
import yaml
# Safe YAML loading (SAFE)
config = yaml.safe_load(user_input)
# For complex objects, use schema validation
from schema import Schema, And, Use, Optional
config_schema = Schema({
'database': {
'host': And(str, len),
'port': And(Use(int), lambda n: 1024 <= n <= 65535),
},
Optional('debug'): bool,
})
config = yaml.safe_load(user_input)
validated_config = config_schema.validate(config)
```
### B701, B702, B703: Template Autoescape
**Vulnerable Code:**
```python
from jinja2 import Environment
# Autoescape disabled (XSS VULNERABLE)
env = Environment(autoescape=False)
template = env.from_string(user_template)
output = template.render(name=user_input)
```
**Secure Solution:**
```python
from jinja2 import Environment, select_autoescape
from markupsafe import Markup, escape
# Enable autoescape (SAFE)
env = Environment(
autoescape=select_autoescape(['html', 'xml'])
)
# Or for all templates
env = Environment(autoescape=True)
# Explicitly mark safe content
def render_html(content):
# Sanitize first
clean_content = escape(content)
return Markup(clean_content)
# Django: Ensure autoescape is enabled (default)
# In Django templates:
# {{ user_input }} <!-- Auto-escaped -->
# {{ user_input|safe }} <!-- Only use after sanitization -->
```
**Best Practices:**
- Always enable autoescape in template engines
- Never mark user input as safe without sanitization
- Use Content Security Policy (CSP) headers
- Validate and sanitize all user inputs
- Use templating libraries with secure defaults
---
## General Security Principles
1. **Defense in Depth**: Implement multiple layers of security controls
2. **Least Privilege**: Grant minimum necessary permissions
3. **Fail Securely**: Errors should not expose sensitive information
4. **Input Validation**: Validate all inputs at trust boundaries
5. **Output Encoding**: Encode data based on output context
6. **Secure Defaults**: Use secure configurations by default
7. **Keep Dependencies Updated**: Regularly update security libraries
8. **Security Testing**: Include security tests in CI/CD pipelines
## Additional Resources
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
- [Python Security Best Practices](https://python.readthedocs.io/en/stable/library/security_warnings.html)
- [CWE Top 25](https://cwe.mitre.org/top25/)