Security best practices, API key management, input validation. Use when handling secrets, user input, or security-sensitive code.
Provides security patterns for API keys, input validation, and file operations. Activates when handling secrets, user input, or security-sensitive code to prevent vulnerabilities like path traversal and injection attacks.
/plugin marketplace add akaszubski/autonomous-dev/plugin install autonomous-dev@autonomous-devThis skill is limited to using the following tools:
Security best practices and patterns for [PROJECT_NAME] project.
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 (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
.env
.env.local
.env.*.local
*.key
*.pem
secrets/
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
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!
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!
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}'")
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)
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")
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!
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
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!
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!
# 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
is_relative_to()secrets moduleThis skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.