Per-provider CA cert settings ignored in metadata probes
Cluster 66544 · agent. Data as of
Nothing open on GitHub. Fix merged in #56681. Check it covers your case.
The short answer
- Known?
- Yes. 5 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #56681. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #86491. That is separate from the fix event. - Still open
- Nothing open on GitHub.
These issues and PRs address a bug where provider-scoped SSL/CA certificate configuration was not being passed through to custom /models and pricing endpoint probes. The work involves threading per-provider TLS settings into those requests.
Open issues0
Open PRs0
Fixes on record
-
#56681 fix(agent): honor custom CA certs for custom_providers HTTPS endpoints (main + aux)
files:
agent/agent_init.py,agent/agent_runtime_helpers.py,agent/auxiliary_client.py,agent/process_bootstrap.py,agent/ssl_verify.py,hermes_cli/config.py,run_agent.py,tests/agent/test_auxiliary_client_ssl_verify.py,tests/agent/test_ssl_verify.py,tests/hermes_cli/test_custom_provider_tls.py,tests/run_agent/test_create_openai_client_ssl_verify.pyShow the diff
-
agent/agent_init.py@@ -974,6 +974,21 @@ def _moa_reference_relay(event: str, **kwargs: Any) -> None: # this mutation is reflected in the client built just below. agent._apply_user_default_headers() + try: + from hermes_cli.config import ( + apply_custom_provider_tls_to_client_kwargs, + get_compatible_custom_providers, + load_config, + ) + + apply_custom_provider_tls_to_client_kwargs( + client_kwargs, + str(client_kwargs.get("base_url") or agent.base_url or ""), + get_compatible_custom_providers(load_config()), + ) + except Exception: + logger.debug("custom-provider TLS resolution skipped", exc_info=True) + agent.api_key = [redacted:secret-assignment]"api_key", "") agent.base_url = client_kwargs.get("base_url", agent.base_url) try: -
agent/agent_runtime_helpers.py@@ -1513,6 +1513,7 @@ def anthropic_prompt_cache_policy( def create_openai_client(agent, client_kwargs: dict, *, reason: str, shared: bool) -> Any: from agent.auxiliary_client import _validate_base_url, _validate_proxy_env_urls + from agent.ssl_verify import resolve_httpx_verify # Treat client_kwargs as read-only. Callers pass agent._client_kwargs (or shallow # copies of it) in; any in-place mutation leaks back into the stored dict and is # reused on subsequent requests. #10933 hit this by injecting an httpx.Client @@ -1522,6 +1523,9 @@ def create_openai_client(agent, client_kwargs: dict, *, reason: str, shared: boo # copy locks the contract so future transport/keepalive work can't reintroduce # the same class of bug. client_kwargs = dict(client_kwargs) + ssl_ca_cert = client_kwargs.pop("ssl_ca_cert", None) + ssl_verify_cfg = client_kwargs.pop("ssl_verify", None) + httpx_verify = resolve_httpx_verify(ca_bundle=ssl_ca_cert, ssl_verify=ssl_verify_cfg) _validate_proxy_env_urls() _validate_base_url(client_kwargs.get("base_url")) if agent.provider == "copilot-acp" or str(client_kwargs.get("base_url", "")).startswith("acp://copilot"): @@ -1545,7 +1549,9 @@ def create_openai_client(agent, client_kwargs: dict, *, reason: str, shared: boo if k in {"api_key", "base_url", "default_headers", "timeout", "http_client"} } if "http_client" not in safe_kwargs: - keepalive_http = agent._build_keepalive_http_client(base_url) + keepalive_http = agent._build_keepalive_http_client( + base_url, verify=httpx_verify, + ) if keepalive_http is not None: safe_kwargs["http_client"] = keepalive_http client = GeminiNativeClient(**safe_kwargs) @@ -1574,7 +1580,9 @@ def create_openai_client(agent, client_kwargs: dict, *, reason: str, shared: boo # Tests in ``tests/run_agent/test_create_openai_client_reuse.py`` and # ``tests/run_agent/test_sequential_chats_live.py`` pin this invariant. if "http_client" not in client_kwargs: - keepalive_http = agent._build_keepalive_http_client(client_kwargs.get("base_url", "")) + keepalive_http = agent._build_keepalive_http_client( + client_kwargs.get("base_url", ""), verify=httpx_verify, + ) if keepalive_http is not None: client_kwargs["http_client"] = keepalive_http # Delegate all rate-limit / 5xx retry to hermes's outer conversation loop, @@ -1778,6 +1786,24 @@ def switch_model(agent, new_model, new_provider, api_key='', base_url='', api_mo "api_key": effective_key, "base_url": effective_base, } + try: + from hermes_cli.config import ( + apply_custom_provider_tls_to_client_kwargs, + get_compatible_custom_providers, + load_config_readonly, + ) + + # Read custom_providers from live config (not the init-time + # snapshot on ``agent._custom_providers``) so ssl_ca_cert / + # ssl_verify edits are honored when switching mid-session, + # matching the context-length reload below (#15779). + apply_custom_provider_tls_to_client_kwargs( + agent._client_kwargs, + str(effective_base or ""), + get_compatible_custom_providers(load_config_readonly()), + ) + except Exception: + logger.debug("custom-provider TLS resolution skipped on switch_model", exc_info=True) _sm_timeout = get_provider_request_timeout(agent.provider, agent.model) if _sm_timeout is not None: agent._client_kwargs["timeout"] = _sm_timeout -
agent/auxiliary_client.py@@ -128,13 +128,45 @@ def __repr__(self): _LOGGED_UNSUPPORTED_OAUTH_KEYS: set = set() +def _resolve_aux_verify(base_url: Optional[str]) -> Any: + """Resolve httpx ``verify`` for an auxiliary-client base_url. + + Mirrors the main client's TLS resolution so auxiliary calls (compression, + vision, web_extract, title generation, etc.) honor per-provider + ``ssl_ca_cert`` / ``ssl_verify`` config and the ``HERMES_CA_BUNDLE`` / + ``SSL_CERT_FILE`` env conventions. Best-effort: any failure falls back to + the httpx/certifi default (``True``). + """ + try: + from agent.ssl_verify import resolve_httpx_verify + from hermes_cli.config import ( + get_custom_provider_tls_settings, + load_config_readonly, + ) + + tls = get_custom_provider_tls_settings( + str(base_url or ""), config=load_config_readonly() + ) + return resolve_httpx_verify( + ca_bundle=tls.get("ssl_ca_cert"), + ssl_verify=tls.get("ssl_verify"), + base_url=str(base_url or ""), + ) + except Exception: + return True + + def _openai_http_client_kwargs( base_url: Optional[str], *, async_mode: bool = False, ) -> Dict[str, Any]: """Inject keepalive httpx client with env-only proxy (not macOS system proxy).""" - client = build_keepalive_http_client(str(base_url or ""), async_mode=async_mode) + client = build_keepalive_http_client( + str(base_url or ""), + async_mode=async_mode, + verify=_resolve_aux_verify(base_url), + ) if client is None: return {} return {"http_client": client} -
agent/process_bootstrap.py@@ -146,6 +146,7 @@ def build_keepalive_http_client( base_url: str = "", *, async_mode: bool = False, + verify: Any = True, ) -> Optional[Any]: """Build an httpx client for OpenAI SDK calls with env-only proxy policy. @@ -154,6 +155,13 @@ def build_keepalive_http_client( ``trust_env`` path, so macOS system proxy settings from ``urllib.request.getproxies()`` (which omit the ExceptionsList) are not applied. Mirrors ``AIAgent._build_keepalive_http_client``. + + ``verify`` is forwarded to httpx so auxiliary-client calls (compression, + vision, web_extract, title generation, etc.) honor the same per-provider + ``ssl_ca_cert`` / ``ssl_verify`` and ``HERMES_CA_BUNDLE`` settings the main + client uses. It is passed on the ``HTTPTransport`` (which owns the SSL + context when a custom transport is supplied) and, for the copilot branch + that has no custom transport, on the client itself. """ try: import httpx @@ -161,7 +169,7 @@ def build_keepalive_http_client( if "api.githubcopilot.com" in str(base_url or "").lower(): client_cls = httpx.AsyncClient if async_mode else httpx.Client - return client_cls() + return client_cls(verify=verify) sock_opts = [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)] if hasattr(socket, "TCP_KEEPIDLE"): @@ -174,8 +182,10 @@ def build_keepalive_http_client( proxy = _get_proxy_for_base_url(base_url) transport_cls = httpx.AsyncHTTPTransport if async_mode else httpx.HTTPTransport client_cls = httpx.AsyncClient if async_mode else httpx.Client + # verify lives on the transport: httpx ignores the client-level + # ``verify`` when a custom ``transport=`` is supplied. return client_cls( - transport=transport_cls(socket_options=sock_opts), + transport=transport_cls(socket_options=sock_opts, verify=verify), proxy=proxy, ) except Exception: -
agent/ssl_verify.py@@ -0,0 +1,63 @@ +"""TLS verify resolution for httpx/OpenAI provider clients.""" + +from __future__ import annotations + +import logging +import os +import ssl +from pathlib import Path +from typing import Any, Optional + +logger = logging.getLogger(__name__) + + +def _coerce_insecure(ssl_verify: Any) -> bool: + if ssl_verify is False: + return True + if isinstance(ssl_verify, str) and ssl_verify.strip().lower() in {"false", "0", "no", "off"}: + return True + return False + + +def resolve_httpx_verify( + *, + ca_bundle: Optional[str] = None, + ssl_verify: Any = None, + base_url: str = "", +) -> bool | ssl.SSLContext: + """Resolve httpx ``verify`` for provider HTTP clients. + + Priority: + 1. ``ssl_verify: false`` — disable verification (local dev only) + 2. explicit ``ca_bundle`` (per-provider ``ssl_ca_cert`` config field) + 3. ``HERMES_CA_BUNDLE``, ``SSL_CERT_FILE``, ``REQUESTS_CA_BUNDLE``, + ``CURL_CA_BUNDLE`` env vars + 4. ``True`` (httpx/certifi default) + + ``base_url`` is used only for the insecure-mode warning message. + """ + if _coerce_insecure(ssl_verify): + logger.warning( + "TLS certificate verification DISABLED (ssl_verify: false) for %s — " + "this is intended for local development only and is unsafe on any " + "network you do not fully control.", + base_url or "a custom provider endpoint", + ) + return False + + effective_ca = ( + (ca_bundle or "").strip() + or os.getenv("HERMES_CA_BUNDLE", "").strip() + or os.getenv("SSL_CERT_FILE", "").strip() + or os.getenv("REQUESTS_CA_BUNDLE", "").strip() + or os.getenv("CURL_CA_BUNDLE", "").strip() + ) + if effective_ca: + ca_path = str(Path(effective_ca).expanduser()) + if os.path.isfile(ca_path): + return ssl.create_default_context(cafile=ca_path) + logger.warning( + "CA bundle path does not exist: %s — falling back to default certificates", + effective_ca, + ) + return True -
hermes_cli/config.py@@ -4478,7 +4478,7 @@ def _normalize_custom_provider_entry( "api_mode", "transport", "model", "default_model", "models", "context_length", "rate_limit_delay", "request_timeout_seconds", "stale_timeout_seconds", - "discover_models", "extra_body", + "discover_models", "extra_body", "ssl_ca_cert", "ssl_verify", } for camel, snake in _CAMEL_ALIASES.items(): if camel in entry and snake not in entry: @@ -4585,6 +4585,16 @@ def _normalize_custom_provider_entry( if isinstance(extra_body, dict): normalized["extra_body"] = dict(extra_body) + ssl_ca_cert = entry.get("ssl_ca_cert") + if isinstance(ssl_ca_cert, str) and ssl_ca_cert.strip(): + normalized["ssl_ca_cert"] = ssl_ca_cert.strip() + + ssl_verify = entry.get("ssl_verify") + if isinstance(ssl_verify, bool): + normalized["ssl_verify"] = ssl_verify + elif isinstance(ssl_verify, str) and ssl_verify.strip(): + normalized["ssl_verify"] = ssl_verify.strip() + return normalized @@ -4612,6 +4622,8 @@ def _custom_provider_entry_to_provider_config( "rate_limit_delay", "discover_models", "extra_body", + "ssl_ca_cert", + "ssl_verify", ): if field in normalized: provider_entry[field] = normalized[field] @@ -4688,6 +4700,71 @@ def _append_if_new(entry: Optional[Dict[str, Any]]) -> None: return compatible +def _coerce_ssl_verify(value: Any) -> Optional[bool]: + if value is None: + return None + if isinstance(value, bool): + return value + if isinstance(value, str): + lowered = value.strip().lower() + if lowered in {"false", "0", "no", "off"}: + return False + if lowered in {"true", "1", "yes", "on"}: + return True + return None + + +def get_custom_provider_tls_settings( + base_url: str, + custom_providers: Optional[List[Dict[str, Any]]] = None, + config: Optional[Dict[str, Any]] = None, +) -> Dict[str, Any]: + """Return TLS settings from a matching ``custom_providers`` / ``providers`` entry.""" + if custom_providers is None: + try: + custom_providers = get_compatible_custom_providers(config) + except Exception: + custom_providers = [] + if not base_url or not isinstance(custom_providers, list): + return {} + + # Case-insensitive compare: elsewhere custom_providers are keyed on a + # lowercased base_url (see get_compatible_custom_providers dedup), and + # scheme/host are case-insensitive anyway — so a config entry written as + # https://Ollama.Example.com/v1 must still match a lowercased runtime + # base_url. Exact match after rstrip('/') + lower() (no prefix/substring). + target_url = (base_url or "").rstrip("/").lower() + for entry in custom_providers: + if not isinstance(entry, dict): + continue + entry_url = (entry.get("base_url") or "").rstrip("/").lower() + if not entry_url or entry_url != target_url: + continue + out: Dict[str, Any] = {} + ca = entry.get("ssl_ca_cert") + if isinstance(ca, str) and ca.strip(): + out["ssl_ca_cert"] = ca.strip() + verify = _coerce_ssl_verify(entry.get("ssl_verify")) + if verify is not None: + out["ssl_verify"] = verify + return out + return {} + + +def apply_custom_provider_tls_to_client_kwargs( + client_kwargs: Dict[str, Any], + base_url: str, + custom_providers: Optional[List[Dict[str, Any]]] = None, + config: Optional[Dict[str, Any]] = None, +) -> None: + """Attach per-provider TLS knobs to OpenAI client kwargs when matched.""" + tls = get_custom_provider_tls_settings(base_url, custom_providers, config) + if tls.get("ssl_ca_cert"): + client_kwargs["ssl_ca_cert"] = tls["ssl_ca_cert"] + if "ssl_verify" in tls: + client_kwargs["ssl_verify"] = tls["ssl_verify"] + + def get_custom_provider_context_length( model: str, base_url: str, @@ -4813,6 +4890,7 @@ def check_config_version() -> Tuple[int, int]: _VALID_CUSTOM_PROVIDER_FIELDS = { "name", "base_url", "api_key", "api_mode", "model", "models", "context_length", "rate_limit_delay", "extra_body", + "ssl_ca_cert", "ssl_verify", # key_env is read at runtime by runtime_provider.py and auxiliary_client.py # — include it here so the set accurately describes the supported schema. "key_env", -
run_agent.py@@ -3884,13 +3884,13 @@ def _is_openai_client_closed(client: Any) -> bool: return False @staticmethod - def _build_keepalive_http_client(base_url: str = "") -> Any: + def _build_keepalive_http_client(base_url: str = "", *, verify: Any = True) -> Any: try: import httpx as _httpx import socket as _socket if "api.githubcopilot.com" in str(base_url or "").lower(): - return _httpx.Client() + return _httpx.Client(verify=verify) _sock_opts = [(_socket.SOL_SOCKET, _socket.SO_KEEPALIVE, 1)] if hasattr(_socket, "TCP_KEEPIDLE"): @@ -3904,8 +3904,10 @@ def _build_keepalive_http_client(base_url: str = "") -> Any: # Explicitly read proxy settings while still honoring NO_PROXY for # loopback / local endpoints such as a locally hosted sub2api. _proxy = _get_proxy_for_base_url(base_url) + # verify lives on the transport: httpx ignores the client-level + # ``verify`` when a custom ``transport=`` is supplied. return _httpx.Client( - transport=_httpx.HTTPTransport(socket_options=_sock_opts), + transport=_httpx.HTTPTransport(socket_options=_sock_opts, verify=verify), proxy=_proxy, ) except Exception: -
tests/agent/test_auxiliary_client_ssl_verify.py@@ -0,0 +1,79 @@ +"""Regression: auxiliary-client keepalive httpx client must honor custom CA bundles. + +The main OpenAI client resolves per-provider ``ssl_ca_cert`` / ``ssl_verify`` and +``HERMES_CA_BUNDLE`` via ``agent.ssl_verify.resolve_httpx_verify``. Auxiliary calls +(compression, vision, web_extract, title generation, session_search) build their own +keepalive client through ``agent.process_bootstrap.build_keepalive_http_client`` and must +apply the same TLS settings — otherwise an HTTPS custom_providers endpoint signed by a +private CA works for chat but fails ``APIConnectionError`` on every auxiliary task. +""" + +import ssl + +import certifi +import httpx +import pytest + +from agent.process_bootstrap import build_keepalive_http_client + +_CA_ENV_VARS = ("HERMES_CA_BUNDLE", "SSL_CERT_FILE", "REQUESTS_CA_BUNDLE", "HTTPS_PROXY") + + [redacted:email] +def clean_tls_env(monkeypatch): + for var in _CA_ENV_VARS: + monkeypatch.delenv(var, raising=False) + + +def test_build_keepalive_http_client_forwards_verify_context(clean_tls_env): + ctx = ssl.create_default_context(cafile=certifi.where()) + client = build_keepalive_http_client("https://ollama.example.com/v1", verify=ctx) + assert isinstance(client, httpx.Client) + assert client._transport._pool._ssl_context is ctx + + +def test_build_keepalive_http_client_verify_false_disables_hostname_check(clean_tls_env): + client = build_keepalive_http_client("https://ollama.example.com/v1", verify=False) + assert isinstance(client, httpx.Client) + assert client._transport._pool._ssl_context.check_hostname is False + + +def test_build_keepalive_http_client_default_verify_true(clean_tls_env): + client = build_keepalive_http_client("https://ollama.example.com/v1") + assert isinstance(client, httpx.Client) + + +def test_resolve_aux_verify_uses_per_provider_ssl_ca_cert(clean_tls_env, monkeypatch): + """_resolve_aux_verify should mirror the main-client resolution for a matched base_url.""" + import hermes_cli.config as cfg + from agent import auxiliary_client + + # get_custom_provider_tls_settings is imported inside the function from + # hermes_cli.config, so patch it at the source module. + monkeypatch.setattr( + cfg, + "get_custom_provider_tls_settings", + lambda *a, **k: {"ssl_ca_cert": certifi.where()}, + ) + verify = auxiliary_client._resolve_aux_verify("https://ollama.example.com/v1") + assert isinstance(verify, ssl.SSLContext) + + +def test_resolve_aux_verify_ssl_verify_false(clean_tls_env, monkeypatch): + import hermes_cli.config as cfg + from agent import auxiliary_client + + monkeypatch.setattr( + cfg, + "get_custom_provider_tls_settings", + lambda *a, **k: {"ssl_verify": False}, + ) + assert auxiliary_client._resolve_aux_verify("https://ollama.example.com/v1") is False + + +def test_resolve_aux_verify_no_match_defaults_true(clean_tls_env, monkeypatch): + import hermes_cli.config as cfg + from agent import auxiliary_client + + monkeypatch.setattr(cfg, "get_custom_provider_tls_settings", lambda *a, **k: {}) + assert auxiliary_client._resolve_aux_verify("https://openrouter.ai/api/v1") is True -
tests/agent/test_ssl_verify.py@@ -0,0 +1,40 @@ +"""Tests for agent.ssl_verify.resolve_httpx_verify.""" + +import ssl + +import certifi +import pytest + +from agent.ssl_verify import resolve_httpx_verify + +_CA_ENV_VARS = ("HERMES_CA_BUNDLE", "SSL_CERT_FILE", "REQUESTS_CA_BUNDLE") + + [redacted:email] +def clean_ca_env(monkeypatch): + for var in _CA_ENV_VARS: + monkeypatch.delenv(var, raising=False) + + +def test_ssl_verify_false_disables_verification(clean_ca_env): + assert resolve_httpx_verify(ssl_verify=False) is False + + +def test_hermes_ca_bundle_returns_ssl_context(clean_ca_env, monkeypatch): + monkeypatch.setenv("HERMES_CA_BUNDLE", certifi.where()) + result = resolve_httpx_verify() + assert isinstance(result, ssl.SSLContext) + + +def test_explicit_ca_bundle_param(clean_ca_env): + result = resolve_httpx_verify(ca_bundle=certifi.where()) + assert isinstance(result, ssl.SSLContext) + + +def test_missing_ca_bundle_falls_back_to_true(clean_ca_env, monkeypatch): + monkeypatch.setenv("HERMES_CA_BUNDLE", "/nonexistent/root-ca.pem") + assert resolve_httpx_verify() is True + + +def test_default_without_env_is_true(clean_ca_env): + assert resolve_httpx_verify() is True -
tests/hermes_cli/test_custom_provider_tls.py@@ -0,0 +1,72 @@ +"""Tests for per-provider TLS settings in custom_providers config.""" + +from hermes_cli.config import ( + apply_custom_provider_tls_to_client_kwargs, + get_custom_provider_tls_settings, +) + + +def test_get_custom_provider_tls_settings_matches_base_url(): + providers = [ + { + "name": "Ollama", + "base_url": "https://ollama.example.com/v1", + "ssl_ca_cert": "/etc/ssl/mkcert-root.pem", + } + ] + tls = get_custom_provider_tls_settings( + "https://ollama.example.com/v1/", + custom_providers=providers, + ) + assert tls == {"ssl_ca_cert": "/etc/ssl/mkcert-root.pem"} + + +def test_apply_custom_provider_tls_to_client_kwargs(): + client_kwargs = {"api_key": "x", "base_url": "https://ollama.example.com/v1"} + providers = [ + { + "name": "Ollama", + "base_url": "https://ollama.example.com/v1", + "ssl_ca_cert": "/etc/ssl/mkcert-root.pem", + "ssl_verify": True, + } + ] + apply_custom_provider_tls_to_client_kwargs( + client_kwargs, + "https://ollama.example.com/v1", + custom_providers=providers, + ) + assert client_kwargs["ssl_ca_cert"] == "/etc/ssl/mkcert-root.pem" + assert client_kwargs["ssl_verify"] is True + + +def test_get_custom_provider_tls_settings_matches_case_insensitively(): + """A config base_url with mixed case must still match a lowercased runtime base_url.""" + providers = [ + { + "name": "Ollama", + "base_url": "https://Ollama.Example.com/v1", + "ssl_ca_cert": "/etc/ssl/mkcert-root.pem", + } + ] + tls = get_custom_provider_tls_settings( + "https://ollama.example.com/v1", + custom_providers=providers, + ) + assert tls == {"ssl_ca_cert": "/etc/ssl/mkcert-root.pem"} + + +def test_get_custom_provider_tls_settings_no_substring_bypass(): + """A base_url that is only a prefix of an entry must NOT match.""" + providers = [ + { + "name": "Ollama", + "base_url": "https://ollama.example.com/v1", + "ssl_verify": False, + } + ] + # A different host that shares a prefix must not pick up ssl_verify:false. + assert get_custom_provider_tls_settings( + "https://ollama.example.com.attacker.test/v1", + custom_providers=providers, + ) == {} -
tests/run_agent/test_create_openai_client_ssl_verify.py@@ -0,0 +1,46 @@ +"""Regression: keepalive httpx client must honor custom CA bundles for HTTPS providers.""" + +import ssl + +import certifi +import httpx +import pytest + +from agent.ssl_verify import resolve_httpx_verify +from run_agent import AIAgent + +_CA_ENV_VARS = ("HERMES_CA_BUNDLE", "SSL_CERT_FILE", "REQUESTS_CA_BUNDLE", "HTTPS_PROXY") + + [redacted:email] +def clean_tls_env(monkeypatch): + for var in _CA_ENV_VARS: + monkeypatch.delenv(var, raising=False) + + +def test_build_keepalive_http_client_uses_hermes_ca_bundle(clean_tls_env, monkeypatch): + monkeypatch.setenv("HERMES_CA_BUNDLE", certifi.where()) + verify = resolve_httpx_verify() + client = AIAgent._build_keepalive_http_client( + "https://ollama.example.com/v1", verify=verify, + ) + assert isinstance(client, httpx.Client) + assert isinstance(client._transport._pool._ssl_context, ssl.SSLContext) + + +def test_build_keepalive_http_client_honors_per_provider_ssl_ca_cert(clean_tls_env): + verify = resolve_httpx_verify(ca_bundle=certifi.where()) + client = AIAgent._build_keepalive_http_client( + "https://ollama.example.com/v1", verify=verify, + ) + assert isinstance(client, httpx.Client) + assert isinstance(client._transport._pool._ssl_context, ssl.SSLContext) + + +def test_build_keepalive_http_client_ssl_verify_false(clean_tls_env): + verify = resolve_httpx_verify(ssl_verify=False) + client = AIAgent._build_keepalive_http_client( + "https://ollama.example.com/v1", verify=verify, + ) + assert isinstance(client, httpx.Client) + assert client._transport._pool._ssl_context.check_hostname is False
-
-
#86491 fix(providers): honor per-provider TLS on custom /models and pricing probes
files:
agent/model_metadata.py,hermes_cli/models.py,hermes_cli/urllib_security.py,tests/agent/test_custom_provider_ca_probes.py
2 PRs closed without merging
From the close comments: merged via another change 1 · superseded by another PR 1
Closed issues · 1
- Closed#66544Custom-provider metadata probes ignore provider-scoped CA settings
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.40: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the Lab