Root cause
Authentication & access control introduced
feat: Implement post-quantum keyserver system with plugin architecture
How AI contributed: Direct introductionPublished Apr 1, 2026 in
jahlives/openssl_encryptAll 20 findings in this repository
Most-permissive CORS configuration in both standalone FastAPI servers (server/key-server/app/main.py and server/telemetry-server/app/main.py): CORSMiddleware with allow_origins=settings.cors_origins defaulting to ['*'], combined with allow_credentials=True, allow_methods=['*'], allow_headers=['*']; docker-compose and .env.example also default CORS_ORIGINS=*. Any website can make fully credentialed cross-origin requests to the API, so an attacker's site can act on behalf of any visiting user (authenticated API calls).
Root cause
feat: Implement post-quantum keyserver system with plugin architecture
Fix
security: Change CORS default from wildcard to empty list (GHSA-c65f-x25w-62jv)
AI-assisted fix: Claude Code · Tobi
Case facts
@@ -0,0 +1,105 @@+#!/usr/bin/env python3+"""+Pydantic Schemas++This module defines Pydantic schemas for API request/response validation.++SECURITY:+- Validates all input data+- Enforces algorithm whitelists+- Ensures required fields present+"""++from datetime import datetime+from typing import Optional++from pydantic import BaseModel, Field, field_validator+++class KeyBundleSchema(BaseModel):+ """+ Schema for public key bundle.++ This matches the PublicKeyBundle format from the client.+ """++ name: str = Field(..., min_length=1, max_length=255)+ email: Optional[str] = Field(None, max_length=255)+ fingerprint: str = Field(..., min_length=1, max_length=255)+ created_at: str = Field(..., description="ISO 8601 timestamp")++ encryption_public_key: str = Field(..., description="Base64-encoded public key")+ signing_public_key: str = Field(..., description="Base64-encoded public key")++ encryption_algorithm: str = Field(..., max_length=50)+ signing_algorithm: str = Field(..., max_length=50)++ self_signature: str = Field(..., description="Base64-encoded signature")++ @field_validator("encryption_algorithm")+ @classmethod+ def validate_encryption_algorithm(cls, v):+ """Validate encryption algorithm against whitelist."""+ allowed = ["ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"]+ if v not in allowed:+ raise ValueError(f"Invalid encryption algorithm. Allowed: {', '.join(allowed)}")+ return v++ @field_validator("signing_algorithm")+ @classmethod+ def validate_signing_algorithm(cls, v):+ """Validate signing algorithm against whitelist."""+ allowed = ["ML-DSA-44", "ML-DSA-65", "ML-DSA-87"]+ if v not in allowed:+ raise ValueError(f"Invalid signing algorithm. Allowed: {', '.join(allowed)}")+ return v++ class Config:+ json_schema_extra = {+ "example": {+ "name": "alice",+ "email": "[email protected]",+ "fingerprint": "3a:4b:5c:6d:7e:8f:...",+ "created_at": "2025-12-30T12:00:00Z",+ "encryption_public_key": "base64_encoded_key",+ "signing_public_key": "base64_encoded_key",+ "encryption_algorithm": "ML-KEM-768",+ "signing_algorithm": "ML-DSA-65",+ "self_signature": "base64_encoded_signature",+ }+ }+++class KeyUploadResponse(BaseModel):+ """Response for successful key upload."""++ success: bool = True+ fingerprint: str+ message: str = "Key uploaded successfully"+++class KeySearchResponse(BaseModel):+ """Response for key search."""++ key: Optional[KeyBundleSchema] = None+ message: Optional[str] = None+++class RevocationRequest(BaseModel):+ """Request for key revocation."""@@ -0,0 +1,147 @@+#!/usr/bin/env python3+"""+OpenSSL Encrypt Keyserver - Main Application++This is the main FastAPI application for the keyserver.++SECURITY:+- Bearer token authentication for uploads/revocations+- Public search endpoint (no auth)+- Signature verification for all uploaded keys+- CORS configuration+- Rate limiting++Features:+- Upload public keys (authenticated)+- Search for public keys (public)+- Revoke keys (authenticated)+- Health check endpoint+"""++import logging+from contextlib import asynccontextmanager++from fastapi import FastAPI+from fastapi.middleware.cors import CORSMiddleware++from .api.v1 import keys+from .config import settings+from .database import init_db++# Set up logging+logging.basicConfig(+ level=logging.INFO if not settings.debug else logging.DEBUG,+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",+)++logger = logging.getLogger(__name__)+++@asynccontextmanager+async def lifespan(app: FastAPI):+ """+ Lifespan event handler for FastAPI.++ Runs on startup and shutdown.+ """+ # Startup+ logger.info("Starting OpenSSL Encrypt Keyserver...")+ logger.info(f"Version: {settings.version}")+ logger.info(f"Debug mode: {settings.debug}")++ # Initialize database+ try:+ init_db()+ logger.info("Database initialized successfully")+ except Exception as e:+ logger.error(f"Failed to initialize database: {e}")+ raise++ # Check if liboqs is available+ try:+ import oqs++ logger.info("liboqs is available - signature verification enabled")+ except ImportError:+ logger.warning(+ "liboqs not available - signature verification will fail. "+ "Install with: pip install liboqs-python"+ )++ yield++ # Shutdown+ logger.info("Shutting down OpenSSL Encrypt Keyserver...")+++# Create FastAPI application+app = FastAPI(+ title=settings.app_name,+ version=settings.version,+ description="Post-quantum keyserver for public key distribution",+ lifespan=lifespan,+)++# Add CORS middleware+app.add_middleware(+ CORSMiddleware,+ allow_origins=settings.cors_origins,+ allow_credentials=True,@@ -0,0 +1,56 @@+#!/usr/bin/env python3+"""+Keyserver Configuration++This module handles server configuration with environment variable support.++SECURITY:+- Database credentials from environment variables+- API token validation configuration+- CORS settings for security+"""++import os+from typing import List++from pydantic_settings import BaseSettings+++class Settings(BaseSettings):+ """+ Server settings with environment variable support.++ All settings can be overridden via environment variables.+ Example: DATABASE_URL, API_TOKEN_SALT, etc.+ """++ # Application+ app_name: str = "OpenSSL Encrypt Keyserver"+ version: str = "1.0.0"+ debug: bool = False++ # Database+ database_url: str = os.getenv(+ "DATABASE_URL", "postgresql://keyserver:keyserver@localhost/keyserver"+ )++ # Security+ api_token_salt: str = os.getenv("API_TOKEN_SALT", "change-me-in-production")+ cors_origins: List[str] = ["*"] # Configure appropriately in production++ # Rate limiting+ rate_limit_enabled: bool = True+ rate_limit_requests_per_minute: int = 60+ rate_limit_upload_per_day: int = 10++ # Allowed algorithms (strict PQC only)+ allowed_kem_algorithms: List[str] = ["ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"]+ allowed_signing_algorithms: List[str] = ["ML-DSA-44", "ML-DSA-65", "ML-DSA-87"]++ class Config:+ env_file = ".env"+ case_sensitive = False+++# Global settings instance+settings = Settings()@@ -42,7 +42,7 @@ class Settings(BaseSettings): raw_data_retention_days: int = 90 # CORS (for public stats endpoint)- cors_origins: list = ["*"] # Configure for production+ cors_origins: list = [] # Set via CORS_ORIGINS env var for production class Config: env_file = ".env"@@ -58,6 +58,18 @@ def validate_settings(s: Settings) -> None: Raises: ValueError: If required settings are missing or insecure. """+ if "*" in s.cors_origins:+ if s.debug:+ logger.warning(+ "SECURITY WARNING: CORS wildcard '*' is active. "+ "This should only be used in debug mode."+ )+ else:+ raise ValueError(+ "CORS wildcard '*' is not allowed in production. "+ "Set CORS_ORIGINS to specific allowed origins."+ )+ if not s.database_url: if s.debug: logger.warning(@@ -35,7 +35,7 @@ class Settings(BaseSettings): # Security - MUST be set via environment variable (min 32 chars) api_token_secret: str = os.getenv("API_TOKEN_SECRET", "")- cors_origins: List[str] = ["*"] # Configure appropriately in production+ cors_origins: List[str] = [] # Set via CORS_ORIGINS env var for production # Rate limiting rate_limit_enabled: bool = True@@ -70,6 +70,18 @@ def validate_settings(s: Settings) -> None: "Example: DATABASE_URL=postgresql://user:pass@host/dbname" ) + if "*" in s.cors_origins:+ if s.debug:+ logger.warning(+ "SECURITY WARNING: CORS wildcard '*' is active. "+ "This should only be used in debug mode."+ )+ else:+ raise ValueError(+ "CORS wildcard '*' is not allowed in production. "+ "Set CORS_ORIGINS to specific allowed origins."+ )+ if not s.api_token_secret or len(s.api_token_secret) < 32: if s.debug: logger.warning(AI-assisted change 7c74ff7c371bb081d1b065822301e0d0d50c732ba979a739c3be3c47bf4a0a75 · Fix 5beb4c89a79cf40b694c7328076fe57ab3dc42969397f0eee40d484f5c09887e