From b48f2867acc7d144acb59ac98c9aa2f0d45f934a Mon Sep 17 00:00:00 2001 From: Diocrafts Date: Sun, 22 Feb 2026 22:40:55 +0100 Subject: [PATCH] security(P0): fail-closed when auth is enabled but cannot initialize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, if enable_auth=true but the database or auth services failed to initialize, the server silently started in PUBLIC mode — all API routes accessible without authentication. This is a critical fail-open bug. Fixes (fail-closed behavior): 1. main.rs: DB pool creation failure with enable_auth=true now panics instead of falling through with db_pool=None 2. di.rs: create_auth_services() failure now propagates the error via ? instead of logging and continuing with auth_services=None 3. main.rs: Added defensive assert! that auth_service is Some when enable_auth=true, preventing any future refactor from reintroducing the silent degradation The server will now refuse to start if authentication is configured but cannot be properly initialized. --- src/common/di.rs | 31 +++++++++++++++++++++---------- src/main.rs | 21 ++++++++++++++++++--- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/common/di.rs b/src/common/di.rs index 6b261b2d..9fb1f046 100644 --- a/src/common/di.rs +++ b/src/common/di.rs @@ -459,21 +459,32 @@ impl AppServiceFactory { // Auth services if self.config.features.enable_auth { - match crate::infrastructure::auth_factory::create_auth_services( + let services = crate::infrastructure::auth_factory::create_auth_services( &self.config, pool.clone(), Some(apps.folder_service_concrete.clone()), ) .await - { - Ok(services) => { - tracing::info!("Authentication services initialized successfully"); - auth_services = Some(services); - } - Err(e) => { - tracing::error!("Failed to initialize authentication services: {}", e); - } - } + .map_err(|e| { + // SECURITY: fail-closed. If auth is required but the auth + // services cannot be created, propagate the error so the + // server refuses to start — never degrade to public mode. + tracing::error!( + "FATAL: enable_auth=true but auth services failed to initialize: {}", + e + ); + DomainError::internal_error( + "AuthInit", + format!( + "Authentication is enabled but auth services failed: {}. \ + Refusing to start without authentication.", + e + ), + ) + })?; + + tracing::info!("Authentication services initialized successfully"); + auth_services = Some(services); } } diff --git a/src/main.rs b/src/main.rs index e24daa77..855b87c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,8 +72,13 @@ async fn main() -> Result<(), Box> { Some(Arc::new(pool)) } Err(e) => { - tracing::error!("Failed to initialize database pool: {}", e); - None + // SECURITY: fail-closed. If auth is required but the database + // is unreachable, the server MUST NOT start in public mode. + panic!( + "FATAL: enable_auth=true but database connection failed: {}. \ + Refusing to start without authentication.", + e + ); } } } else { @@ -134,7 +139,17 @@ async fn main() -> Result<(), Box> { }; // Apply auth middleware to protected API routes when auth is enabled - if config.features.enable_auth && app_state.auth_service.is_some() { + if config.features.enable_auth { + // SECURITY: if auth is required, auth_service MUST be present at this + // point. The earlier guards in di.rs and main.rs guarantee this, but + // add a defensive check so a future refactor cannot silently degrade. + assert!( + app_state.auth_service.is_some(), + "FATAL: enable_auth=true but auth_service is None. \ + This should have been caught during initialization." + ); + } + if config.features.enable_auth { use interfaces::api::handlers::auth_handler::auth_routes; use oxicloud::interfaces::middleware::auth::auth_middleware;