fix: remove SSRF protection for Community Edition local deployments

Community Edition runs entirely locally, so SSRF protection against
private IPs is unnecessary and blocks legitimate use cases like:
- Ollama on localhost:11434
- vLLM on 192.168.x.x
- Other local model servers

Enterprise Edition should re-enable SSRF protection for multi-tenant
cloud deployments where it provides security value.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
HackWeasel
2025-12-15 16:18:24 -05:00
parent 676d48ea1f
commit dc884df271

View File

@@ -12,7 +12,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
import logging import logging
import re import re
import ipaddress
from urllib.parse import urlparse from urllib.parse import urlparse
from app.core.database import get_db from app.core.database import get_db
@@ -23,65 +22,10 @@ logger = logging.getLogger(__name__)
router = APIRouter(prefix="/models", tags=["Model Management"]) router = APIRouter(prefix="/models", tags=["Model Management"])
def is_private_ip(url: str) -> bool: # NOTE: SSRF protection removed for Community Edition
""" # Community Edition runs locally, so private IP access is legitimate
Check if URL points to a private/internal IP address. # (e.g., Ollama on localhost:11434, vLLM on 192.168.x.x, etc.)
# Enterprise Edition should re-enable SSRF protection for multi-tenant deployments
SSRF Protection: Prevents requests to private networks (RFC1918),
localhost, loopback, and other reserved IP ranges.
Also resolves hostnames to check if they point to private IPs.
Exception: Docker networking hostnames (host.docker.internal, ollama-host)
are allowed for Community Edition local deployments where services
need to reach the host machine from within containers.
"""
import socket
# Docker networking hostnames allowed for local model access (Ollama, vLLM, etc.)
# These only work inside Docker containers and are explicitly configured in docker-compose
DOCKER_ALLOWED_HOSTS = {
'host.docker.internal', # Docker's standard for reaching host (macOS/Windows/Linux)
'ollama-host', # Custom alias for Ollama defined in docker-compose
'gateway.docker.internal', # Docker gateway (sometimes used)
}
try:
parsed = urlparse(url)
hostname = parsed.hostname
if not hostname:
return True
# Allow Docker networking hostnames for local model access
if hostname.lower() in DOCKER_ALLOWED_HOSTS:
return False
# Check for localhost variants
if hostname in ('localhost', '127.0.0.1', '::1', '0.0.0.0', '0', 'localhost.localdomain'):
return True
# Check RFC1918 and other private ranges
try:
ip = ipaddress.ip_address(hostname)
return ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local
except ValueError:
# It's a hostname, not an IP - resolve it and check
# This prevents DNS rebinding attacks
try:
resolved_ips = socket.getaddrinfo(hostname, None, socket.AF_UNSPEC, socket.SOCK_STREAM)
for family, _, _, _, sockaddr in resolved_ips:
ip_str = sockaddr[0]
try:
ip = ipaddress.ip_address(ip_str)
if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local:
return True
except ValueError:
continue
return False
except socket.gaierror:
# DNS resolution failed - block to be safe
return True
except Exception:
return True
@@ -302,15 +246,6 @@ async def test_endpoint_url(
error_type="invalid_format" error_type="invalid_format"
) )
# SSRF Protection: Block requests to private/internal IP addresses
if is_private_ip(request.endpoint):
return HealthCheckResponse(
healthy=False,
status="unhealthy",
error="Access to private/internal IP addresses is not allowed",
error_type="invalid_endpoint"
)
# Determine test URL based on provider # Determine test URL based on provider
base_endpoint = request.endpoint.rstrip('/') base_endpoint = request.endpoint.rstrip('/')
if request.provider and request.provider in PROVIDER_HEALTH_ENDPOINTS: if request.provider and request.provider in PROVIDER_HEALTH_ENDPOINTS:
@@ -321,7 +256,7 @@ async def test_endpoint_url(
async with httpx.AsyncClient(timeout=10.0, follow_redirects=False) as client: async with httpx.AsyncClient(timeout=10.0, follow_redirects=False) as client:
try: try:
# codeql[py/full-ssrf] URL validated by is_private_ip() check at line 290 # Community Edition: SSRF check removed - private IPs allowed for local deployments
response = await client.get(test_url) response = await client.get(test_url)
latency_ms = (time.time() - start_time) * 1000 latency_ms = (time.time() - start_time) * 1000