fix(security): cover remaining catalog credential paths

This commit is contained in:
kshitijk4poor 2026-07-11 11:47:21 +05:30 committed by kshitij
parent 27a1042b13
commit cf34a1e8c7
3 changed files with 48 additions and 6 deletions

View file

@ -18,14 +18,13 @@ def url_origin(url: str) -> tuple[str, str, int | None]:
"""Return a normalized (scheme, hostname, effective port) origin."""
parsed = urllib.parse.urlparse(url)
scheme = (parsed.scheme or "").lower()
try:
port = parsed.port
except ValueError:
port = None
# Accessing ``parsed.port`` validates malformed/non-numeric ports. Let the
# ValueError fail the request closed instead of collapsing it to a default.
port = parsed.port
return (
scheme,
(parsed.hostname or "").lower().rstrip("."),
port or _DEFAULT_PORTS.get(scheme),
port if port is not None else _DEFAULT_PORTS.get(scheme),
)

View file

@ -4,6 +4,7 @@ import json
import logging
import urllib.request
from hermes_cli.urllib_security import open_credentialed_url
from providers import register_provider
from providers.base import ProviderProfile
@ -28,7 +29,7 @@ class AnthropicProfile(ProviderProfile):
req.add_header("x-api-key", api_key)
req.add_header("anthropic-version", "2023-06-01")
req.add_header("Accept", "application/json")
with urllib.request.urlopen(req, timeout=timeout) as resp:
with open_credentialed_url(req, timeout=timeout) as resp:
data = json.loads(resp.read().decode())
return [
m["id"]

View file

@ -98,6 +98,13 @@ def test_url_origin_normalizes_default_ports_and_trailing_dot():
443,
)
assert url_origin("http://example.test") != url_origin("https://example.test")
assert url_origin("https://example.test:0") == (
"https",
"example.test",
0,
)
with pytest.raises(ValueError):
url_origin("https://example.test:not-a-port")
def test_cross_host_redirect_drops_arbitrary_credentials_on_wire():
@ -337,6 +344,41 @@ class _LmStudioSourceHandler(BaseHTTPRequestHandler):
pass
def test_anthropic_profile_drops_x_api_key_on_redirect(monkeypatch):
import importlib
AnthropicProfile = importlib.import_module(
"plugins.model-providers.anthropic"
).AnthropicProfile
source = _server()
sink = _server()
_RecordingHandler.requests = []
_RecordingHandler.redirect_status = 302
_RecordingHandler.redirect_to = f"http://localhost:{sink.server_port}/sink"
original_request = urllib.request.Request
def local_anthropic_request(url, *args, **kwargs):
if url == "https://api.anthropic.com/v1/models":
url = f"http://127.0.0.1:{source.server_port}/redirect"
return original_request(url, *args, **kwargs)
monkeypatch.setattr(urllib.request, "Request", local_anthropic_request)
try:
result = AnthropicProfile(name="anthropic").fetch_models(
api_key="anthropic-secret", timeout=3
)
finally:
source.shutdown()
sink.shutdown()
assert result == []
_, headers = _RecordingHandler.requests[-1]
assert "x-api-key" not in headers
assert headers["accept"] == "application/json"
def test_lmstudio_load_post_drops_bearer_on_redirect(monkeypatch):
from hermes_cli import models