SSRF via model endpoint URL — no scheme/host validation on POST /model-endpoints
Prerequisites
- I searched open issues and discussions and did not find an existing report of this bug.
- This is not a security vulnerability. (Vulnerabilities go to GitHub Security Advisories — see SECURITY.md.)
- I am running the latest code from the
devbranch (the default branch you get on clone, where fixes land first) and the bug still reproduces there. Pleasegit pullthe latestdevbefore filing.
Odysseus Revision
3b6c16916233 (2026-09-14)
Install Method
Manual Python install (pip / venv)
Operating System
Windows
Steps to Reproduce
- Start Odysseus with default config
- Authenticate as admin
- POST /api/model-endpoints/test with body: base_url=http://169.254.169.254/latest/meta-data/
- Server probes the cloud metadata endpoint and returns model details
- Repeat with base_url=file:///etc/passwd, base_url=http://10.0.0.1:6379
- All probes are accepted — no validation applied
Expected Behaviour
URLs resolving to link-local IPs (169.254.169.254), non-HTTP schemes (file://, gopher://), or loopback addresses should be rejected with HTTP 400, matching the existing SSRF protection pattern used in embedding_routes.py, contacts_routes.py, gallery_routes.py, and webhook routes — all of which call check_outbound_url() from src/url_safety.py.
Actual Behaviour
Both POST /api/model-endpoints/test and POST /api/model-endpoints accept arbitrary user-supplied URLs and make HTTP requests to them via _probe_endpoint() with zero validation — no scheme check, no DNS resolution, no private-IP filtering. The probe succeeds and returns endpoint/model details from the target URL.
Logs / Screenshots
Vulnerability confirmed on unfixed dev branch (3b6c16916233):
Cloud metadata (169.254.169.254) → HTTP 200 (probe succeeds)
file:///etc/passwd → accepted, no rejection
Internal Redis (10.0.0.1:6379) → accepted, no rejection
Loopback (127.0.0.1) → accepted, no rejection
None return HTTP 400 — server accepts any URL and probes it.
AFTER PATCH (fix/model-endpoint-ssrf branch):
test_cloud_metadata_blocked PASSED
test_non_http_scheme_blocked PASSED
test_loopback_always_rejected PASSED
test_strict_mode_blocks_loopback PASSED
test_cloud_metadata_blocked (create) PASSED
test_non_http_scheme_blocked (create) PASSED
test_loopback_always_rejected (create) PASSED
7 passed in 2.00s
Model / Backend (if relevant)
No response
Are you willing to submit a fix?
Yes — I can open a PR
Additional Information
Every other route that accepts external URLs in the codebase already uses check_outbound_url(): embedding (line 266), contacts (line 68), gallery (lines 335, 1272, 1536), notes (line 455), and webhooks (line 108). The model endpoint routes are the only two exceptions. A fix with 7 regression tests is ready on branch fix/model-endpoint-ssrf.
Source: odysseus-dev/odysseus