fix: Allow HTTP for all private network ranges in Supabase URLs (#417)

* fix: Allow HTTP for all private network ranges in Supabase URLs

- Extend HTTP support to all RFC 1918 private IP ranges
- Class A: 10.0.0.0 to 10.255.255.255 (10.0.0.0/8)
- Class B: 172.16.0.0 to 172.31.255.255 (172.16.0.0/12)
- Class C: 192.168.0.0 to 192.168.255.255 (192.168.0.0/16)
- Also includes link-local (169.254.0.0/16) addresses
- Uses Python's ipaddress module for robust IP validation
- Maintains HTTPS requirement for public/production URLs
- Backwards compatible with existing localhost exceptions

* security: Fix URL validation vulnerabilities

- Replace substring matching with exact hostname matching to prevent bypass attacks
- Exclude unspecified address (0.0.0.0) from allowed HTTP hosts
- Add support for .localhost domains per RFC 6761
- Improve error messages with hostname context for better debugging

Addresses security concerns raised in PR review regarding:
- Malicious domains like 'localhost.attacker.com' bypassing HTTPS requirements
- Unspecified address being incorrectly allowed as valid connection target

---------

Co-authored-by: tazmon95 <tazmon95@users.noreply.github.com>
Co-authored-by: root <root@supatest2.jtpa.net>
This commit is contained in:
John C Fitzpatrick 2025-08-21 11:06:25 -07:00 committed by GitHub
parent 6f7c08eeb7
commit eb526af689
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -3,6 +3,7 @@ Environment configuration management for the MCP server.
""" """
import os import os
import ipaddress
from dataclasses import dataclass from dataclasses import dataclass
from urllib.parse import urlparse from urllib.parse import urlparse
@ -104,8 +105,29 @@ def validate_supabase_url(url: str) -> bool:
# Require HTTPS for production (non-local) URLs # Require HTTPS for production (non-local) URLs
if parsed.scheme == "http": if parsed.scheme == "http":
hostname = parsed.hostname or "" hostname = parsed.hostname or ""
if not any(local in hostname for local in ["localhost", "127.0.0.1", "host.docker.internal", "0.0.0.0"]):
raise ConfigurationError("Supabase URL must use HTTPS for non-local environments") # Check for exact localhost and Docker internal hosts (security: prevent subdomain bypass)
local_hosts = ["localhost", "127.0.0.1", "host.docker.internal"]
if hostname in local_hosts or hostname.endswith(".localhost"):
return True
# Check if hostname is a private IP address
try:
ip = ipaddress.ip_address(hostname)
# Allow HTTP for private IP addresses (RFC 1918)
# Class A: 10.0.0.0/8
# Class B: 172.16.0.0/12
# Class C: 192.168.0.0/16
# Also includes link-local (169.254.0.0/16) and loopback
# Exclude unspecified address (0.0.0.0) for security
if (ip.is_private or ip.is_loopback or ip.is_link_local) and not ip.is_unspecified:
return True
except ValueError:
# hostname is not a valid IP address, could be a domain name
pass
# If not a local host or private IP, require HTTPS
raise ConfigurationError(f"Supabase URL must use HTTPS for non-local environments (hostname: {hostname})")
if not parsed.netloc: if not parsed.netloc:
raise ConfigurationError("Invalid Supabase URL format") raise ConfigurationError("Invalid Supabase URL format")