Spaces:
Paused
Paused
| """ | |
| Test URL validation for SSRF protection in API key configuration. | |
| Note: The validation is intentionally permissive for self-hosted scenarios. | |
| It only blocks: | |
| - Invalid schemes (must be http or https) | |
| - Malformed URLs | |
| - Link-local addresses (169.254.x.x) - used for cloud metadata endpoints | |
| Localhost and private IPs are ALLOWED because this is a self-hosted application | |
| where users commonly run local services (Ollama, LM Studio, etc.). | |
| """ | |
| import pytest | |
| from api.credentials_service import validate_url | |
| class TestUrlValidation: | |
| """Test suite for URL validation to prevent SSRF attacks.""" | |
| def test_valid_https_url(self): | |
| """Valid HTTPS URLs should pass.""" | |
| validate_url("https://api.openai.com", "openai") | |
| validate_url("https://example.com/api", "anthropic") | |
| # Should not raise | |
| def test_valid_http_url(self): | |
| """Valid HTTP URLs should pass.""" | |
| validate_url("http://example.com", "openai") | |
| # Should not raise | |
| def test_invalid_scheme(self): | |
| """URLs with invalid schemes should be rejected.""" | |
| with pytest.raises(ValueError, match="Invalid URL scheme"): | |
| validate_url("ftp://example.com", "openai") | |
| with pytest.raises(ValueError, match="Invalid URL scheme"): | |
| validate_url("file:///etc/passwd", "openai") | |
| def test_localhost_allowed_for_self_hosted(self): | |
| """Localhost should be allowed for self-hosted services.""" | |
| # This is a self-hosted app, localhost is valid for local services | |
| validate_url("http://localhost:8000", "openai") | |
| validate_url("http://127.0.0.1:8000", "azure") | |
| # Should not raise | |
| def test_localhost_allowed_for_ollama(self): | |
| """Localhost should be allowed for Ollama provider.""" | |
| validate_url("http://localhost:11434", "ollama") | |
| validate_url("http://127.0.0.1:11434", "ollama") | |
| # Should not raise | |
| def test_private_ip_allowed_for_self_hosted(self): | |
| """Private IP addresses should be allowed for self-hosted scenarios.""" | |
| # This is a self-hosted app, private IPs are valid for internal services | |
| validate_url("http://10.0.0.1", "openai") | |
| validate_url("http://172.16.0.1:8080", "anthropic") | |
| validate_url("http://192.168.1.1", "azure") | |
| # Should not raise | |
| def test_private_ip_allowed_for_ollama(self): | |
| """Private IP addresses should be allowed for Ollama provider.""" | |
| validate_url("http://192.168.1.100:11434", "ollama") | |
| validate_url("http://10.0.0.50:11434", "ollama") | |
| # Should not raise | |
| def test_loopback_allowed_for_self_hosted(self): | |
| """Loopback addresses should be allowed for self-hosted scenarios.""" | |
| validate_url("http://127.0.0.2", "openai") | |
| # Should not raise | |
| def test_link_local_rejection(self): | |
| """Link-local addresses should be rejected (cloud metadata protection).""" | |
| with pytest.raises(ValueError, match="Link-local addresses"): | |
| validate_url("http://169.254.169.254", "openai") | |
| # Also reject for ollama - link-local is never valid | |
| with pytest.raises(ValueError, match="Link-local addresses"): | |
| validate_url("http://169.254.169.254", "ollama") | |
| def test_ipv6_localhost_allowed(self): | |
| """IPv6 localhost should be allowed for self-hosted scenarios.""" | |
| validate_url("http://[::1]:8000", "openai") | |
| # Should not raise | |
| def test_empty_url(self): | |
| """Empty URLs should not raise (handled elsewhere).""" | |
| validate_url("", "openai") | |
| # None is handled by the function's early return check | |
| # Should not raise | |
| def test_invalid_url_format(self): | |
| """Malformed URLs should be rejected.""" | |
| with pytest.raises(ValueError): | |
| validate_url("not-a-url", "openai") | |
| def test_public_hostnames_allowed(self): | |
| """Public hostnames should be allowed.""" | |
| validate_url("https://api.openai.com/v1", "openai") | |
| validate_url("https://api.anthropic.com", "anthropic") | |
| validate_url("https://generativelanguage.googleapis.com", "google") | |
| validate_url("https://api.groq.com", "groq") | |
| # Should not raise | |
| def test_azure_specific_urls(self): | |
| """Azure OpenAI endpoints should be validated.""" | |
| validate_url( | |
| "https://my-resource.openai.azure.com", "azure" | |
| ) | |
| # Localhost is allowed for self-hosted | |
| validate_url("http://localhost:8000", "azure") | |
| # Should not raise | |
| def test_openai_compatible_urls(self): | |
| """OpenAI-compatible provider URLs should be validated.""" | |
| validate_url("https://api.together.xyz", "openai_compatible") | |
| # Private IPs are allowed for self-hosted | |
| validate_url("http://192.168.1.1:8080", "openai_compatible") | |
| # Should not raise | |
| def test_ipv4_mapped_ipv6_link_local_rejected(self): | |
| """IPv4-mapped IPv6 addresses pointing to link-local should be rejected.""" | |
| with pytest.raises(ValueError, match="Link-local addresses"): | |
| validate_url("http://[::ffff:169.254.169.254]", "openai") | |
| def test_ipv4_mapped_ipv6_private_allowed(self): | |
| """IPv4-mapped IPv6 addresses pointing to private IPs should be allowed.""" | |
| validate_url("http://[::ffff:192.168.1.1]", "openai") | |
| # Should not raise - private IPs allowed for self-hosted | |