From 355a2356d4c084499f7e6796786dcb6600aec9c9 Mon Sep 17 00:00:00 2001 From: L1nSn0w Date: Mon, 15 Dec 2025 11:24:06 +0800 Subject: [PATCH] security/fix-swagger-info-leak-m02 (#29283) Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> --- api/.env.example | 12 +++++++++++- api/configs/feature/__init__.py | 33 ++++++++++++++++++++++++++++++--- api/extensions/ext_login.py | 4 ++-- api/libs/external_api.py | 20 ++++++++++++++++++-- 4 files changed, 61 insertions(+), 8 deletions(-) diff --git a/api/.env.example b/api/.env.example index 516a119d98..fb92199893 100644 --- a/api/.env.example +++ b/api/.env.example @@ -626,7 +626,17 @@ QUEUE_MONITOR_ALERT_EMAILS= QUEUE_MONITOR_INTERVAL=30 # Swagger UI configuration -SWAGGER_UI_ENABLED=true +# SECURITY: Swagger UI is automatically disabled in PRODUCTION environment (DEPLOY_ENV=PRODUCTION) +# to prevent API information disclosure. +# +# Behavior: +# - DEPLOY_ENV=PRODUCTION + SWAGGER_UI_ENABLED not set -> Swagger DISABLED (secure default) +# - DEPLOY_ENV=DEVELOPMENT/TESTING + SWAGGER_UI_ENABLED not set -> Swagger ENABLED +# - SWAGGER_UI_ENABLED=true -> Swagger ENABLED (overrides environment check) +# - SWAGGER_UI_ENABLED=false -> Swagger DISABLED (explicit disable) +# +# For development, you can uncomment below or set DEPLOY_ENV=DEVELOPMENT +# SWAGGER_UI_ENABLED=false SWAGGER_UI_PATH=/swagger-ui.html # Whether to encrypt dataset IDs when exporting DSL files (default: true) diff --git a/api/configs/feature/__init__.py b/api/configs/feature/__init__.py index a5916241df..5c0edb60ac 100644 --- a/api/configs/feature/__init__.py +++ b/api/configs/feature/__init__.py @@ -1221,9 +1221,19 @@ class WorkflowLogConfig(BaseSettings): class SwaggerUIConfig(BaseSettings): - SWAGGER_UI_ENABLED: bool = Field( - description="Whether to enable Swagger UI in api module", - default=True, + """ + Configuration for Swagger UI documentation. + + Security Note: Swagger UI is automatically disabled in PRODUCTION environment + to prevent API information disclosure. Set SWAGGER_UI_ENABLED=true explicitly + to enable in production if needed. + """ + + SWAGGER_UI_ENABLED: bool | None = Field( + description="Whether to enable Swagger UI in api module. " + "Automatically disabled in PRODUCTION environment for security. " + "Set to true explicitly to enable in production.", + default=None, ) SWAGGER_UI_PATH: str = Field( @@ -1231,6 +1241,23 @@ class SwaggerUIConfig(BaseSettings): default="/swagger-ui.html", ) + @property + def swagger_ui_enabled(self) -> bool: + """ + Compute whether Swagger UI should be enabled. + + If SWAGGER_UI_ENABLED is explicitly set, use that value. + Otherwise, disable in PRODUCTION environment for security. + """ + if self.SWAGGER_UI_ENABLED is not None: + return self.SWAGGER_UI_ENABLED + + # Auto-disable in production environment + import os + + deploy_env = os.environ.get("DEPLOY_ENV", "PRODUCTION") + return deploy_env.upper() != "PRODUCTION" + class TenantIsolatedTaskQueueConfig(BaseSettings): TENANT_ISOLATED_TASK_CONCURRENCY: int = Field( diff --git a/api/extensions/ext_login.py b/api/extensions/ext_login.py index 74299956c0..5cbdd4db12 100644 --- a/api/extensions/ext_login.py +++ b/api/extensions/ext_login.py @@ -22,8 +22,8 @@ login_manager = flask_login.LoginManager() @login_manager.request_loader def load_user_from_request(request_from_flask_login): """Load user based on the request.""" - # Skip authentication for documentation endpoints - if dify_config.SWAGGER_UI_ENABLED and request.path.endswith((dify_config.SWAGGER_UI_PATH, "/swagger.json")): + # Skip authentication for documentation endpoints (only when Swagger is enabled) + if dify_config.swagger_ui_enabled and request.path.endswith((dify_config.SWAGGER_UI_PATH, "/swagger.json")): return None auth_token = extract_access_token(request) diff --git a/api/libs/external_api.py b/api/libs/external_api.py index 61a90ee4a9..31ca2b3e08 100644 --- a/api/libs/external_api.py +++ b/api/libs/external_api.py @@ -131,12 +131,28 @@ class ExternalApi(Api): } def __init__(self, app: Blueprint | Flask, *args, **kwargs): + import logging + import os + kwargs.setdefault("authorizations", self._authorizations) kwargs.setdefault("security", "Bearer") - kwargs["add_specs"] = dify_config.SWAGGER_UI_ENABLED - kwargs["doc"] = dify_config.SWAGGER_UI_PATH if dify_config.SWAGGER_UI_ENABLED else False + + # Security: Use computed swagger_ui_enabled which respects DEPLOY_ENV + swagger_enabled = dify_config.swagger_ui_enabled + kwargs["add_specs"] = swagger_enabled + kwargs["doc"] = dify_config.SWAGGER_UI_PATH if swagger_enabled else False # manual separate call on construction and init_app to ensure configs in kwargs effective super().__init__(app=None, *args, **kwargs) self.init_app(app, **kwargs) register_external_error_handlers(self) + + # Security: Log warning when Swagger is enabled in production environment + deploy_env = os.environ.get("DEPLOY_ENV", "PRODUCTION") + if swagger_enabled and deploy_env.upper() == "PRODUCTION": + logger = logging.getLogger(__name__) + logger.warning( + "SECURITY WARNING: Swagger UI is ENABLED in PRODUCTION environment. " + "This may expose sensitive API documentation. " + "Set SWAGGER_UI_ENABLED=false or remove the explicit setting to disable." + )