Skip to content

Security: SSRF via user-controlled baseUrl in AI proxy endpoints #393

@CrepuscularIRIS

Description

@CrepuscularIRIS

Bug Description

The /api/ai/test and /api/ai/chat endpoints accept a baseUrl field from the request body and use it directly to construct outbound HTTP requests, with no validation of the destination host. This allows any caller to make the UltraRAG server issue requests to arbitrary internal or external URLs.

Location

ui/backend/app.py, /api/ai/test handler (~line 2360):

base_url = payload.get("baseUrl", "").rstrip("/")
test_url = f"{base_url}/models"
resp = requests.get(test_url, headers=headers, timeout=10)

ui/backend/app.py, /api/ai/chat handler (~line 2510):

base_url = settings.get("baseUrl", "").rstrip("/")
chat_url = f"{base_url}/chat/completions"
resp = requests.post(chat_url, headers=headers, ...)

Reproduction

curl -X POST http://<ultrarag-host>/api/ai/test \
  -H "Content-Type: application/json" \
  -d '{"provider":"openai","baseUrl":"http://169.254.169.254/latest","apiKey":"x","model":"m"}'

The server will issue GET http://169.254.169.254/latest/models, reaching the cloud instance metadata service or any internal endpoint.

Impact

Attackers can use the UltraRAG server as an SSRF proxy to enumerate and access internal services, cloud metadata endpoints (AWS/GCP/Azure IMDS), and other hosts unreachable from the public internet.

Suggested Fix

Validate baseUrl against a strict allowlist of permitted AI provider domains, and reject private/loopback/link-local addresses after DNS resolution before making any outbound request:

from urllib.parse import urlparse
import ipaddress

ALLOWED_HOSTS = {"api.openai.com", "api.anthropic.com", ...}
parsed = urlparse(base_url)
if parsed.hostname not in ALLOWED_HOSTS:
    return jsonify({"success": False, "error": "baseUrl not permitted"})

Found via automated codebase analysis. Happy to submit a PR if this is confirmed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions