Root cause
Authentication & access control introduced
feat: Add unified FastAPI server with JWT authentication and modular architecture
How AI contributed: Direct introductionPublished Feb 10, 2026 in
jahlives/openssl_encryptAll 20 findings in this repository
openssl_encrypt versions before 1.4.0 contain an information disclosure vulnerability in the /ready endpoint that returns full database exception strings to unauthenticated callers. Attackers can trigger database errors to extract sensitive information including hostnames, IP addresses, connection parameters, and potentially credentials from exception messages.
The FastAPI GET /ready route performs an SQLAlchemy connection/query check without authentication and catches every resulting exception, then places str(e) directly in the JSON response's reason field. A remote unauthenticated caller that causes or observes a database failure can therefore receive driver and connection details, including database hostnames, IP addresses, ports, parameters, versions, and potentially credentials embedded in an error.
Root cause
feat: Add unified FastAPI server with JWT authentication and modular architecture
Fix
security: Stop leaking DB errors in readiness endpoint (GHSA-2vhw-q7vh-7xv2)
Case facts
@@ -0,0 +1,198 @@+#!/usr/bin/env python3+"""+OpenSSL Encrypt Server - Main Entry Point++Unified FastAPI server for Keyserver and Telemetry modules.+"""++import logging+from contextlib import asynccontextmanager++from fastapi import FastAPI+from fastapi.middleware.cors import CORSMiddleware+import uvicorn++from .config import Settings, settings, validate_config+from .core.database import close_db, init_db, init_engine+from .core.exceptions import ServerException, server_exception_handler+from .modules import load_modules++# Configure logging+logging.basicConfig(+ level=getattr(logging, settings.log_level.upper()),+ 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("=" * 60)+ logger.info(f"Starting {settings.app_name}...")+ logger.info(f"Version: {settings.version}")+ logger.info(f"Debug mode: {settings.debug}")+ logger.info("=" * 60)++ # Validate configuration+ try:+ validate_config(settings)+ logger.info("Configuration validated successfully")+ except ValueError as e:+ logger.error(f"Configuration error: {e}")+ raise++ # Import all models (must be done before init_db)+ if settings.keyserver_enabled:+ from .modules.keyserver import models as ks_models # noqa: F401+ if settings.telemetry_enabled:+ from .modules.telemetry import models as tm_models # noqa: F401++ # Initialize database+ try:+ database_url = settings.get_database_url()+ init_engine(database_url)+ await 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 - PQC signature verification enabled")+ except ImportError:+ logger.warning(+ "liboqs not available - keyserver signature verification will fail. "+ "Install with: pip install liboqs-python"+ )++ # Load enabled modules+ try:+ loaded_modules = load_modules(app, settings)+ logger.info(f"Loaded modules: {', '.join(loaded_modules)}")+ except Exception as e:+ logger.error(f"Failed to load modules: {e}")+ raise++ logger.info("=" * 60)+ logger.info(f"Server started on {settings.server_host}:{settings.server_port}")+ logger.info("=" * 60)+@@ -0,0 +1,84 @@+#!/usr/bin/env python3+"""+Telemetry Pydantic Schemas++Request/response validation for telemetry endpoints.+"""++from datetime import datetime+from typing import Dict, List, Optional++from pydantic import BaseModel, Field+++class RegisterResponse(BaseModel):+ """Response for client registration"""++ client_id: str+ token: str+ expires_at: str # ISO 8601 datetime+ token_type: str = "Bearer"+++class TelemetryEventSchema(BaseModel):+ """Single telemetry event"""++ timestamp: str = Field(..., description="Event timestamp (ISO 8601)")+ operation: str = Field(..., description="Operation: encrypt or decrypt")+ mode: str = Field(..., description="Mode: symmetric or asymmetric")+ format_version: int = Field(..., description="File format version (4-8)")++ hash_algorithms: List[str] = Field(..., description="Hash algorithms used")+ kdf_algorithms: List[str] = Field(..., description="KDF algorithms used")+ kdf_parameters: Optional[Dict[str, Dict[str, int]]] = Field(+ default=None, description="KDF parameters"+ )++ encryption_algorithm: str = Field(..., description="Encryption algorithm")++ cascade_enabled: bool = Field(default=False, description="Cascade encryption enabled")+ cascade_cipher_count: Optional[int] = Field(+ default=None, description="Number of cascade ciphers"+ )++ pqc_kem_algorithm: Optional[str] = Field(default=None, description="PQC KEM algorithm")+ pqc_signing_algorithm: Optional[str] = Field(+ default=None, description="PQC signing algorithm"+ )++ hsm_plugin_used: Optional[str] = Field(default=None, description="HSM plugin name")++ success: bool = Field(default=True, description="Operation success")+ error_category: Optional[str] = Field(default=None, description="Error category if failed")+++class TelemetryBatchRequest(BaseModel):+ """Batch telemetry upload request"""++ events: List[TelemetryEventSchema] = Field(+ ..., min_length=1, max_length=1000, description="List of telemetry events (max 1000)"+ )+++class TelemetryBatchResponse(BaseModel):+ """Batch telemetry upload response"""++ received: int = Field(..., description="Number of events received")+ processed: int = Field(..., description="Number of events successfully processed")+++class StatsResponse(BaseModel):+ """Public statistics response"""++ total_operations: int+ total_clients: int+ algorithms: Dict[str, int]+ operations: Dict[str, int]+ success_rate: float+++class ErrorResponse(BaseModel):+ """Standard error response"""++ detail: str+ success: bool = False@@ -0,0 +1,124 @@+#!/usr/bin/env python3+"""+Telemetry API Routes++Endpoints:+- POST /api/v1/telemetry/register - Register new client (no auth)+- POST /api/v1/telemetry/events - Submit events (auth required)+- GET /api/v1/telemetry/stats - Get public statistics (no auth)+"""++import logging++from fastapi import APIRouter, Depends, Request, Security, status+from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer+from sqlalchemy.ext.asyncio import AsyncSession++from ...core.database import get_db+from .auth import get_telemetry_auth+from .schemas import (+ ErrorResponse,+ RegisterResponse,+ StatsResponse,+ TelemetryBatchRequest,+ TelemetryBatchResponse,+)+from .service import TelemetryService++logger = logging.getLogger(__name__)++router = APIRouter(prefix="/api/v1/telemetry", tags=["telemetry"])++security = HTTPBearer()+++# Dependency that lazily gets the auth instance+async def get_current_client(+ request: Request,+ credentials: HTTPAuthorizationCredentials = Security(security),+) -> str:+ """Get current authenticated client ID"""+ auth = get_telemetry_auth()+ payload = auth.verify_token(credentials.credentials)++ # Update last seen (fire and forget)+ try:+ await auth.update_last_seen(payload.sub)+ except Exception:+ pass++ return payload.sub++[email protected](+ "/register",+ response_model=RegisterResponse,+ status_code=status.HTTP_200_OK,+ summary="Register new telemetry client",+)+async def register():+ """+ Register a new Telemetry client.++ Returns a JWT token that can ONLY be used for Telemetry endpoints.+ The token includes an issuer claim that prevents cross-module usage.++ Returns:+ RegisterResponse: Client ID, JWT token, expiration+ """+ auth = get_telemetry_auth()+ return await auth.register_client()++[email protected](+ "/events",+ response_model=TelemetryBatchResponse,+ status_code=status.HTTP_200_OK,+ responses={+ 401: {"model": ErrorResponse, "description": "Authentication required"},+ 400: {"model": ErrorResponse, "description": "Invalid events"},+ },+ summary="Submit telemetry events",+)+async def submit_events(+ request: TelemetryBatchRequest,+ db: AsyncSession = Depends(get_db),+ client_id: str = Depends(get_current_client),+):+ """+ Submit telemetry events.@@ -172,7 +172,8 @@ async def readiness_check(): return {"status": "ready"} except Exception as e:- return {"status": "not_ready", "reason": str(e)}+ logger.warning("Readiness check failed: %s", e)+ return {"status": "not_ready", "reason": "database check failed"} @app.get("/info") async def server_info():AI-assisted change a7d5c0d64ec62c77713cfa90479663a0447a494086724c14f0aff6259838996a · Fix 3f600bf331712664e9de80cda7ef5d052c5482d124a22f0e74dc296cd00b42a1