diff --git a/AGENTS.md b/AGENTS.md index 9355867..4815d80 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,6 +50,10 @@ service is serving — confirm with `haystack-enterprise service-status SERVICE` Set `CI=true` to replace spinners and progress bars with plain line output. +The CLI verifies TLS against the OS trust store (via `truststore`), so a corporate proxy's root CA +installed in Windows or macOS is trusted. The SDK used as a library does not; call +`truststore.inject_into_ssl()` yourself if you hit `CERTIFICATE_VERIFY_FAILED` behind such a proxy. + `deploy --env KEY=VALUE`, `--env KEY` (value read from the environment) and `--env-file FILE` set service secrets on the target service. Existing names are updated and others left alone, and the values apply on the next request without a redeploy. diff --git a/haystack_enterprise_sdk/cli.py b/haystack_enterprise_sdk/cli.py index a1dc61a..05ae7ed 100644 --- a/haystack_enterprise_sdk/cli.py +++ b/haystack_enterprise_sdk/cli.py @@ -21,6 +21,7 @@ import click import httpx import structlog +import truststore import typer from dotenv import dotenv_values from tabulate import tabulate @@ -2118,6 +2119,9 @@ def run_packaged() -> None: This is the console-script entrypoint (``haystack-enterprise``) declared in ``pyproject.toml``; it is not a subcommand. It runs the Typer app and turns API errors into a clean exit. """ + # Verify TLS against the OS trust store instead of certifi, so CAs installed by corporate + # TLS-inspecting proxies (common on Windows) are trusted. CLI only: the library leaves `ssl` alone. + truststore.inject_into_ssl() try: cli_app() except HaystackEnterpriseAPIError as err: diff --git a/pyproject.toml b/pyproject.toml index 5ddbcc1..09bf596 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ dependencies = [ "pydantic>=2.11.4", "ruamel.yaml>=0.18.10", "sniffio>=1.3.1", + "truststore>=0.10.0", # CLI verifies TLS against the OS trust store (corporate proxy CAs on Windows) ] [project.optional-dependencies] diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 3bef5be..8b09235 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -26,7 +26,7 @@ UploadSessionWriteModeEnum, WriteMode, ) -from haystack_enterprise_sdk.cli import _browser_login, _configure_cli_logging, _ui_url_for, cli_app +from haystack_enterprise_sdk.cli import _browser_login, _configure_cli_logging, _ui_url_for, cli_app, run_packaged from haystack_enterprise_sdk.models import UserInfo from haystack_enterprise_sdk.workflows.sync_client.files import download as sync_download @@ -720,3 +720,12 @@ def test_verbose_shows_info_and_debug(self, capsys: pytest.CaptureFixture) -> No combined = captured.out + captured.err assert "info-line" in combined assert "debug-line" in combined + + +def test_run_packaged_uses_os_trust_store() -> None: + with ( + patch("haystack_enterprise_sdk.cli.truststore.inject_into_ssl") as inject, + patch("haystack_enterprise_sdk.cli.cli_app"), + ): + run_packaged() + inject.assert_called_once_with() diff --git a/uv.lock b/uv.lock index 67c5714..106e250 100644 --- a/uv.lock +++ b/uv.lock @@ -772,6 +772,7 @@ dependencies = [ { name = "tabulate" }, { name = "tenacity" }, { name = "tqdm" }, + { name = "truststore" }, { name = "typer" }, { name = "yaspin" }, ] @@ -813,6 +814,7 @@ requires-dist = [ { name = "tabulate", specifier = ">=0.9.0" }, { name = "tenacity", specifier = ">=8.3.0" }, { name = "tqdm", specifier = ">=4.66.4" }, + { name = "truststore", specifier = ">=0.10.0" }, { name = "typer", specifier = ">=0.15.0" }, { name = "yaspin", specifier = ">=3.0.0" }, ] @@ -2508,6 +2510,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl", hash = "sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73", size = 80199, upload-time = "2026-09-11T07:25:14.599Z" }, ] +[[package]] +name = "truststore" +version = "0.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/9f/c5201d42a484c061e528825fc8e2d565f5abd50a4ced6fb7d29c4ec99b2b/truststore-0.10.5.tar.gz", hash = "sha256:30d36967ccaded5cbb38d602c433f53600036c79d502f4533a49b60a03bbefcd", size = 28091, upload-time = "2026-10-12T22:27:31.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e9/3a7820be2bb0fe53b6bc9c3be26d3d1158004e4c3ab953aa6840b955b1e9/truststore-0.10.5-py3-none-any.whl", hash = "sha256:9aaaedaefaf06d8b206278cf8b5012bc897f485a874503501e12d776df78951c", size = 19017, upload-time = "2026-10-12T22:27:30.377Z" }, +] + [[package]] name = "typer" version = "0.27.2"