BlakeL's picture
Upload 115 files
3f9f85b verified
"""
Security utilities for API key management and validation.
This module provides secure handling of sensitive configuration data.
"""
import hashlib
import os
import secrets
from typing import Optional, Dict, Any
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
class ErrorType(Enum):
"""Types of errors that can occur in the system."""
VALIDATION_ERROR = "validation_error"
AUTHENTICATION_ERROR = "authentication_error"
AUTHORIZATION_ERROR = "authorization_error"
RATE_LIMIT_ERROR = "rate_limit_error"
API_ERROR = "api_error"
NETWORK_ERROR = "network_error"
TIMEOUT_ERROR = "timeout_error"
UNKNOWN_ERROR = "unknown_error"
@dataclass
class ErrorResponse:
"""Standardized error response for API operations."""
error_type: ErrorType
error_code: str
message: str
details: Optional[str] = None
timestamp: datetime = None
request_id: Optional[str] = None
def __post_init__(self):
if self.timestamp is None:
self.timestamp = datetime.now()
def __iter__(self):
"""Prevent unpacking by raising a clear error message."""
raise TypeError(
"ErrorResponse object cannot be unpacked. "
"Access attributes directly: error_response.error_type, error_response.error_code, etc."
)
def to_dict(self) -> Dict[str, Any]:
"""Convert ErrorResponse to dictionary for easy access."""
return {
"error_type": self.error_type.value if hasattr(self.error_type, 'value') else str(self.error_type),
"error_code": self.error_code,
"message": self.message,
"details": self.details,
"timestamp": self.timestamp,
"request_id": self.request_id
}
class SecurityUtils:
"""Security utilities for API key management."""
@staticmethod
def mask_api_key(api_key: str, visible_chars: int = 4) -> str:
"""
Mask an API key for safe logging.
Args:
api_key: The API key to mask
visible_chars: Number of characters to show at the end
Returns:
Masked API key string
"""
if not api_key or len(api_key) < visible_chars:
return "***"
masked_length = len(api_key) - visible_chars
return "*" * masked_length + api_key[-visible_chars:]
@staticmethod
def generate_secret_key(length: int = 32) -> str:
"""
Generate a cryptographically secure secret key.
Args:
length: Length of the secret key
Returns:
Random secret key
"""
return secrets.token_urlsafe(length)
@staticmethod
def hash_api_key(api_key: str) -> str:
"""
Create a hash of an API key for identification purposes.
Args:
api_key: The API key to hash
Returns:
SHA-256 hash of the API key
"""
return hashlib.sha256(api_key.encode()).hexdigest()[:16]
@staticmethod
def validate_api_key_format(api_key: str, expected_prefix: Optional[str] = None) -> bool:
"""
Validate API key format.
Args:
api_key: The API key to validate
expected_prefix: Expected prefix (e.g., "sk-", "pk-")
Returns:
True if valid, False otherwise
"""
if not api_key or len(api_key) < 10:
return False
if expected_prefix and not api_key.startswith(expected_prefix):
return False
return True
@staticmethod
def check_environment_security() -> dict:
"""
Check environment security settings.
Returns:
Dictionary with security status
"""
security_status = {
"env_file_exists": os.path.exists(".env"),
"env_file_permissions": None,
"home_directory_secure": True,
"temp_directory_secure": True,
"recommendations": []
}
# Check .env file permissions
if security_status["env_file_exists"]:
try:
stat_info = os.stat(".env")
permissions = oct(stat_info.st_mode)[-3:]
security_status["env_file_permissions"] = permissions
# Check if file is world-readable (security risk)
if int(permissions[2]) > 4: # Others can read
security_status["recommendations"].append(
"⚠️ .env file is world-readable. Run: chmod 600 .env"
)
except OSError:
security_status["env_file_permissions"] = "unknown"
# Check if running in secure environment
if os.path.expanduser("~") == "/root":
security_status["recommendations"].append(
"⚠️ Running as root user. Consider using a non-root user."
)
# Check for common security issues
if not security_status["env_file_exists"]:
security_status["recommendations"].append(
"❌ .env file not found. Create one from env.example"
)
return security_status
def secure_log_api_key(api_key: str, key_name: str = "API_KEY") -> str:
"""
Create a secure log message for API keys.
Args:
api_key: The API key
key_name: Name of the key for logging
Returns:
Safe log message
"""
if not api_key:
return f"{key_name}: Not configured"
masked_key = SecurityUtils.mask_api_key(api_key)
key_hash = SecurityUtils.hash_api_key(api_key)
return f"{key_name}: {masked_key} (hash: {key_hash})"
# Example usage
if __name__ == "__main__":
# Test security utilities
test_key = "sk-1234567890abcdef1234567890abcdef"
print("🔒 Security Utilities Test:")
print(f"Original key: {test_key}")
print(f"Masked key: {SecurityUtils.mask_api_key(test_key)}")
print(f"Key hash: {SecurityUtils.hash_api_key(test_key)}")
print(f"Valid format: {SecurityUtils.validate_api_key_format(test_key, 'sk-')}")
print(f"Secure log: {secure_log_api_key(test_key, 'ANTHROPIC_API_KEY')}")
print("\n🛡️ Environment Security Check:")
security_status = SecurityUtils.check_environment_security()
print(f"Environment file exists: {security_status['env_file_exists']}")
print(f"File permissions: {security_status['env_file_permissions']}")
if security_status['recommendations']:
print("\n📋 Security Recommendations:")
for rec in security_status['recommendations']:
print(f" {rec}")
else:
print("✅ No security issues detected!")