keystone-cli 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/PKG-INFO +2 -1
  2. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/pyproject.toml +5 -2
  3. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/__main__.py +4 -0
  4. keystone_cli-0.2.2/src/keystone_cli/_tls.py +40 -0
  5. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli.egg-info/PKG-INFO +2 -1
  6. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli.egg-info/SOURCES.txt +2 -0
  7. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli.egg-info/requires.txt +1 -0
  8. keystone_cli-0.2.2/tests/test_tls.py +125 -0
  9. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/setup.cfg +0 -0
  10. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/__init__.py +0 -0
  11. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/_build_env.py +0 -0
  12. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/_stdio.py +0 -0
  13. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/_version.py +0 -0
  14. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/agent_logs.py +0 -0
  15. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/agent_skills.py +0 -0
  16. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/auth/__init__.py +0 -0
  17. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/auth/device_flow.py +0 -0
  18. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/auth/token_store.py +0 -0
  19. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/commands/__init__.py +0 -0
  20. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/commands/agent.py +0 -0
  21. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/commands/dev.py +0 -0
  22. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/commands/kb.py +0 -0
  23. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/commands/kb_metadata.py +0 -0
  24. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/commands/login.py +0 -0
  25. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/commands/skills.py +0 -0
  26. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/commands/tool_credentials.py +0 -0
  27. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/commands/whoami.py +0 -0
  28. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/commands/workspace.py +0 -0
  29. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/devloop.py +0 -0
  30. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/platform_client.py +0 -0
  31. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/run_debug.py +0 -0
  32. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/runner.py +0 -0
  33. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/scaffold.py +0 -0
  34. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/skills/keystone-debug/SKILL.md +0 -0
  35. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/blank/README.md.tmpl +0 -0
  36. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/blank/agent.yaml.tmpl +0 -0
  37. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/blank/env.tmpl +0 -0
  38. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/blank/gitignore.tmpl +0 -0
  39. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/blank/pkg/__init__.py.tmpl +0 -0
  40. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/blank/pkg/graph.py.tmpl +0 -0
  41. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/blank/pyproject.toml.tmpl +0 -0
  42. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/guardrails/README.md.tmpl +0 -0
  43. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/guardrails/agent.yaml.tmpl +0 -0
  44. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/guardrails/env.tmpl +0 -0
  45. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/guardrails/gitignore.tmpl +0 -0
  46. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/guardrails/pkg/__init__.py.tmpl +0 -0
  47. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/guardrails/pkg/graph.py.tmpl +0 -0
  48. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/guardrails/pyproject.toml.tmpl +0 -0
  49. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/hitl/README.md.tmpl +0 -0
  50. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/hitl/agent.yaml.tmpl +0 -0
  51. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/hitl/env.tmpl +0 -0
  52. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/hitl/gitignore.tmpl +0 -0
  53. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/hitl/pkg/__init__.py.tmpl +0 -0
  54. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/hitl/pkg/graph.py.tmpl +0 -0
  55. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/hitl/pyproject.toml.tmpl +0 -0
  56. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/kb-builder/README.md.tmpl +0 -0
  57. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/kb-builder/agent.yaml.tmpl +0 -0
  58. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/kb-builder/env.tmpl +0 -0
  59. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/kb-builder/gitignore.tmpl +0 -0
  60. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/kb-builder/pkg/__init__.py.tmpl +0 -0
  61. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/kb-builder/pkg/graph.py.tmpl +0 -0
  62. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/kb-builder/pyproject.toml.tmpl +0 -0
  63. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/llm/README.md.tmpl +0 -0
  64. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/llm/agent.yaml.tmpl +0 -0
  65. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/llm/env.tmpl +0 -0
  66. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/llm/gitignore.tmpl +0 -0
  67. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/llm/pkg/__init__.py.tmpl +0 -0
  68. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/llm/pkg/graph.py.tmpl +0 -0
  69. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/llm/pyproject.toml.tmpl +0 -0
  70. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/llm-streaming/README.md.tmpl +0 -0
  71. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/llm-streaming/agent.yaml.tmpl +0 -0
  72. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/llm-streaming/env.tmpl +0 -0
  73. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/llm-streaming/gitignore.tmpl +0 -0
  74. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/llm-streaming/pkg/__init__.py.tmpl +0 -0
  75. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/llm-streaming/pkg/graph.py.tmpl +0 -0
  76. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/llm-streaming/pyproject.toml.tmpl +0 -0
  77. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/memory/README.md.tmpl +0 -0
  78. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/memory/agent.yaml.tmpl +0 -0
  79. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/memory/env.tmpl +0 -0
  80. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/memory/gitignore.tmpl +0 -0
  81. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/memory/pkg/__init__.py.tmpl +0 -0
  82. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/memory/pkg/graph.py.tmpl +0 -0
  83. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/memory/pyproject.toml.tmpl +0 -0
  84. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/memory-service/README.md.tmpl +0 -0
  85. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/memory-service/agent.yaml.tmpl +0 -0
  86. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/memory-service/env.tmpl +0 -0
  87. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/memory-service/gitignore.tmpl +0 -0
  88. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/memory-service/pkg/__init__.py.tmpl +0 -0
  89. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/memory-service/pkg/graph.py.tmpl +0 -0
  90. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/memory-service/pyproject.toml.tmpl +0 -0
  91. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/ocr/README.md.tmpl +0 -0
  92. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/ocr/agent.yaml.tmpl +0 -0
  93. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/ocr/env.tmpl +0 -0
  94. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/ocr/gitignore.tmpl +0 -0
  95. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/ocr/pkg/__init__.py.tmpl +0 -0
  96. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/ocr/pkg/graph.py.tmpl +0 -0
  97. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/ocr/pyproject.toml.tmpl +0 -0
  98. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/rag-qa/README.md.tmpl +0 -0
  99. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/rag-qa/agent.yaml.tmpl +0 -0
  100. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/rag-qa/env.tmpl +0 -0
  101. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/rag-qa/gitignore.tmpl +0 -0
  102. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/rag-qa/pkg/__init__.py.tmpl +0 -0
  103. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/rag-qa/pkg/graph.py.tmpl +0 -0
  104. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/rag-qa/pyproject.toml.tmpl +0 -0
  105. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/skill/README.md.tmpl +0 -0
  106. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/skill/agent.yaml.tmpl +0 -0
  107. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/skill/env.tmpl +0 -0
  108. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/skill/gitignore.tmpl +0 -0
  109. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/skill/pkg/__init__.py.tmpl +0 -0
  110. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/skill/pkg/graph.py.tmpl +0 -0
  111. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/skill/pyproject.toml.tmpl +0 -0
  112. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/tool-agent/README.md.tmpl +0 -0
  113. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/tool-agent/agent.yaml.tmpl +0 -0
  114. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/tool-agent/env.tmpl +0 -0
  115. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/tool-agent/gitignore.tmpl +0 -0
  116. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/tool-agent/pkg/__init__.py.tmpl +0 -0
  117. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/tool-agent/pkg/graph.py.tmpl +0 -0
  118. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli/templates/tool-agent/pyproject.toml.tmpl +0 -0
  119. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli.egg-info/dependency_links.txt +0 -0
  120. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli.egg-info/entry_points.txt +0 -0
  121. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/src/keystone_cli.egg-info/top_level.txt +0 -0
  122. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_agent.py +0 -0
  123. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_assistant_provenance_and_flags.py +0 -0
  124. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_auth.py +0 -0
  125. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_build_progress.py +0 -0
  126. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_ci_token.py +0 -0
  127. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_config_env_mismatch.py +0 -0
  128. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_deploy.py +0 -0
  129. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_dev.py +0 -0
  130. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_doctor.py +0 -0
  131. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_encoding.py +0 -0
  132. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_help_quality.py +0 -0
  133. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_hitl_approver_cli.py +0 -0
  134. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_hitl_decisions_cli.py +0 -0
  135. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_init.py +0 -0
  136. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_invoke.py +0 -0
  137. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_invoke_stream.py +0 -0
  138. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_kb_commands.py +0 -0
  139. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_kb_metadata_commands.py +0 -0
  140. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_login_command.py +0 -0
  141. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_logs.py +0 -0
  142. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_offline.py +0 -0
  143. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_platform_client.py +0 -0
  144. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_publish.py +0 -0
  145. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_retire_delete.py +0 -0
  146. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_run_debug.py +0 -0
  147. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_runs.py +0 -0
  148. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_skills.py +0 -0
  149. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_status.py +0 -0
  150. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_stdio.py +0 -0
  151. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_threads.py +0 -0
  152. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_tool_credentials.py +0 -0
  153. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_traces.py +0 -0
  154. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_version.py +0 -0
  155. {keystone_cli-0.2.1 → keystone_cli-0.2.2}/tests/test_workspace.py +0 -0
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keystone-cli
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Keystone pro-code agent CLI — build, test, deploy and operate agents on the Keystone platform.
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: keystone-agent-sdk<0.14,>=0.13.3
7
7
  Requires-Dist: keystone-request-context<0.4,>=0.3
8
8
  Requires-Dist: typer<1,>=0.12
9
9
  Requires-Dist: httpx<1,>=0.27
10
+ Requires-Dist: truststore<1,>=0.10
10
11
  Provides-Extra: dev
11
12
  Requires-Dist: pytest<9,>=8; extra == "dev"
12
13
  Requires-Dist: pytest-cov<7,>=6; extra == "dev"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "keystone-cli"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "Keystone pro-code agent CLI — build, test, deploy and operate agents on the Keystone platform."
9
9
  requires-python = ">=3.12"
10
10
  dependencies = [
@@ -18,6 +18,9 @@ dependencies = [
18
18
  "typer>=0.12,<1",
19
19
  # `login` (device-flow → Keycloak) + `deploy` (bundle upload → agent-hub via Kong). FDP-3170/3169.
20
20
  "httpx>=0.27,<1",
21
+ # FDP-6328 — verify TLS through the OS trust store (keystone_cli/_tls.py), so a corporate CA in
22
+ # the macOS Keychain / Windows store works without SSL_CERT_FILE. Pure Python, no dependencies.
23
+ "truststore>=0.10,<1",
21
24
  ]
22
25
 
23
26
  [project.optional-dependencies]
@@ -45,7 +48,7 @@ where = ["src"]
45
48
  keystone_cli = ["templates/*/*.tmpl", "templates/*/pkg/*.tmpl", "skills/*/SKILL.md"]
46
49
 
47
50
  [tool.ruff]
48
- target-version = "0.2.1"
51
+ target-version = "0.2.2"
49
52
  line-length = 120
50
53
 
51
54
  [tool.ruff.lint]
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import typer
6
6
 
7
7
  from keystone_cli._stdio import make_stdio_unicode_safe
8
+ from keystone_cli._tls import use_system_trust_store
8
9
  from keystone_cli._version import version_line
9
10
  from keystone_cli.commands.agent import agent_app
10
11
  from keystone_cli.commands.dev import dev
@@ -45,6 +46,9 @@ def _main(
45
46
  ),
46
47
  ) -> None:
47
48
  """Keystone pro-code agent CLI."""
49
+ # FDP-6328 — here, not at import: every command passes through this callback before it opens
50
+ # a connection, while merely importing the app (tests do) leaves the process's `ssl` alone.
51
+ use_system_trust_store()
48
52
 
49
53
 
50
54
  @app.command(name="version")
@@ -0,0 +1,40 @@
1
+ """Verify TLS against the operating system's trust store (FDP-6328).
2
+
3
+ httpx verifies servers against ``certifi``'s bundled CA list, never the OS store. On a network
4
+ that inspects TLS — TCB's, where a corporate proxy re-signs every HTTPS connection with the
5
+ bank's own CA — that CA is installed in the OS store (macOS Keychain, Windows certificate
6
+ store), so browsers work, but certifi does not know it. Every CLI command then failed with
7
+ ``CERTIFICATE_VERIFY_FAILED`` until the user exported ``SSL_CERT_FILE=<corp bundle>`` on each
8
+ call — which REPLACES certifi rather than adding to it, so hosts outside the proxy broke instead.
9
+
10
+ ``truststore`` makes Python's ``ssl`` verify through the OS (Security.framework on macOS,
11
+ CryptoAPI on Windows, OpenSSL with the system paths on Linux) — the approach pip takes for the
12
+ same reason. ``SSL_CERT_FILE`` keeps working on top: httpx hands it to ``load_verify_locations``,
13
+ which truststore adds as extra trust anchors (checked with a local CA and httpx 0.28.1: OS-trusted
14
+ and SSL_CERT_FILE-trusted servers both verify).
15
+
16
+ ``KEYSTONE_SYSTEM_TRUST=0`` turns it off (certifi only, the old behaviour), for the day the OS
17
+ store itself is the problem.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import os
23
+ from collections.abc import Mapping
24
+
25
+ import truststore
26
+
27
+ ENV_SYSTEM_TRUST = "KEYSTONE_SYSTEM_TRUST"
28
+ _OFF = {"0", "false", "no", "off"}
29
+
30
+
31
+ def use_system_trust_store(env: Mapping[str, str] | None = None) -> bool:
32
+ """Make every TLS connection this process opens verify through the OS trust store.
33
+
34
+ Call once, before any HTTP client exists (an ``httpx.Client`` builds its SSL context when it
35
+ is created). Returns whether it is on. Idempotent: truststore ignores a second injection."""
36
+ env = os.environ if env is None else env
37
+ if (env.get(ENV_SYSTEM_TRUST) or "").strip().lower() in _OFF:
38
+ return False
39
+ truststore.inject_into_ssl()
40
+ return True
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keystone-cli
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Keystone pro-code agent CLI — build, test, deploy and operate agents on the Keystone platform.
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: keystone-agent-sdk<0.14,>=0.13.3
7
7
  Requires-Dist: keystone-request-context<0.4,>=0.3
8
8
  Requires-Dist: typer<1,>=0.12
9
9
  Requires-Dist: httpx<1,>=0.27
10
+ Requires-Dist: truststore<1,>=0.10
10
11
  Provides-Extra: dev
11
12
  Requires-Dist: pytest<9,>=8; extra == "dev"
12
13
  Requires-Dist: pytest-cov<7,>=6; extra == "dev"
@@ -3,6 +3,7 @@ src/keystone_cli/__init__.py
3
3
  src/keystone_cli/__main__.py
4
4
  src/keystone_cli/_build_env.py
5
5
  src/keystone_cli/_stdio.py
6
+ src/keystone_cli/_tls.py
6
7
  src/keystone_cli/_version.py
7
8
  src/keystone_cli/agent_logs.py
8
9
  src/keystone_cli/agent_skills.py
@@ -145,6 +146,7 @@ tests/test_skills.py
145
146
  tests/test_status.py
146
147
  tests/test_stdio.py
147
148
  tests/test_threads.py
149
+ tests/test_tls.py
148
150
  tests/test_tool_credentials.py
149
151
  tests/test_traces.py
150
152
  tests/test_version.py
@@ -2,6 +2,7 @@ keystone-agent-sdk<0.14,>=0.13.3
2
2
  keystone-request-context<0.4,>=0.3
3
3
  typer<1,>=0.12
4
4
  httpx<1,>=0.27
5
+ truststore<1,>=0.10
5
6
 
6
7
  [dev]
7
8
  pytest<9,>=8
@@ -0,0 +1,125 @@
1
+ """FDP-6328 — TLS verifies through the OS trust store, so a corporate CA works without SSL_CERT_FILE."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import http.server
6
+ import shutil
7
+ import ssl
8
+ import subprocess
9
+ import threading
10
+ from collections.abc import Iterator
11
+ from pathlib import Path
12
+
13
+ import httpx
14
+ import pytest
15
+ import truststore
16
+ from typer.testing import CliRunner
17
+
18
+ from keystone_cli import _tls
19
+ from keystone_cli.__main__ import app
20
+
21
+ runner = CliRunner()
22
+
23
+
24
+ def _record_injections(monkeypatch) -> list[bool]:
25
+ calls: list[bool] = []
26
+ monkeypatch.setattr(_tls.truststore, "inject_into_ssl", lambda: calls.append(True))
27
+ return calls
28
+
29
+
30
+ def test_on_by_default(monkeypatch) -> None:
31
+ calls = _record_injections(monkeypatch)
32
+ assert _tls.use_system_trust_store(env={}) is True
33
+ assert calls == [True]
34
+
35
+
36
+ @pytest.mark.parametrize("value", ["0", "false", "No", " off "])
37
+ def test_opt_out_keeps_certifi(monkeypatch, value) -> None:
38
+ calls = _record_injections(monkeypatch)
39
+ assert _tls.use_system_trust_store(env={"KEYSTONE_SYSTEM_TRUST": value}) is False
40
+ assert calls == []
41
+
42
+
43
+ def test_every_command_turns_it_on(monkeypatch) -> None:
44
+ seen: list[bool] = []
45
+ monkeypatch.setattr("keystone_cli.__main__.use_system_trust_store", lambda: seen.append(True) or True)
46
+ result = runner.invoke(app, ["version"])
47
+ assert result.exit_code == 0, result.output
48
+ assert seen == [True]
49
+
50
+
51
+ # ── end to end: a server signed by a "corporate" CA, as a TLS-inspecting proxy presents ──
52
+
53
+
54
+ def _openssl(*args: str, cwd: Path) -> None:
55
+ subprocess.run(["openssl", *args], cwd=cwd, check=True, capture_output=True) # noqa: S603, S607
56
+
57
+
58
+ @pytest.fixture
59
+ def corp_server(tmp_path: Path) -> Iterator[tuple[str, Path]]:
60
+ if shutil.which("openssl") is None:
61
+ pytest.skip("openssl binary not available")
62
+ _openssl("req", "-x509", "-newkey", "rsa:2048", "-nodes", "-keyout", "ca.key", "-out", "ca.pem",
63
+ "-days", "2", "-subj", "/CN=Test Corp CA", cwd=tmp_path) # fmt: skip
64
+ _openssl("req", "-newkey", "rsa:2048", "-nodes", "-keyout", "srv.key", "-out", "srv.csr",
65
+ "-subj", "/CN=localhost", cwd=tmp_path) # fmt: skip
66
+ # macOS (Security.framework) refuses a server cert without serverAuth usage.
67
+ (tmp_path / "ext.cnf").write_text(
68
+ "subjectAltName=DNS:localhost,IP:127.0.0.1\nextendedKeyUsage=serverAuth\n"
69
+ "keyUsage=digitalSignature,keyEncipherment\nbasicConstraints=CA:FALSE\n",
70
+ encoding="utf-8",
71
+ )
72
+ _openssl("x509", "-req", "-in", "srv.csr", "-CA", "ca.pem", "-CAkey", "ca.key", "-CAcreateserial",
73
+ "-out", "srv.pem", "-days", "2", "-extfile", "ext.cnf", cwd=tmp_path) # fmt: skip
74
+
75
+ server = http.server.HTTPServer(("127.0.0.1", 0), http.server.BaseHTTPRequestHandler)
76
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
77
+ ctx.load_cert_chain(tmp_path / "srv.pem", tmp_path / "srv.key")
78
+ server.socket = ctx.wrap_socket(server.socket, server_side=True)
79
+ threading.Thread(target=server.serve_forever, daemon=True).start()
80
+ try:
81
+ yield f"https://localhost:{server.server_address[1]}/", tmp_path / "ca.pem"
82
+ finally:
83
+ server.shutdown()
84
+
85
+
86
+ @pytest.fixture
87
+ def system_trust(monkeypatch) -> Iterator[None]:
88
+ monkeypatch.delenv("KEYSTONE_SYSTEM_TRUST", raising=False)
89
+ assert _tls.use_system_trust_store() is True
90
+ try:
91
+ yield
92
+ finally:
93
+ truststore.extract_from_ssl() # undo the process-wide patch for the rest of the suite
94
+
95
+
96
+ def _verifies(url: str) -> bool:
97
+ try:
98
+ with httpx.Client(timeout=10) as client:
99
+ client.get(url) # any HTTP status is fine — only the handshake matters
100
+ except httpx.ConnectError as exc:
101
+ assert "certif" in str(exc).lower() or "trust" in str(exc).lower(), exc
102
+ return False
103
+ return True
104
+
105
+
106
+ def test_httpx_verifies_through_the_os_store_once_on(system_trust) -> None:
107
+ # httpx (0.27/0.28) builds its context with ssl.create_default_context(); after the injection
108
+ # that is truststore's, i.e. verification goes through Keychain / the Windows store.
109
+ assert isinstance(ssl.create_default_context(), truststore.SSLContext)
110
+ with httpx.Client() as client:
111
+ transport = client._transport # noqa: SLF001 — the only place httpx keeps the context
112
+ assert isinstance(transport._pool._ssl_context, truststore.SSLContext) # noqa: SLF001
113
+
114
+
115
+ def test_an_unknown_corporate_ca_is_still_rejected(corp_server, system_trust, monkeypatch) -> None:
116
+ url, _ca = corp_server
117
+ monkeypatch.delenv("SSL_CERT_FILE", raising=False)
118
+ assert _verifies(url) is False # the OS store is used, verification is NOT switched off
119
+
120
+
121
+ def test_ssl_cert_file_still_adds_a_ca_on_top_of_the_os_store(corp_server, system_trust, monkeypatch) -> None:
122
+ # Users who already export SSL_CERT_FILE (the 0.2.1 workaround) must not break on upgrade.
123
+ url, ca = corp_server
124
+ monkeypatch.setenv("SSL_CERT_FILE", str(ca))
125
+ assert _verifies(url) is True
File without changes