security-patterns
Security Patterns Skill
Security best practices and patterns for [PROJECT_NAME] project.
When This Activates
- API key handling
- User input validation
- File operations
- Security-sensitive code
- Keywords: "security", "api key", "secret", "validate", "input"
API Keys & Secrets
Environment Variables (REQUIRED)
import os
from pathlib import Path
from dotenv import load_dotenv
# ✅ CORRECT: Load from environment
load_dotenv()
api_key = os.getenv("ANTHROPIC_API_KEY")
if not api_key:
raise ValueError(
"ANTHROPIC_API_KEY not set\n"
"Add to .env file: ANTHROPIC_API_KEY=sk-ant-...\n"
"See: docs/guides/setup.md"
)
# ❌ WRONG: Hardcoded secret
api_key = "sk-ant-1234567890abcdef" # NEVER DO THIS!
.env File Setup
# .env (must be in .gitignore!)
ANTHROPIC_API_KEY=sk-ant-your-key-here
OPENAI_API_KEY=sk-your-key-here
HUGGINGFACE_TOKEN=hf_your-token-here
.gitignore MUST Include
# .gitignore
.env
.env.local
.env.*.local
*.key
*.pem
secrets/
Secure API Key Validation
import re
def validate_anthropic_key(api_key: str) -> bool:
"""Validate Anthropic API key format.
Args:
api_key: API key to validate
Returns:
True if valid format
Raises:
ValueError: If invalid format
"""
if not api_key:
raise ValueError("API key is empty")
if not api_key.startswith("sk-ant-"):
raise ValueError(
"Invalid Anthropic API key format\n"
"Expected: sk-ant-...\n"
"See: docs/guides/api-keys.md"
)
# Check length (Anthropic keys are ~40 chars)
if len(api_key) < 20:
raise ValueError("API key too short")
return True
Input Validation
Path Traversal Prevention
from pathlib import Path
def load_safe_file(filename: str, base_dir: Path) -> str:
"""Load file with path traversal protection.
Args:
filename: Requested filename
base_dir: Base directory (files must be within this)
Returns:
File contents
Raises:
ValueError: If path traversal detected
FileNotFoundError: If file doesn't exist
"""
# Resolve to absolute path
base_dir = base_dir.resolve()
file_path = (base_dir / filename).resolve()
# Check file is within base_dir (prevents ../ attacks)
if not file_path.is_relative_to(base_dir):
raise ValueError(
f"Invalid file path: {filename}\n"
f"Path traversal detected (../ not allowed)\n"
f"Allowed directory: {base_dir}"
)
if not file_path.exists():
raise FileNotFoundError(f"File not found: {file_path}")
return file_path.read_text()
# ✅ SAFE: Validates path
content = load_safe_file("config.yaml", Path("/data"))
# ❌ BLOCKED: Path traversal attempt
content = load_safe_file("../../etc/passwd", Path("/data")) # ValueError!
Command Injection Prevention
import subprocess
import shlex
# ✅ CORRECT: Shell=False with list arguments
def run_command_safe(command: str, args: list[str]) -> str:
"""Run command safely without shell injection.
Args:
command: Command to run
args: List of arguments
Returns:
Command output
"""
result = subprocess.run(
[command] + args, # List, not string
shell=False, # CRITICAL: Never use shell=True
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0:
raise RuntimeError(f"Command failed: {result.stderr}")
return result.stdout
# ✅ SAFE: No injection possible
output = run_command_safe("ls", ["-la", "/tmp"])
# ❌ WRONG: Shell injection risk
def run_command_unsafe(user_input: str):
# User could input: "; rm -rf /"
subprocess.run(f"ls {user_input}", shell=True) # NEVER DO THIS!
SQL Injection Prevention
import sqlite3
# ✅ CORRECT: Parameterized queries
def get_user_safe(db, username: str):
"""Safe database query with parameters."""
cursor = db.cursor()
cursor.execute(
"SELECT * FROM users WHERE username = ?", # Parameterized
(username,)
)
return cursor.fetchone()
# ❌ WRONG: String interpolation
def get_user_unsafe(db, username):
# User could input: "admin' OR '1'='1"
cursor = db.cursor()
cursor.execute(f"SELECT * FROM users WHERE username = '{username}'")
File Operations Security
Secure File Permissions
from pathlib import Path
import os
def create_secure_file(path: Path, content: str) -> None:
"""Create file with restricted permissions.
Args:
path: File path
content: File content
"""
# Write file
path.write_text(content)
# Set permissions to owner-only (0o600 = rw-------)
path.chmod(0o600)
def create_secure_directory(path: Path) -> None:
"""Create directory with restricted permissions."""
path.mkdir(parents=True, exist_ok=True)
# Owner only (0o700 = rwx------)
path.chmod(0o700)
# Usage
cache_dir = Path.home() / ".cache" / "[project_name]"
create_secure_directory(cache_dir)
config_file = cache_dir / "api_key.txt"
create_secure_file(config_file, api_key)
File Upload Validation
from pathlib import Path
ALLOWED_EXTENSIONS = {".json", ".yaml", ".yml", ".txt", ".csv"}
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
def validate_upload(file_path: Path) -> None:
"""Validate uploaded file.
Args:
file_path: Path to uploaded file
Raises:
ValueError: If file invalid
"""
# Check extension
if file_path.suffix.lower() not in ALLOWED_EXTENSIONS:
raise ValueError(
f"Invalid file type: {file_path.suffix}\n"
f"Allowed: {ALLOWED_EXTENSIONS}"
)
# Check size
size = file_path.stat().st_size
if size > MAX_FILE_SIZE:
raise ValueError(
f"File too large: {size / 1024 / 1024:.1f}MB\n"
f"Maximum: {MAX_FILE_SIZE / 1024 / 1024}MB"
)
# Check not executable
if os.access(file_path, os.X_OK):
raise ValueError("Executable files not allowed")
Cryptographic Operations
Secure Random Generation
import secrets
# ✅ CORRECT: Cryptographically secure
def generate_token() -> str:
"""Generate secure random token."""
return secrets.token_hex(32) # 64 characters
def generate_session_id() -> str:
"""Generate secure session ID."""
return secrets.token_urlsafe(32)
# ❌ WRONG: Not cryptographically secure
import random
token = str(random.randint(0, 999999)) # NEVER for security!
Password Hashing (if needed)
import hashlib
import secrets
def hash_password(password: str) -> tuple[str, str]:
"""Hash password with salt.
Args:
password: Plain text password
Returns:
Tuple of (salt, hashed_password)
"""
# Generate random salt
salt = secrets.token_hex(16)
# Hash with salt
hashed = hashlib.pbkdf2_hmac(
'sha256',
password.encode('utf-8'),
salt.encode('utf-8'),
100000 # iterations
)
return salt, hashed.hex()
def verify_password(
password: str,
salt: str,
expected_hash: str
) -> bool:
"""Verify password against hash."""
hashed = hashlib.pbkdf2_hmac(
'sha256',
password.encode('utf-8'),
salt.encode('utf-8'),
100000
)
return hashed.hex() == expected_hash
Model Download Security
Validate HuggingFace Repo
import re
def validate_repo_id(repo_id: str) -> bool:
"""Validate HuggingFace repository ID.
Args:
repo_id: Repository ID (org/model)
Returns:
True if valid
Raises:
ValueError: If invalid format
"""
# Expected format: org/model-name
pattern = r'^[a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+$'
if not re.match(pattern, repo_id):
raise ValueError(
f"Invalid repo ID: {repo_id}\n"
f"Expected format: organization/model-name\n"
f"Example: [model_repo]/Llama-3.2-1B-Instruct-4bit"
)
# Prevent malicious patterns
if '..' in repo_id or '/' * 2 in repo_id:
raise ValueError("Invalid characters in repo ID")
return True
# ✅ SAFE
validate_repo_id("[model_repo]/Llama-3.2-1B-Instruct-4bit")
# ❌ BLOCKED
validate_repo_id("../../../etc/passwd") # ValueError!
Logging Security
Never Log Secrets
import logging
# ✅ CORRECT: Redact sensitive data
def log_api_call(api_key: str, endpoint: str):
"""Log API call without exposing key."""
masked_key = api_key[:7] + "***" + api_key[-4:]
logging.info(f"API call to {endpoint} with key {masked_key}")
# ❌ WRONG: Logs full API key
def log_api_call_unsafe(api_key, endpoint):
logging.info(f"API call: {endpoint} | Key: {api_key}") # NEVER!
Dependencies Security
Check for Vulnerabilities
# Install safety
pip install safety
# Check dependencies
safety check
# Check specific requirements
safety check -r requirements.txt
# Alternative: pip-audit
pip install pip-audit
pip-audit
Security Checklist
Code Review
- No hardcoded API keys/secrets
- All secrets in .env (gitignored)
- .env file in .gitignore
- Input validation on user data
- Path traversal prevention
- No shell=True in subprocess
- Parameterized database queries
- Secure file permissions
- Cryptographically secure random
- No secrets in logs
- Dependencies scanned for vulnerabilities
File Operations
- Validate file extensions
- Check file size limits
- Prevent path traversal
- Restrict file permissions
- Validate before deserialize
API Operations
- API keys from environment
- Keys validated before use
- Keys masked in logs
- Rate limiting considered
- Error messages don't expose secrets
Common Vulnerabilities (OWASP Top 10)
- Injection → Use parameterized queries
- Authentication → Use secure tokens (secrets module)
- Sensitive Data → Never hardcode, use .env
- XXE → Disable external entities in XML
- Access Control → Validate file paths
- Security Config → Secure defaults
- XSS → Sanitize output (if web)
- Deserialization → Don't unpickle untrusted data
- Components → Keep dependencies updated
- Logging → Don't log secrets
Key Takeaways
- Never hardcode secrets - Use environment variables
- Validate all inputs - User data, file paths, commands
- Prevent path traversal - Use
is_relative_to() - No shell=True - Use list arguments with subprocess
- Parameterized queries - Never string interpolation
- Secure random - Use
secretsmodule - Restrict permissions - Files 0o600, dirs 0o700
- Mask secrets in logs - Show only first/last few chars
- Scan dependencies - Use safety/pip-audit
- .gitignore secrets - .env, _.key, _.pem
More from akaszubski/anyclaude-local
testing-guide
Complete testing methodology - TDD, progression tracking, regression prevention, and test patterns
1observability
Logging, debugging, profiling, and performance monitoring for development. Use when adding logging, debugging issues, profiling performance, or instrumenting code for observability.
1git-workflow
Git best practices, commit conventions, branching strategies, and pull request workflows. Use when working with git operations, commits, branches, or PRs.
1python-standards
Python code quality standards (PEP 8, type hints, docstrings). Use when writing Python code.
1github-workflow
GitHub-first workflow - Issues, PRs, milestones, auto-tracking for solo developer productivity
1code-review
This skill should be used when reviewing code or preparing code for review. It provides guidelines for what to look for in reviews, how to write constructive feedback, and standards for review comments.
1