fix: sanitize remote media safety errors#490
Conversation
janhilgard
left a comment
There was a problem hiding this comment.
Good SSRF hardening. The key insight — validation must happen before any SSE data is emitted — is correctly implemented.
UnsafeRemoteURLError.public_message — clean separation between internal diagnostics (IP, hostname, reason) and client-facing message. The test explicitly verifies 169.254.169.254 appears in the internal error but NOT in public_message — exactly the right security boundary assertion.
Early validation placement:
_prepare_chat_messages()→ catches both streaming and non-streaming chat completions before any response bytes are sentcreate_response()streaming path →_validate_remote_media_urls(chat_request.messages)runs beforeStreamingResponse()constructor_prepare_responses_request()→ catches non-streaming Responses path
All three entry points validate before the point of no return (SSE start or response body).
_raise_remote_media_http_error — logs full details server-side via logger.warning, returns only the generic public_message via HTTPException(status_code=400). No information leakage to the client.
_iter_remote_media_urls — handles all multimodal content types (image_url, video_url, audio_url, image, video, audio) plus Pydantic model objects. The is_url() filter correctly skips base64 data URIs and local paths that don't need SSRF validation.
Documentation — honest about the DNS rebinding / split-horizon limitation. Recommending egress controls or a trusted proxy is the right advice rather than claiming complete protection.
Endpoint test — the AWS metadata endpoint (169.254.169.254) is the classic SSRF target, good choice. The assertion "169.254.169.254" not in response.text verifies no internal detail leaks in the full response body.
Single commit, CI green. LGTM.
|
The Anthropic # vllm_mlx/server.py:4900-4906 (current PR head)
release_on_exit = True
prepared = _prepare_anthropic_invocation( # raises UnsafeRemoteURLError
engine,
openai_request,
effective_max_tokens,
)Lines 4895 to 4912 in d486a8d The fix is the same pattern already used twice in this PR: try:
prepared = _prepare_anthropic_invocation(
engine,
openai_request,
effective_max_tokens,
)
except UnsafeRemoteURLError as exc:
_raise_remote_media_http_error(exc)Today this is partly masked because The second thing worth flagging: the # vllm_mlx/server.py:4692-4699
try:
if request.stream:
chat_request = _responses_request_to_chat_request(request)
_validate_remote_media_urls(chat_request.messages)
return StreamingResponse(
_disconnect_guard(_stream_responses_request(request), raw_request),
media_type="text/event-stream",
)Lines 4690 to 4708 in d486a8d And once again, lazily, inside the generator via # vllm_mlx/server.py:2034-2046
_validate_model_name(request.model)
engine = get_engine()
chat_request = _responses_request_to_chat_request(request)
...
_validate_remote_media_urls(chat_request.messages)Lines 2030 to 2048 in d486a8d The second call runs after
Pick one site. Either drop the call inside There's also a third item worth noting but not blocking: the Responses API path runs Lines 1647 to 1670 in d486a8d |
|
@waybarrios I addressed the follow-ups you flagged and pushed Changes:
Validation:
CI is green on the pushed commit as well. |
|
Ok this looks better now. You got my approval! |
Closes #488.
This is a narrow follow-up for the SSRF review notes on remote media handling. It does not attempt to implement pinned-IP transport; instead it makes the current behavior explicit and removes the client-visible leakage from URL-safety failures.
Changes:
UnsafeRemoteURLError.public_messageValidation:
AI_RUNTIME_BYPASS_SAFETY_GATE=1 /opt/ai-runtime/venv-live/bin/python -m pytest tests/test_mllm.py tests/test_server.py -q -k 'unsafe_remote_url_error_has_safe_public_message or validate_url_safety or request_with_safe_redirects or blocks_unsafe_url_before_request or chat_completion_sanitizes_remote_media_safety_errors'AI_RUNTIME_BYPASS_SAFETY_GATE=1 /opt/ai-runtime/venv-live/bin/python -m pytest tests/test_mllm.py -qAI_RUNTIME_BYPASS_SAFETY_GATE=1 /opt/ai-runtime/venv-live/bin/python -m pytest tests/test_server.py -qAI_RUNTIME_BYPASS_SAFETY_GATE=1 /opt/ai-runtime/venv-live/bin/python -m compileall -q vllm_mlx/models/mllm.py vllm_mlx/server.pyblack --target-version py312 --check vllm_mlx/models/mllm.py vllm_mlx/server.py tests/test_mllm.py tests/test_server.pyuvx ruff check vllm_mlx/models/mllm.py vllm_mlx/server.py tests/test_mllm.py tests/test_server.py --select E,F,W --ignore E402,E501,E731,F811,F841git diff --check