diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 6f241a930e..befa97d098 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -1513,7 +1513,15 @@ def _resolve_verify( if effective_insecure: return False if effective_ca: - return str(effective_ca) + ca_path = str(effective_ca) + if not os.path.isfile(ca_path): + import logging + logging.getLogger("hermes.auth").warning( + "CA bundle path does not exist: %s — falling back to default certificates", + ca_path, + ) + return True + return ca_path return True diff --git a/tests/hermes_cli/test_auth_nous_provider.py b/tests/hermes_cli/test_auth_nous_provider.py index c449fe3b49..698d6b3725 100644 --- a/tests/hermes_cli/test_auth_nous_provider.py +++ b/tests/hermes_cli/test_auth_nous_provider.py @@ -1,6 +1,7 @@ """Regression tests for Nous OAuth refresh + agent-key mint interactions.""" import json +import os from datetime import datetime, timezone from pathlib import Path @@ -10,6 +11,80 @@ import pytest from hermes_cli.auth import AuthError, get_provider_auth_state, resolve_nous_runtime_credentials +# ============================================================================= +# _resolve_verify: CA bundle path validation +# ============================================================================= + + +class TestResolveVerifyFallback: + """Verify _resolve_verify falls back to True when CA bundle path doesn't exist.""" + + def test_missing_ca_bundle_in_auth_state_falls_back(self): + from hermes_cli.auth import _resolve_verify + + result = _resolve_verify(auth_state={ + "tls": {"insecure": False, "ca_bundle": "/nonexistent/ca-bundle.pem"}, + }) + assert result is True + + def test_valid_ca_bundle_in_auth_state_is_returned(self, tmp_path): + from hermes_cli.auth import _resolve_verify + + ca_file = tmp_path / "ca-bundle.pem" + ca_file.write_text("fake cert") + result = _resolve_verify(auth_state={ + "tls": {"insecure": False, "ca_bundle": str(ca_file)}, + }) + assert result == str(ca_file) + + def test_missing_ssl_cert_file_env_falls_back(self, monkeypatch): + from hermes_cli.auth import _resolve_verify + + monkeypatch.setenv("SSL_CERT_FILE", "/nonexistent/ssl-cert.pem") + monkeypatch.delenv("HERMES_CA_BUNDLE", raising=False) + result = _resolve_verify(auth_state={"tls": {}}) + assert result is True + + def test_missing_hermes_ca_bundle_env_falls_back(self, monkeypatch): + from hermes_cli.auth import _resolve_verify + + monkeypatch.setenv("HERMES_CA_BUNDLE", "/nonexistent/hermes-ca.pem") + monkeypatch.delenv("SSL_CERT_FILE", raising=False) + result = _resolve_verify(auth_state={"tls": {}}) + assert result is True + + def test_insecure_takes_precedence_over_missing_ca(self): + from hermes_cli.auth import _resolve_verify + + result = _resolve_verify( + insecure=True, + auth_state={"tls": {"ca_bundle": "/nonexistent/ca.pem"}}, + ) + assert result is False + + def test_no_ca_bundle_returns_true(self, monkeypatch): + from hermes_cli.auth import _resolve_verify + + monkeypatch.delenv("HERMES_CA_BUNDLE", raising=False) + monkeypatch.delenv("SSL_CERT_FILE", raising=False) + result = _resolve_verify(auth_state={"tls": {}}) + assert result is True + + def test_explicit_ca_bundle_param_missing_falls_back(self): + from hermes_cli.auth import _resolve_verify + + result = _resolve_verify(ca_bundle="/nonexistent/explicit-ca.pem") + assert result is True + + def test_explicit_ca_bundle_param_valid_is_returned(self, tmp_path): + from hermes_cli.auth import _resolve_verify + + ca_file = tmp_path / "explicit-ca.pem" + ca_file.write_text("fake cert") + result = _resolve_verify(ca_bundle=str(ca_file)) + assert result == str(ca_file) + + def _setup_nous_auth( hermes_home: Path, *,