From cfcffd38ab90c9daf6cb36a799cd8affb02929b8 Mon Sep 17 00:00:00 2001 From: Allegro Date: Mon, 30 Mar 2026 23:54:58 +0000 Subject: [PATCH] security: fix auth bypass and CORS misconfiguration (V-008, V-009) API Server security hardening: V-009 (CVSS 8.1) - Authentication Bypass Fix: - Changed default from allow-all to deny-all when no API key configured - Added explicit API_SERVER_ALLOW_UNAUTHENTICATED setting for local dev - Added warning logs for both secure and insecure configurations V-008 (CVSS 8.2) - CORS Misconfiguration Fix: - Reject wildcard '*' CORS origins (security vulnerability with credentials) - Require explicit origin configuration - Added warning log when wildcard detected Changes: - gateway/platforms/api_server.py: Hardened auth and CORS handling Refs: V-008, V-009 in SECURITY_AUDIT_REPORT.md CWE-287: Improper Authentication CWE-942: Permissive Cross-domain Policy --- gateway/platforms/api_server.py | 57 ++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/gateway/platforms/api_server.py b/gateway/platforms/api_server.py index 19fa5f60d..76a39b99a 100644 --- a/gateway/platforms/api_server.py +++ b/gateway/platforms/api_server.py @@ -292,7 +292,29 @@ class APIServerAdapter(BasePlatformAdapter): extra = config.extra or {} self._host: str = extra.get("host", os.getenv("API_SERVER_HOST", DEFAULT_HOST)) self._port: int = int(extra.get("port", os.getenv("API_SERVER_PORT", str(DEFAULT_PORT)))) + + # SECURITY FIX (V-009): Fail-secure default for API key + # Previously: Empty API key allowed all requests (dangerous default) + # Now: Require explicit "allow_unauthenticated" setting to disable auth self._api_key: str = extra.get("key", os.getenv("API_SERVER_KEY", "")) + self._allow_unauthenticated: bool = extra.get( + "allow_unauthenticated", + os.getenv("API_SERVER_ALLOW_UNAUTHENTICATED", "").lower() in ("true", "1", "yes") + ) + + # SECURITY: Log warning if no API key configured + if not self._api_key and not self._allow_unauthenticated: + logger.warning( + "API_SERVER_KEY not configured. All requests will be rejected. " + "Set API_SERVER_ALLOW_UNAUTHENTICATED=true for local-only use, " + "or configure API_SERVER_KEY for production." + ) + elif not self._api_key and self._allow_unauthenticated: + logger.warning( + "API_SERVER running without authentication. " + "This is only safe for local-only deployments." + ) + self._cors_origins: tuple[str, ...] = self._parse_cors_origins( extra.get("cors_origins", os.getenv("API_SERVER_CORS_ORIGINS", "")), ) @@ -317,15 +339,22 @@ class APIServerAdapter(BasePlatformAdapter): return tuple(str(item).strip() for item in items if str(item).strip()) def _cors_headers_for_origin(self, origin: str) -> Optional[Dict[str, str]]: - """Return CORS headers for an allowed browser origin.""" + """Return CORS headers for an allowed browser origin. + + SECURITY FIX (V-008): Never allow wildcard "*" with credentials. + If "*" is configured, we reject the request to prevent security issues. + """ if not origin or not self._cors_origins: return None + # SECURITY FIX (V-008): Reject wildcard CORS origins + # Wildcard with credentials is a security vulnerability if "*" in self._cors_origins: - headers = dict(_CORS_HEADERS) - headers["Access-Control-Allow-Origin"] = "*" - headers["Access-Control-Max-Age"] = "600" - return headers + logger.warning( + "CORS wildcard '*' is not allowed for security reasons. " + "Please configure specific origins in API_SERVER_CORS_ORIGINS." + ) + return None # Reject wildcard - too dangerous if origin not in self._cors_origins: return None @@ -355,10 +384,22 @@ class APIServerAdapter(BasePlatformAdapter): Validate Bearer token from Authorization header. Returns None if auth is OK, or a 401 web.Response on failure. - If no API key is configured, all requests are allowed. + + SECURITY FIX (V-009): Fail-secure default + - If no API key is configured AND allow_unauthenticated is not set, + all requests are rejected (secure by default) + - Only allow unauthenticated requests if explicitly configured """ - if not self._api_key: - return None # No key configured — allow all (local-only use) + # SECURITY: Fail-secure default - reject if no key and not explicitly allowed + if not self._api_key and not self._allow_unauthenticated: + return web.json_response( + {"error": {"message": "Authentication required. Configure API_SERVER_KEY or set API_SERVER_ALLOW_UNAUTHENTICATED=true for local development.", "type": "authentication_error", "code": "auth_required"}}, + status=401, + ) + + # Allow unauthenticated requests only if explicitly configured + if not self._api_key and self._allow_unauthenticated: + return None # Explicitly allowed for local-only use auth_header = request.headers.get("Authorization", "") if auth_header.startswith("Bearer "): -- 2.43.0