[fix] plugin: ignore credentials in a user configured LLM server URL
httpx derives an "Authorization: Basic" header from the userinfo of a URL, so a user could make SearXNG send a header of their choosing to a host of their choosing (e.g. to probe an internal service behind basic auth). A user preference carrying credentials is now ignored and the administrator default is used instead; credentials in the configured base_url are untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -152,7 +152,15 @@ def _server_api_key(cfg: SettingsAISummary, server: str) -> str:
|
||||
def _user_server(request: "SXNG_Request", cfg: SettingsAISummary) -> str:
|
||||
"""The LLM server URL for this request: the user's ``ai_summary_server``
|
||||
preference, or the administrator's default."""
|
||||
return str(request.preferences.get_value("ai_summary_server") or "").strip() or cfg.base_url
|
||||
server = str(request.preferences.get_value("ai_summary_server") or "").strip()
|
||||
# Credentials are stripped from a user's server URL: httpx turns them into
|
||||
# an Authorization header, and a user should not be able to make SearXNG
|
||||
# send a header of their choosing to a host of their choosing. An
|
||||
# administrator can still use credentials in the configured base_url (e.g.
|
||||
# an LLM server behind basic auth).
|
||||
if server and "@" in urlparse(server).netloc:
|
||||
server = ""
|
||||
return server or cfg.base_url
|
||||
|
||||
|
||||
class SXNGPlugin(Plugin):
|
||||
|
||||
@@ -349,6 +349,17 @@ class PluginAISummary(SearxTestCase):
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual([("http://untrusted.example.org:11434", "")], calls)
|
||||
|
||||
def test_endpoint_ignores_credentials_in_user_server(self):
|
||||
# httpx would turn the userinfo into an Authorization header; the
|
||||
# preference is ignored and the admin's server is used instead
|
||||
self.setattr4test(searx.get_setting("ai_summary"), "api_key", "sk-secret")
|
||||
calls = self.mock_upstream_recording(sse_stream_mock([]))
|
||||
self.client.set_cookie("ai_summary_server", "http://user:pass@untrusted.example.org:11434")
|
||||
|
||||
res = self.client.post("/ai_summary", json={"messages": [{"role": "user", "content": "hi"}]})
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual([(BASE_URL, "sk-secret")], calls)
|
||||
|
||||
def test_endpoint_upstream_error(self):
|
||||
self.mock_upstream(sse_stream_mock([], status_code=500))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user