mirror of https://github.com/razor-ai/soup.git
fix(security): harden chat proxy SSRF with ipaddress.is_loopback validation
Replace string-based hostname allowlist with ipaddress.ip_address().is_loopback to properly handle 127.x.x.x range and IPv6 loopback. Blocks private/link-local addresses (192.168.x.x, 10.x.x.x, ::ffff:127.0.0.1) for HTTP endpoints. Adds 2 tests: reject private IP, allow 127.0.0.2 loopback.
This commit is contained in:
parent
69c4ee6a29
commit
b90f6e0c7a
|
|
@ -614,8 +614,17 @@ def create_app(host: str = "127.0.0.1", port: int = 7860):
|
|||
# SSRF protection: localhost-only HTTP, HTTPS for remote
|
||||
parsed = urlparse(req.endpoint)
|
||||
if parsed.scheme == "http":
|
||||
import ipaddress as _ipaddr
|
||||
|
||||
host = parsed.hostname or ""
|
||||
if host not in ("localhost", "127.0.0.1", "0.0.0.0", "::1"):
|
||||
is_local = host in ("localhost", "0.0.0.0")
|
||||
if not is_local:
|
||||
try:
|
||||
addr = _ipaddr.ip_address(host)
|
||||
is_local = addr.is_loopback
|
||||
except ValueError:
|
||||
is_local = False
|
||||
if not is_local:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="HTTP only allowed for localhost endpoints",
|
||||
|
|
|
|||
|
|
@ -186,6 +186,47 @@ class TestChatInvalidScheme:
|
|||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_rejects_ipv6_mapped_loopback_alias(self):
|
||||
"""Chat endpoint should reject IPv6-mapped private addresses."""
|
||||
try:
|
||||
from fastapi.testclient import TestClient
|
||||
except ImportError:
|
||||
pytest.skip("FastAPI not installed")
|
||||
|
||||
from soup_cli.ui.app import create_app
|
||||
|
||||
client = TestClient(create_app())
|
||||
response = client.post(
|
||||
"/api/chat/send",
|
||||
json={
|
||||
"messages": [{"role": "user", "content": "hi"}],
|
||||
"endpoint": "http://192.168.1.1:8000",
|
||||
},
|
||||
headers=_auth_headers(),
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_allows_127_loopback_range(self):
|
||||
"""Chat endpoint should allow 127.x.x.x loopback addresses."""
|
||||
try:
|
||||
from fastapi.testclient import TestClient
|
||||
except ImportError:
|
||||
pytest.skip("FastAPI not installed")
|
||||
|
||||
from soup_cli.ui.app import create_app
|
||||
|
||||
client = TestClient(create_app())
|
||||
response = client.post(
|
||||
"/api/chat/send",
|
||||
json={
|
||||
"messages": [{"role": "user", "content": "hi"}],
|
||||
"endpoint": "http://127.0.0.2:8000",
|
||||
},
|
||||
headers=_auth_headers(),
|
||||
)
|
||||
# 127.0.0.2 is loopback — should NOT be rejected as SSRF
|
||||
assert response.status_code != 400
|
||||
|
||||
def test_rejects_file_scheme(self):
|
||||
"""Chat endpoint should reject file:// URLs."""
|
||||
try:
|
||||
|
|
|
|||
Loading…
Reference in New Issue