Claude Code Plugins

Community-maintained marketplace

Feedback

security-patterns

@akaszubski/realign
1
0

Security best practices, API key management, input validation. Use when handling secrets, user input, or security-sensitive code.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name security-patterns
type knowledge
description Security best practices, API key management, input validation. Use when handling secrets, user input, or security-sensitive code.
keywords security, api key, secret, validation, injection, owasp
auto_activate true

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)

  1. Injection → Use parameterized queries
  2. Authentication → Use secure tokens (secrets module)
  3. Sensitive Data → Never hardcode, use .env
  4. XXE → Disable external entities in XML
  5. Access Control → Validate file paths
  6. Security Config → Secure defaults
  7. XSS → Sanitize output (if web)
  8. Deserialization → Don't unpickle untrusted data
  9. Components → Keep dependencies updated
  10. Logging → Don't log secrets

Key Takeaways

  1. Never hardcode secrets - Use environment variables
  2. Validate all inputs - User data, file paths, commands
  3. Prevent path traversal - Use is_relative_to()
  4. No shell=True - Use list arguments with subprocess
  5. Parameterized queries - Never string interpolation
  6. Secure random - Use secrets module
  7. Restrict permissions - Files 0o600, dirs 0o700
  8. Mask secrets in logs - Show only first/last few chars
  9. Scan dependencies - Use safety/pip-audit
  10. .gitignore secrets - .env, *.key, *.pem