# NousResearch/hermes-agent · Per-provider CA cert settings ignored in metadata probes

## Open now

- 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.

## TL;DR

- 5 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 2 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #56681. Verify it addresses your case.
- **Recommended:** read PR #56681 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-86491`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.40`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #66544: Custom-provider metadata probes ignore provider-scoped CA settings

## Prior & competing PRs

- **#56681: fix(agent): honor custom CA certs for custom_providers HTTPS endpoints (main + aux)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - 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.py`
  - fix hunks:
  - `agent/agent_init.py`

    ```diff
    @@ -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`

    ```diff
    @@ -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
    ```

    ```diff
    @@ -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"):
    ```

    ```diff
    @@ -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)
    ```

    ```diff
    @@ -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,
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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.
     
    ```

    ```diff
    @@ -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
    ```

    ```diff
    @@ -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"):
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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:
    ```

    ```diff
    @@ -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
     
     
    ```

    ```diff
    @@ -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]
    ```

    ```diff
    @@ -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,
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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"):
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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`

    ```diff
    @@ -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**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `agent/model_metadata.py`, `hermes_cli/models.py`, `hermes_cli/urllib_security.py`, `tests/agent/test_custom_provider_ca_probes.py`
- **#66561: fix(providers): honor per-provider TLS on custom /models and pricing probes**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `44d580617f33adb67a5913c1aa3b76319d057809`, `#86491`, `#56681`, `#66544`
- **#66564: fix(ssl): thread provider ssl_ca_cert into /models and pricing probes (#66544)**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#66561`, `#66561`

## Fix status

- Merged to main in PR #56681 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-08-14`.
- Subsystem: `agent`
