From c33be87aad46a2b699f42ff48ea7616ca0b4b601 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:23:54 +0530 Subject: [PATCH] fix(urllib): log the default-certificates fallback once, after every bundle failed Each failed candidate used to claim "falling back to default certificates" even when the next candidate (certifi on macOS) loaded fine. Per-failure warnings now say "trying the next bundle"; the default-certificates warning is emitted once at the final (None, None) return. Level unchanged (WARNING). PROOF: test_default_certificates_fallback_is_logged_once_after_all_bundles_fail fails on the previous per-candidate wording and passes with this change; tests/hermes_cli/test_urllib_security.py 22 passed. --- hermes_cli/urllib_security.py | 6 +++--- tests/hermes_cli/test_urllib_security.py | 25 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/hermes_cli/urllib_security.py b/hermes_cli/urllib_security.py index 85f311885b..d8626bd13c 100644 --- a/hermes_cli/urllib_security.py +++ b/hermes_cli/urllib_security.py @@ -167,9 +167,9 @@ def _build_https_context(candidates: tuple[str, ...]) -> tuple[ssl.SSLContext | try: return ssl.create_default_context(cafile=path), path except (OSError, ssl.SSLError) as exc: - logger.warning( - "CA bundle could not be loaded from %s: %s — falling back to default certificates", path, exc - ) + logger.warning("CA bundle could not be loaded from %s: %s — trying the next bundle", path, exc) + if candidates: + logger.warning("No configured CA bundle could be loaded — falling back to default certificates") return None, None diff --git a/tests/hermes_cli/test_urllib_security.py b/tests/hermes_cli/test_urllib_security.py index 3e901402f2..c7bb2f5c98 100644 --- a/tests/hermes_cli/test_urllib_security.py +++ b/tests/hermes_cli/test_urllib_security.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import logging from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import ssl from threading import Thread @@ -600,6 +601,30 @@ def test_fallback_bundle_change_does_not_invalidate_the_memo(monkeypatch, tmp_pa assert calls == [str(preferred)] +def test_default_certificates_fallback_is_logged_once_after_all_bundles_fail(monkeypatch, caplog): + """A failed candidate says "trying the next bundle"; the default-certificates line is emitted once.""" + import hermes_cli.urllib_security as urllib_security + + def create_default_context(*, cafile=None): + raise ssl.SSLError(f"bad bundle {cafile}") + + monkeypatch.setattr(ssl, "create_default_context", create_default_context) + + with caplog.at_level(logging.WARNING, logger=urllib_security.logger.name): + assert urllib_security._build_https_context(("/a.pem", "/b.pem")) == (None, None) + + messages = [record.getMessage() for record in caplog.records] + per_failure = [m for m in messages if "trying the next bundle" in m] + assert [m.split(":")[0] for m in per_failure] == [ + "CA bundle could not be loaded from /a.pem", + "CA bundle could not be loaded from /b.pem", + ] + assert [m for m in messages if "falling back to default certificates" in m] == [ + "No configured CA bundle could be loaded — falling back to default certificates" + ] + assert all(record.levelno == logging.WARNING for record in caplog.records) + + @pytest.mark.parametrize( ("candidates", "first_load_fails_for", "expected_load_sequence"), [