diff --git a/agent/ssl_guard.py b/agent/ssl_guard.py index ac1b7841b2..35c3e47621 100644 --- a/agent/ssl_guard.py +++ b/agent/ssl_guard.py @@ -55,7 +55,13 @@ def _validate_bundle_path(label: str, value: str, *, require_substantial: bool = ctx = ssl.create_default_context(cafile=str(path)) except Exception as exc: raise _ssl_err(f"{label} CA bundle at {value} cannot be loaded: {exc}") from exc - if not ctx.get_ca_certs(): + try: + loaded_certs = ctx.get_ca_certs() + except NotImplementedError: + # truststore-backed SSLContext (Windows OS trust store) doesn't + # implement get_ca_certs(); bundle was already validated above. + return + if not loaded_certs: raise _ssl_err(f"{label} CA bundle at {value} did not load any certificates") diff --git a/tests/agent/test_ssl_ca_guard.py b/tests/agent/test_ssl_ca_guard.py index 4b9da7d32f..a381352a45 100644 --- a/tests/agent/test_ssl_ca_guard.py +++ b/tests/agent/test_ssl_ca_guard.py @@ -19,8 +19,6 @@ def test_healthy_bundle_passes(monkeypatch): verify_ca_bundle() - - def test_empty_certifi_bundle_raises_ssl_error(monkeypatch, tmp_path): """Empty file is treated as a corrupted bundle.""" fake = tmp_path / "empty.pem" @@ -44,7 +42,33 @@ def test_missing_explicit_ca_bundle_env_raises_before_httpx(monkeypatch, tmp_pat assert "force-reinstall" in message +def test_truststore_get_ca_certs_not_implemented_is_accepted(monkeypatch, tmp_path): + """A truststore-backed SSLContext (Windows OS trust store) raises + NotImplementedError from get_ca_certs(). The guard must accept the + already-loaded bundle rather than fail. + Regression for the empty-message ``Failed to initialize OpenAI client:`` + seen on every fresh agent init on Windows (str(NotImplementedError()) == ""). + """ + from agent import ssl_guard + bundle = tmp_path / "bundle.pem" + bundle.write_text( + "-----BEGIN CERTIFICATE-----\nfake\n-----END CERTIFICATE-----\n", + encoding="utf-8", + ) + class _TruststoreLikeContext: + def get_ca_certs(self, binary_form=False): # noqa: ARG002 - mirror ssl API + raise NotImplementedError() + # create_default_context(cafile=...) loads the bundle fine; only the + # post-load introspection is unsupported under truststore. + monkeypatch.setattr( + ssl_guard.ssl, "create_default_context", lambda *a, **k: _TruststoreLikeContext() + ) + monkeypatch.setenv("SSL_CERT_FILE", str(bundle)) + + # Must not raise on the explicit env bundle nor the certifi check. + verify_ca_bundle() + verify_ca_bundle_with_fallback()