controlzero 1.13.5__tar.gz → 1.13.7__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 (309) hide show
  1. {controlzero-1.13.5 → controlzero-1.13.7}/CHANGELOG.md +51 -0
  2. {controlzero-1.13.5 → controlzero-1.13.7}/PKG-INFO +2 -1
  3. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/__init__.py +1 -1
  4. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/audit_remote.py +5 -0
  5. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/enforcement.py +31 -0
  6. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/main.py +170 -3
  7. controlzero-1.13.7/controlzero/cli/setup_wizard.py +481 -0
  8. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/spool_cmd.py +2 -0
  9. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/client.py +4 -3
  10. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/enrollment.py +8 -1
  11. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hitl/grant_protocol.py +3 -2
  12. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hook_integrity.py +10 -4
  13. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hosted_policy.py +93 -2
  14. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_keyring.py +65 -5
  15. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/tamper.py +2 -0
  16. {controlzero-1.13.5 → controlzero-1.13.7}/pyproject.toml +6 -1
  17. {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_keychain_dek.py +142 -7
  18. controlzero-1.13.7/tests/test_api_url_config.py +72 -0
  19. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_hook.py +9 -3
  20. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_coding_agent_hooks.py +16 -8
  21. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_5d_email_install.py +1 -1
  22. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_request_approval.py +1 -1
  23. controlzero-1.13.7/tests/test_hook_command_pinning_1705.py +326 -0
  24. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hook_integrity.py +21 -0
  25. controlzero-1.13.7/tests/test_install_api_url.py +69 -0
  26. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_install_hook_command.py +21 -14
  27. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_install_hooks.py +38 -29
  28. controlzero-1.13.7/tests/test_setup_wizard.py +196 -0
  29. controlzero-1.13.7/tests/test_tls_ca_bundle.py +112 -0
  30. {controlzero-1.13.5 → controlzero-1.13.7}/.gitignore +0 -0
  31. {controlzero-1.13.5 → controlzero-1.13.7}/Dockerfile.test +0 -0
  32. {controlzero-1.13.5 → controlzero-1.13.7}/LICENSE +0 -0
  33. {controlzero-1.13.5 → controlzero-1.13.7}/README.md +0 -0
  34. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/__main__.py +0 -0
  35. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/__init__.py +0 -0
  36. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/_messages.py +0 -0
  37. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/action_aliases.py +0 -0
  38. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/action_validator.py +0 -0
  39. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/bundle.py +0 -0
  40. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/condition_ops.py +0 -0
  41. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/credential_hook.py +0 -0
  42. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/credential_scanner.py +0 -0
  43. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/credentials_data/__init__.py +0 -0
  44. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
  45. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/dlp_scanner.py +0 -0
  46. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/enforcer.py +0 -0
  47. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/hook_extractors.py +0 -0
  48. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/io_capture_producer.py +0 -0
  49. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/source_labels.py +0 -0
  50. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/tool_extractors.json +0 -0
  51. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/_internal/types.py +0 -0
  52. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/audit_local.py +0 -0
  53. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/canonical.py +0 -0
  54. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/__init__.py +0 -0
  55. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/__main__.py +0 -0
  56. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/_secrets.py +0 -0
  57. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/agent_config.py +0 -0
  58. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/console.py +0 -0
  59. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/debug_bundle.py +0 -0
  60. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/doctor.py +0 -0
  61. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/exec_cmd.py +0 -0
  62. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hook_entry.py +0 -0
  63. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/__init__.py +0 -0
  64. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/antigravity.py +0 -0
  65. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/base.py +0 -0
  66. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/claude_code.py +0 -0
  67. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/codex_cli.py +0 -0
  68. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/cursor.py +0 -0
  69. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/cursor_cli.py +0 -0
  70. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/gemini_cli.py +0 -0
  71. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/kiro.py +0 -0
  72. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/hosts/unknown.py +0 -0
  73. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/kiro_adapter.py +0 -0
  74. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/migrate.py +0 -0
  75. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/output.py +0 -0
  76. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/telemetry_consent.py +0 -0
  77. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/antigravity/hooks.json +0 -0
  78. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/antigravity.yaml +0 -0
  79. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/autogen.yaml +0 -0
  80. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/claude-code.yaml +0 -0
  81. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/codex-cli.yaml +0 -0
  82. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/cost-cap.yaml +0 -0
  83. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/crewai.yaml +0 -0
  84. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/cursor.yaml +0 -0
  85. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/gemini-cli.yaml +0 -0
  86. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/generic.yaml +0 -0
  87. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
  88. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -0
  89. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
  90. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
  91. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/langchain.yaml +0 -0
  92. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/mcp.yaml +0 -0
  93. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/templates/rag.yaml +0 -0
  94. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/cli/update_cmd.py +0 -0
  95. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/device.py +0 -0
  96. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/error_codes.py +0 -0
  97. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/error_codes.yaml +0 -0
  98. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/errors.py +0 -0
  99. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hitl/__init__.py +0 -0
  100. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hitl/mock.py +0 -0
  101. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hitl/pending_approval.py +0 -0
  102. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hitl/secret_leak_guard.py +0 -0
  103. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hitl/status.py +0 -0
  104. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hooks/__init__.py +0 -0
  105. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/hooks/tool_output_handler.py +0 -0
  106. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/__init__.py +0 -0
  107. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/anthropic.py +0 -0
  108. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/autogen.py +0 -0
  109. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/braintrust.py +0 -0
  110. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/crewai/__init__.py +0 -0
  111. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/crewai/agent.py +0 -0
  112. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/crewai/crew.py +0 -0
  113. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/crewai/task.py +0 -0
  114. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/crewai/tool.py +0 -0
  115. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/google.py +0 -0
  116. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/google_adk/__init__.py +0 -0
  117. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/google_adk/agent.py +0 -0
  118. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/google_adk/tool.py +0 -0
  119. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langchain/__init__.py +0 -0
  120. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langchain/agent.py +0 -0
  121. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langchain/callbacks.py +0 -0
  122. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langchain/chain.py +0 -0
  123. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langchain/graph.py +0 -0
  124. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langchain/modern.py +0 -0
  125. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langchain/tool.py +0 -0
  126. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/langfuse.py +0 -0
  127. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/litellm.py +0 -0
  128. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/openai.py +0 -0
  129. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/pydantic_ai.py +0 -0
  130. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/integrations/vercel_ai.py +0 -0
  131. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/layout_migration.py +0 -0
  132. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/policy_loader.py +0 -0
  133. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/secrets/__init__.py +0 -0
  134. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/secrets/reference.py +0 -0
  135. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/secrets/resolver.py +0 -0
  136. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/secrets/secretstr.py +0 -0
  137. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/__init__.py +0 -0
  138. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_compress.py +0 -0
  139. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_constants.py +0 -0
  140. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_crc32c.py +0 -0
  141. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_crypto.py +0 -0
  142. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_frame.py +0 -0
  143. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_metrics.py +0 -0
  144. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_spool.py +0 -0
  145. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_state.py +0 -0
  146. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/_uploader.py +0 -0
  147. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/spool/cz-audit-v1.dict +0 -0
  148. {controlzero-1.13.5 → controlzero-1.13.7}/controlzero/tracecontext.py +0 -0
  149. {controlzero-1.13.5 → controlzero-1.13.7}/examples/hello_world.py +0 -0
  150. {controlzero-1.13.5 → controlzero-1.13.7}/tests/_fixtures/antigravity-envelope-expected.json +0 -0
  151. {controlzero-1.13.5 → controlzero-1.13.7}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
  152. {controlzero-1.13.5 → controlzero-1.13.7}/tests/conftest.py +0 -0
  153. {controlzero-1.13.5 → controlzero-1.13.7}/tests/fixtures/adapter-contracts/antigravity.json +0 -0
  154. {controlzero-1.13.5 → controlzero-1.13.7}/tests/fixtures/adapter-contracts/claude_code.json +0 -0
  155. {controlzero-1.13.5 → controlzero-1.13.7}/tests/fixtures/adapter-contracts/codex_cli.json +0 -0
  156. {controlzero-1.13.5 → controlzero-1.13.7}/tests/fixtures/adapter-contracts/cursor.json +0 -0
  157. {controlzero-1.13.5 → controlzero-1.13.7}/tests/fixtures/adapter-contracts/gemini_cli.json +0 -0
  158. {controlzero-1.13.5 → controlzero-1.13.7}/tests/fixtures/adapter-contracts/kiro.json +0 -0
  159. {controlzero-1.13.5 → controlzero-1.13.7}/tests/integrations/__init__.py +0 -0
  160. {controlzero-1.13.5 → controlzero-1.13.7}/tests/integrations/test_google.py +0 -0
  161. {controlzero-1.13.5 → controlzero-1.13.7}/tests/parity/action_aliases.json +0 -0
  162. {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/__init__.py +0 -0
  163. {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/conftest.py +0 -0
  164. {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_cli.py +0 -0
  165. {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_concurrency.py +0 -0
  166. {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_conformance.py +0 -0
  167. {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_core.py +0 -0
  168. {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_crash.py +0 -0
  169. {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_diskfull.py +0 -0
  170. {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_durable_default_tamper.py +0 -0
  171. {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_sink_wiring.py +0 -0
  172. {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_transcript_localack.py +0 -0
  173. {controlzero-1.13.5 → controlzero-1.13.7}/tests/spool/test_spool_uploader.py +0 -0
  174. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_action_aliases.py +0 -0
  175. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_action_canonicalization.py +0 -0
  176. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_action_validator_t86.py +0 -0
  177. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_adapter_contracts.py +0 -0
  178. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_adapter_interface.py +0 -0
  179. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_agent_name_env.py +0 -0
  180. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_agy_docs_no_exit2_1373.py +0 -0
  181. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_antigravity_adapter.py +0 -0
  182. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_antigravity_envelope_parity.py +0 -0
  183. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_antigravity_ga_blockers_1248.py +0 -0
  184. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_antigravity_hook_check.py +0 -0
  185. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_antigravity_install.py +0 -0
  186. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_antigravity_posttooluse_observe_58.py +0 -0
  187. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_antigravity_tool_vocab_1303.py +0 -0
  188. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_api_key_mask.py +0 -0
  189. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_audit_project_id_attribution_pr2.py +0 -0
  190. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_audit_remote.py +0 -0
  191. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_audit_remote_io_capture.py +0 -0
  192. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_audit_remote_sdk_version.py +0 -0
  193. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_audit_sink_isolation.py +0 -0
  194. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_bundle_cache_freshness_1303.py +0 -0
  195. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_bundle_parser.py +0 -0
  196. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_bundle_translate.py +0 -0
  197. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_canonical_phase1a.py +0 -0
  198. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_capability_matrix_doc.py +0 -0
  199. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_carve_out.py +0 -0
  200. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_debug_bundle.py +0 -0
  201. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_extractor_integration.py +0 -0
  202. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_hook_entry.py +0 -0
  203. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_hosted_refresh.py +0 -0
  204. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_init.py +0 -0
  205. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_init_templates.py +0 -0
  206. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_tail.py +0 -0
  207. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_test.py +0 -0
  208. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cli_validate.py +0 -0
  209. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_condition_operators_1495.py +0 -0
  210. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_conditions.py +0 -0
  211. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_config_format_parity_1303.py +0 -0
  212. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_conformance.py +0 -0
  213. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_console.py +0 -0
  214. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_credential_hook.py +0 -0
  215. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cross_adapter_shell_enforcement.py +0 -0
  216. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_cursor_adapter.py +0 -0
  217. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_default_action.py +0 -0
  218. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_deny_rule_reconciliation_1011.py +0 -0
  219. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_deny_wins_default_catchall_1620.py +0 -0
  220. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_device.py +0 -0
  221. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_dlp_mask.py +0 -0
  222. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_dlp_scanner.py +0 -0
  223. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_doctor.py +0 -0
  224. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_engine_version_consistency.py +0 -0
  225. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_enrollment.py +0 -0
  226. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_env_dump_438.py +0 -0
  227. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_epic_1247_bryan_acceptance.py +0 -0
  228. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_error_codes.py +0 -0
  229. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_errors_e_codes.py +0 -0
  230. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_fail_closed_eval.py +0 -0
  231. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_failopen_1303.py +0 -0
  232. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_glob_matching.py +0 -0
  233. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_cli_flag.py +0 -0
  234. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_exceptions.py +0 -0
  235. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
  236. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_mock_backend.py +0 -0
  237. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_pending_approval.py +0 -0
  238. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
  239. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_6a_wait.py +0 -0
  240. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_conformance.py +0 -0
  241. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_phase2b_protocol.py +0 -0
  242. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_reason_codes.py +0 -0
  243. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hitl_validator_keys.py +0 -0
  244. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hook_extractors.py +0 -0
  245. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hook_mask_failclosed.py +0 -0
  246. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hosted_local_audit_1247.py +0 -0
  247. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hosted_local_shadow_warn_1265.py +0 -0
  248. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hosted_policy_e2e.py +0 -0
  249. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hosts_adapter.py +0 -0
  250. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hybrid_mode_strict.py +0 -0
  251. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_hybrid_mode_warn.py +0 -0
  252. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_io_capture_producer.py +0 -0
  253. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_kiro_adapter.py +0 -0
  254. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_kiro_cli_e2e.py +0 -0
  255. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_kiro_cli_hook_pin_1265.py +0 -0
  256. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_kiro_cli_v210_hooks.py +0 -0
  257. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_kiro_hook_templates.py +0 -0
  258. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_kiro_install.py +0 -0
  259. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_kiro_install_standardisation.py +0 -0
  260. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_layout_migration_t101.py +0 -0
  261. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_layout_parity_t102.py +0 -0
  262. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_local_mode_dict.py +0 -0
  263. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_local_mode_file_json.py +0 -0
  264. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_local_mode_file_yaml.py +0 -0
  265. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_localized_reason.py +0 -0
  266. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_log_fallback_stderr.py +0 -0
  267. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_log_options_ignored_hosted.py +0 -0
  268. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_log_rotation.py +0 -0
  269. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_migrate.py +0 -0
  270. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_min_sdk_version_gate.py +0 -0
  271. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_multi_client_per_project_175.py +0 -0
  272. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_no_policy_no_key.py +0 -0
  273. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_observe_mode_1247.py +0 -0
  274. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_package_rename_shim.py +0 -0
  275. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_part3_active_policy_count_1303.py +0 -0
  276. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_per_client_isolation_1011.py +0 -0
  277. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_policy_engine_version_phase1b.py +0 -0
  278. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_policy_freshness.py +0 -0
  279. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_policy_load_failclosed_f3.py +0 -0
  280. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_policy_settings.py +0 -0
  281. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_policy_source_audit.py +0 -0
  282. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_pr4_install_messaging.py +0 -0
  283. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_pr4_output.py +0 -0
  284. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_pr4_status.py +0 -0
  285. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_probe_audit_isolation.py +0 -0
  286. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_quarantine.py +0 -0
  287. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_reason_code.py +0 -0
  288. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_refresh.py +0 -0
  289. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_secrets.py +0 -0
  290. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_secrets_reference_resolver.py +0 -0
  291. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_shell_semantic_class.py +0 -0
  292. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_source_labels.py +0 -0
  293. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_sql_semantic_class.py +0 -0
  294. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_synthetic_policy_id_t79.py +0 -0
  295. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_t103_precedence.py +0 -0
  296. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_t104_cache_gc.py +0 -0
  297. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_t108_local_override_audit.py +0 -0
  298. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_t96_single_audit_log.py +0 -0
  299. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_t99_install_prefetch_bundle.py +0 -0
  300. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_tamper.py +0 -0
  301. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_tamper_behavior.py +0 -0
  302. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_tamper_hook.py +0 -0
  303. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_telemetry_consent.py +0 -0
  304. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_tracecontext.py +0 -0
  305. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_unsafe_int_boundary.py +0 -0
  306. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_update_cmd.py +0 -0
  307. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_upgrade_nudge.py +0 -0
  308. {controlzero-1.13.5 → controlzero-1.13.7}/tests/test_version_aware_fail_closed.py +0 -0
  309. {controlzero-1.13.5 → controlzero-1.13.7}/tools/cz-kiro-adapter +0 -0
@@ -1,5 +1,56 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.13.6 (2026-07-12): interpreter-pinned hook command (P0)
4
+
5
+ ### Fixed
6
+
7
+ - **Claude Code / Gemini CLI / Codex CLI hook command is now interpreter-pinned,
8
+ not PATH-dependent (#1705, P0).** `controlzero install` wired the PreToolUse
9
+ hook as a bare `controlzero hook-check`; if a stale/older `controlzero`
10
+ binary (pre-`hook-check`) sat earlier on PATH than the interpreter that ran
11
+ install, the hook resolved to the wrong binary, errored with `invalid
12
+ choice: 'hook-check'`, and Claude Code HARD-BLOCKED every tool call. The
13
+ installer now writes `"<sys.executable>" -m controlzero hook-check` (the
14
+ same PATH-immune form #1265 shipped for Cursor / kiro-cli). The legacy bare
15
+ form is still recognised by hook-integrity / `verify` for back-compat, and
16
+ `verify` now WARNs (does not fail) when it finds a pre-#1705 bare command
17
+ still installed, recommending a re-`install` to migrate.
18
+
19
+ ## 1.13.5 (2026-07-06): HITL key-authed approval path + kiro-cli hook misattribution fix
20
+
21
+ ### Fixed
22
+
23
+ - **SDK-driven HITL now works for key-authed agents (#1601, P0).**
24
+ `request_approval` POST `/api/approval-requests` and poll GET
25
+ `/api/approval-requests/{id}` sat under the browser/session `userAuth`
26
+ chain, so a `cz_live` API key got a 401 and HITL was non-functional for
27
+ real agents. Added a key-authed POST/GET `/v1/sdk/approval-requests`
28
+ surface (mirrors the already-working `/api/grants` precedent); org and
29
+ project resolve from the key, and the project is pinned to the key on
30
+ both create AND poll (an agent cannot target or read another project's
31
+ approval requests). The Python SDK now points at the key-authed path.
32
+ - **E1709 leak-guard false-positive on ordinary English (#1601, P1).** The
33
+ entropy heuristic (`len>=24` + `entropy>3.5`) flagged plain text like the
34
+ default reason `"function requires approval"`, and
35
+ `enforce`/`guard_with_approval` swallowed the resulting
36
+ `SecretValueLeakInPayload` into a misleading `HITL_BACKEND_UNREACHABLE`
37
+ deny. The entropy heuristic is now gated on no-whitespace (a real
38
+ credential is a contiguous token), and the leak error propagates
39
+ distinctly instead of degrading to a backend-unreachable deny.
40
+ - **Kiro CLI hooks misattributed to Codex CLI, mis-scoring the audit source
41
+ (#1655).** Kiro CLI is built on Codex and exports `CODEX_HOME`/
42
+ `CODEX_CLI`, so `CodexCLIAdapter.claim()` was winning on those env hints
43
+ before `KiroCLIAdapter` could check. `CodexCLIAdapter` now defers when
44
+ `hook_event_name` is a Kiro camelCase event (`preToolUse`, `postToolUse`,
45
+ `userPromptSubmit`, `agentSpawn`, `stop`) -- byte-disjoint from Codex's
46
+ PascalCase `PreToolUse`.
47
+ - **Kiro IDE pre-tool-use hook UX.** The opaque "empty/unparseable IDE
48
+ payload; blocking" message now names the upstream bug (#7375/#7408/#7500),
49
+ explains the cause, and directs the user to disable the hook in Kiro IDE
50
+ Settings > Hooks. The shipped `ide-pre-tool-use.kiro.hook` template
51
+ name/description now leads with "[DO NOT ENABLE: blocks all tool calls]"
52
+ instead of burying the warning.
53
+
3
54
  ## 1.13.4 (2026-07-05): deny-wins fail-open fix (P0)
4
55
 
5
56
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: controlzero
3
- Version: 1.13.5
3
+ Version: 1.13.7
4
4
  Summary: AI agent governance: policies, audit, and observability for tool calls. Works locally with no signup.
5
5
  Project-URL: Homepage, https://controlzero.ai
6
6
  Project-URL: Documentation, https://docs.controlzero.ai
@@ -28,6 +28,7 @@ Requires-Dist: httpx>=0.25.0
28
28
  Requires-Dist: loguru>=0.7.0
29
29
  Requires-Dist: pydantic>=2.0.0
30
30
  Requires-Dist: pyyaml>=6.0
31
+ Requires-Dist: questionary<3,>=2.0.0
31
32
  Requires-Dist: rfc8785<0.2,>=0.1.4
32
33
  Requires-Dist: rich>=13.0.0
33
34
  Requires-Dist: zstandard>=0.23.0
@@ -41,7 +41,7 @@ from controlzero.hitl.grant_protocol import (
41
41
  from controlzero.policy_loader import load_policy
42
42
  from controlzero.secrets import SecretRef, SecretStr, secret_ref
43
43
 
44
- __version__ = "1.13.5"
44
+ __version__ = "1.13.7"
45
45
 
46
46
  __all__ = [
47
47
  "Client",
@@ -18,6 +18,7 @@ Design constraints:
18
18
  """
19
19
 
20
20
  from __future__ import annotations
21
+ from .hosted_policy import tls_verify
21
22
 
22
23
  import getpass
23
24
  import json
@@ -968,6 +969,7 @@ class RemoteAuditSink(_SpoolWiringMixin):
968
969
  content=body,
969
970
  headers=headers,
970
971
  timeout=FLUSH_TIMEOUT_S,
972
+ verify=tls_verify(),
971
973
  )
972
974
  if resp.status_code == 401:
973
975
  raise _AuthExpiredError("server returned 401")
@@ -1034,6 +1036,7 @@ class RemoteAuditSink(_SpoolWiringMixin):
1034
1036
  content=body,
1035
1037
  headers=headers,
1036
1038
  timeout=FLUSH_TIMEOUT_S,
1039
+ verify=tls_verify(),
1037
1040
  )
1038
1041
  return post
1039
1042
 
@@ -1192,6 +1195,7 @@ class BearerAuditSink(_SpoolWiringMixin):
1192
1195
  content=body,
1193
1196
  headers=headers,
1194
1197
  timeout=FLUSH_TIMEOUT_S,
1198
+ verify=tls_verify(),
1195
1199
  )
1196
1200
  if resp.status_code == 401:
1197
1201
  raise _AuthExpiredError("server returned 401")
@@ -1244,6 +1248,7 @@ class BearerAuditSink(_SpoolWiringMixin):
1244
1248
  content=body,
1245
1249
  headers=headers,
1246
1250
  timeout=FLUSH_TIMEOUT_S,
1251
+ verify=tls_verify(),
1247
1252
  )
1248
1253
  return post
1249
1254
 
@@ -859,3 +859,34 @@ def installed_command(adapter: str, home: Optional[Path] = None, cwd: Optional[P
859
859
  return _first_cz_command(_load_json(settings), ("preToolUse",))
860
860
  return None
861
861
  return None
862
+
863
+
864
+ # ---------------------------------------------------------------------------
865
+ # Bare, PATH-shadowable hook command detection (#1705).
866
+ # ---------------------------------------------------------------------------
867
+
868
+ # Adapters whose installer now emits the interpreter-pinned
869
+ # `"<sys.executable>" -m controlzero hook-check` form (#1705). A bare
870
+ # `controlzero hook-check` found installed for one of these is either a
871
+ # pre-#1705 install or a hand-edited config -- both are PATH-shadowable (a
872
+ # stale/older `controlzero` earlier on PATH silently resolves instead of the
873
+ # interpreter that ran install) and should be flagged so the operator
874
+ # re-runs `controlzero install` to migrate. Cursor / Kiro CLI / Kiro v3
875
+ # already pinned before #1705 (#1265); Antigravity intentionally still emits
876
+ # the bare form and is not covered by this check.
877
+ BARE_HOOK_COMMAND_ADAPTERS = ("claude_code", "gemini_cli", "codex_cli")
878
+
879
+ # The literal bare command these adapters wrote before #1705.
880
+ LEGACY_BARE_HOOK_CHECK_COMMAND = "controlzero hook-check"
881
+
882
+
883
+ def is_bare_shadowable_hook_command(command: Optional[str]) -> bool:
884
+ """True if ``command`` is exactly the historical bare ``controlzero
885
+ hook-check`` form (#1705): PATH-dependent, so a stale/older
886
+ ``controlzero`` binary earlier on PATH can silently resolve instead of
887
+ the interpreter that ran install. The current installer emits the
888
+ interpreter-pinned ``"<sys.executable>" -m controlzero hook-check`` form
889
+ instead; this only flags configs that predate that fix (or were
890
+ hand-edited back to bare). ``None`` (no command installed) is not bare.
891
+ """
892
+ return command == LEGACY_BARE_HOOK_CHECK_COMMAND
@@ -270,6 +270,43 @@ def init(template: str, out: str, force: bool):
270
270
  click.echo(f" 4. Use it in code: Client(policy_file='{out_path}')")
271
271
 
272
272
 
273
+ @cli.command("setup", hidden=True)
274
+ @click.option("--api-url", default=None,
275
+ help="Self-hosted / air-gap backend URL or host (e.g. https://cz.internal). "
276
+ "Presence selects self-hosted mode; persisted to config.yaml.")
277
+ @click.option("--api-key", default=None,
278
+ help="Control Zero API key (cz_live_/cz_test_). Env: CONTROLZERO_API_KEY.")
279
+ @click.option("--agent", "agents", multiple=True,
280
+ help="Coding agent to wire (repeatable): claude-code, codex-cli, gemini-cli, "
281
+ "kiro, cursor, antigravity. Env: CONTROLZERO_SETUP_AGENTS (comma list).")
282
+ @click.option("--tls-ca-bundle", default=None,
283
+ help="CA bundle path for a self-signed air-gap endpoint. Persisted to config.")
284
+ @click.option("--email", default=None, help="Your work email (Approvals attribution).")
285
+ @click.option("--yes", "assume_yes", is_flag=True,
286
+ help="Non-interactive: resolve from flags/env, never prompt.")
287
+ @click.pass_context
288
+ def setup(ctx, api_url, api_key, agents, tls_ca_bundle, email, assume_yes):
289
+ """Guided onboarding: pick SaaS vs self-hosted, validate, wire agent hooks.
290
+
291
+ Interactive in a TTY; degrades to flags/env (exit 2 on missing input) in
292
+ CI/non-TTY. Hidden/beta -- existing install commands are unchanged.
293
+ """
294
+ from controlzero.cli.setup_wizard import Flags, run_setup
295
+
296
+ code = run_setup(
297
+ ctx,
298
+ Flags(
299
+ api_url=api_url,
300
+ api_key=api_key,
301
+ agents=tuple(agents),
302
+ tls_ca_bundle=tls_ca_bundle,
303
+ email=email,
304
+ assume_yes=assume_yes,
305
+ ),
306
+ )
307
+ sys.exit(code)
308
+
309
+
273
310
  @cli.command()
274
311
  @click.argument("policy_file", type=click.Path(exists=True), default="controlzero.yaml")
275
312
  def validate(policy_file: str):
@@ -1320,6 +1357,7 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
1320
1357
  if prove:
1321
1358
  sys.exit(_run_prove_enforcement(as_json))
1322
1359
 
1360
+ from controlzero.cli import enforcement
1323
1361
  from controlzero.hook_integrity import (
1324
1362
  baseline_exists,
1325
1363
  check_hook_integrity,
@@ -1332,6 +1370,15 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
1332
1370
  has_baseline = baseline_exists(GLOBAL_POLICY_DIR)
1333
1371
  tamper = bool(hook_findings or env_findings)
1334
1372
 
1373
+ # #1705: a bare, PATH-shadowable `controlzero hook-check` predates the
1374
+ # interpreter-pinned fix (or was hand-edited back to bare). Not tamper --
1375
+ # it is a recognised, working CZ hook -- so it does not affect `tamper` /
1376
+ # the exit code, only surfaces as a WARN recommending re-install.
1377
+ bare_adapters = [
1378
+ a for a in enforcement.BARE_HOOK_COMMAND_ADAPTERS
1379
+ if enforcement.is_bare_shadowable_hook_command(enforcement.installed_command(a))
1380
+ ]
1381
+
1335
1382
  if tamper and report:
1336
1383
  _report_hook_tamper(hook_findings, env_findings)
1337
1384
 
@@ -1342,6 +1389,7 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
1342
1389
  "baseline_tampered": integrity.baseline_tampered,
1343
1390
  "hook_findings": hook_findings,
1344
1391
  "env_findings": env_findings,
1392
+ "bare_hook_command_adapters": bare_adapters,
1345
1393
  }, indent=2))
1346
1394
  else:
1347
1395
  click.echo("Control Zero tamper-resistance verify")
@@ -1364,6 +1412,14 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
1364
1412
  click.echo(f" - [env] {f['name']}: {f['reason']}")
1365
1413
  else:
1366
1414
  click.secho(" status : OK (hooks intact)", fg="green")
1415
+ for a in bare_adapters:
1416
+ click.secho(
1417
+ f" ~ WARNING: {a} hook command is the bare, PATH-shadowable "
1418
+ "'controlzero hook-check' form -- re-run `controlzero install "
1419
+ f"{a.replace('_', '-')}` to migrate to the interpreter-pinned "
1420
+ "form (#1705).",
1421
+ fg="yellow",
1422
+ )
1367
1423
 
1368
1424
  if not has_baseline:
1369
1425
  sys.exit(4)
@@ -1427,6 +1483,29 @@ _ANTIGRAVITY_PRETOOL_MATCHER = "*" # agy special-cases "*" (NOT RE2-compiled
1427
1483
  _KIRO_POSTTOOL_MATCHER = "*" # observe-only postToolUse match-all
1428
1484
 
1429
1485
 
1486
+ def _pinned_hook_check_command() -> str:
1487
+ """Interpreter-pinned `controlzero hook-check` command for Claude Code /
1488
+ Gemini CLI / Codex CLI (#1705).
1489
+
1490
+ A bare ``controlzero hook-check`` is PATH-dependent: the agent's hook
1491
+ subshell resolves ``controlzero`` against ITS PATH at hook-execution time,
1492
+ not against the interpreter that ran ``install``. If a stale/older
1493
+ ``controlzero`` binary (one whose only subcommands are
1494
+ ``setup``/``status``/``uninstall``, i.e. pre-``hook-check``) sits earlier
1495
+ on PATH than the one installed into this interpreter's venv, the hook
1496
+ resolves to the wrong binary, prints ``invalid choice: 'hook-check'``,
1497
+ exits non-zero, and the agent HARD-BLOCKS every tool call -- bricking the
1498
+ session. This actually happened (#1705).
1499
+
1500
+ Pinning ``sys.executable -m controlzero`` -- the same fix #1265 shipped
1501
+ for Cursor / kiro-cli -- makes the agent invoke the EXACT interpreter that
1502
+ ran install, which provably has ``hook-check``, regardless of PATH or venv
1503
+ activation. Double-quoted so a space in the interpreter path survives
1504
+ /bin/sh and cmd.exe.
1505
+ """
1506
+ return f'"{sys.executable}" -m controlzero hook-check'
1507
+
1508
+
1430
1509
  @cli.command("hook-check")
1431
1510
  @click.option(
1432
1511
  "--policy",
@@ -3566,6 +3645,45 @@ def _print_api_key_status(api_key: Optional[str]) -> None:
3566
3645
  click.echo("Tip: Pass --api-key cz_live_xxx to sync audit logs to the dashboard.")
3567
3646
 
3568
3647
 
3648
+ def _write_api_url_config(api_url: str) -> None:
3649
+ """Persist ``api_url`` to ~/.controlzero/config.yaml (0600). The resolver
3650
+ reads it (env > config > default), so a self-hosted / air-gap endpoint set
3651
+ once at install reaches the agent hook WITHOUT exporting CONTROLZERO_API_URL
3652
+ into every hook subprocess (the historical air-gap footgun). Scheme+host
3653
+ only; the SDK strips a trailing slash and appends request paths itself."""
3654
+ GLOBAL_POLICY_DIR.mkdir(parents=True, exist_ok=True)
3655
+ try:
3656
+ os.chmod(GLOBAL_POLICY_DIR, 0o700)
3657
+ except OSError:
3658
+ pass
3659
+ config_data: dict = {}
3660
+ if GLOBAL_CONFIG_PATH.exists():
3661
+ try:
3662
+ existing = yaml.safe_load(GLOBAL_CONFIG_PATH.read_text(encoding="utf-8"))
3663
+ if isinstance(existing, dict):
3664
+ config_data = existing
3665
+ except Exception: # noqa: BLE001
3666
+ pass
3667
+ config_data["api_url"] = api_url.rstrip("/")
3668
+ GLOBAL_CONFIG_PATH.write_text(
3669
+ yaml.safe_dump(config_data, default_flow_style=False),
3670
+ encoding="utf-8",
3671
+ )
3672
+ try:
3673
+ os.chmod(GLOBAL_CONFIG_PATH, 0o600)
3674
+ except OSError:
3675
+ pass
3676
+
3677
+
3678
+ def _api_url_install_callback(ctx, param, value):
3679
+ """click callback for the shared --api-url install option. Persists the URL
3680
+ at install time; expose_value=False keeps it off the command signature so no
3681
+ install function needs an api_url parameter."""
3682
+ if value:
3683
+ _write_api_url_config(value)
3684
+ return value
3685
+
3686
+
3569
3687
  def _bind_api_key_for_install(
3570
3688
  api_key: str,
3571
3689
  product: str,
@@ -3641,6 +3759,13 @@ def install():
3641
3759
  default=None,
3642
3760
  help="Your work email. Required for Approvals so the backend knows who's at the keyboard when the API key is shared across a team. Stored at ~/.controlzero/config.yaml.",
3643
3761
  )
3762
+ @click.option(
3763
+ "--api-url",
3764
+ default=None,
3765
+ expose_value=False,
3766
+ callback=_api_url_install_callback,
3767
+ help="Base URL of a self-hosted / air-gap Control Zero backend (e.g. https://cz.internal). Persisted to ~/.controlzero/config.yaml so the hook targets it; overrides the SaaS default. env CONTROLZERO_API_URL still wins.",
3768
+ )
3644
3769
  def install_claude_code(force: bool, merge: bool, settings: Optional[str], api_key: Optional[str], email: Optional[str]):
3645
3770
  """Install controlzero as a Claude Code PreToolUse hook.
3646
3771
 
@@ -3736,7 +3861,10 @@ def install_claude_code(force: bool, merge: bool, settings: Optional[str], api_k
3736
3861
  # env-prefix was a Windows-portability bug (PowerShell / cmd.exe cannot
3737
3862
  # parse the syntax) AND placed the cleartext key into settings.json on
3738
3863
  # disk. Both go away here. See 2026-05-12 incident.
3739
- hook_command = "controlzero hook-check"
3864
+ # Interpreter-pinned, not a bare `controlzero hook-check` -- see
3865
+ # _pinned_hook_check_command() (#1705: a stale controlzero earlier on
3866
+ # PATH otherwise hard-blocks every tool call).
3867
+ hook_command = _pinned_hook_check_command()
3740
3868
  new_block = {
3741
3869
  "matcher": _CLAUDE_CODE_PRETOOL_MATCHER,
3742
3870
  "hooks": [
@@ -3828,6 +3956,13 @@ def install_claude_code(force: bool, merge: bool, settings: Optional[str], api_k
3828
3956
  default=None,
3829
3957
  help="Your work email. Required for Approvals so the backend knows who's at the keyboard when the API key is shared across a team.",
3830
3958
  )
3959
+ @click.option(
3960
+ "--api-url",
3961
+ default=None,
3962
+ expose_value=False,
3963
+ callback=_api_url_install_callback,
3964
+ help="Base URL of a self-hosted / air-gap Control Zero backend (e.g. https://cz.internal). Persisted to ~/.controlzero/config.yaml so the hook targets it; overrides the SaaS default. env CONTROLZERO_API_URL still wins.",
3965
+ )
3831
3966
  def install_gemini_cli(force: bool, merge: bool, settings: Optional[str], api_key: Optional[str], email: Optional[str]):
3832
3967
  """Install controlzero as a Gemini CLI pre-tool-call hook.
3833
3968
 
@@ -3913,7 +4048,9 @@ def install_gemini_cli(force: bool, merge: bool, settings: Optional[str], api_ke
3913
4048
  # _write_api_key_config above). The legacy bash-only env-prefix
3914
4049
  # broke Windows; see the claude-code install path for the full
3915
4050
  # rationale.
3916
- hook_command = "controlzero hook-check"
4051
+ # Interpreter-pinned, not a bare `controlzero hook-check` (#1705); see
4052
+ # _pinned_hook_check_command().
4053
+ hook_command = _pinned_hook_check_command()
3917
4054
 
3918
4055
  # Gemini CLI expects a nested structure:
3919
4056
  # { matcher: "regex", hooks: [{ type: "command", command: "...", timeout: N }] }
@@ -4038,6 +4175,13 @@ def install_gemini_cli(force: bool, merge: bool, settings: Optional[str], api_ke
4038
4175
  default=None,
4039
4176
  help="Your work email. Required for Approvals so the backend knows who's at the keyboard when the API key is shared across a team.",
4040
4177
  )
4178
+ @click.option(
4179
+ "--api-url",
4180
+ default=None,
4181
+ expose_value=False,
4182
+ callback=_api_url_install_callback,
4183
+ help="Base URL of a self-hosted / air-gap Control Zero backend (e.g. https://cz.internal). Persisted to ~/.controlzero/config.yaml so the hook targets it; overrides the SaaS default. env CONTROLZERO_API_URL still wins.",
4184
+ )
4041
4185
  def install_codex_cli(force: bool, merge: bool, config: Optional[str], api_key: Optional[str], email: Optional[str]):
4042
4186
  """Install controlzero as a Codex CLI tool approval hook.
4043
4187
 
@@ -4113,7 +4257,9 @@ def install_codex_cli(force: bool, merge: bool, config: Optional[str], api_key:
4113
4257
  # _write_api_key_config above). The legacy bash-only env-prefix
4114
4258
  # broke Windows; see the claude-code install path for the full
4115
4259
  # rationale.
4116
- hook_command = "controlzero hook-check"
4260
+ # Interpreter-pinned, not a bare `controlzero hook-check` (#1705); see
4261
+ # _pinned_hook_check_command().
4262
+ hook_command = _pinned_hook_check_command()
4117
4263
 
4118
4264
  new_hook_entry = {
4119
4265
  "matcher": _CODEX_PRETOOL_MATCHER,
@@ -4571,6 +4717,13 @@ def _cursor_enforcement_probe(installed_cmd: str) -> tuple:
4571
4717
  help="Your work email. Required for Approvals so the backend knows who is "
4572
4718
  "at the keyboard when the API key is shared across a team.",
4573
4719
  )
4720
+ @click.option(
4721
+ "--api-url",
4722
+ default=None,
4723
+ expose_value=False,
4724
+ callback=_api_url_install_callback,
4725
+ help="Base URL of a self-hosted / air-gap Control Zero backend (e.g. https://cz.internal). Persisted to ~/.controlzero/config.yaml so the hook targets it; overrides the SaaS default. env CONTROLZERO_API_URL still wins.",
4726
+ )
4574
4727
  def install_cursor(
4575
4728
  force: bool,
4576
4729
  merge: bool,
@@ -4785,6 +4938,13 @@ def install_cursor(
4785
4938
  default=None,
4786
4939
  help="Your work email. Required for Approvals so the backend knows who's at the keyboard when the API key is shared across a team.",
4787
4940
  )
4941
+ @click.option(
4942
+ "--api-url",
4943
+ default=None,
4944
+ expose_value=False,
4945
+ callback=_api_url_install_callback,
4946
+ help="Base URL of a self-hosted / air-gap Control Zero backend (e.g. https://cz.internal). Persisted to ~/.controlzero/config.yaml so the hook targets it; overrides the SaaS default. env CONTROLZERO_API_URL still wins.",
4947
+ )
4788
4948
  def install_antigravity(
4789
4949
  force: bool,
4790
4950
  merge: bool,
@@ -6215,6 +6375,13 @@ def _run_kiro_uninstall(
6215
6375
  is_flag=True,
6216
6376
  help="Print what would change without writing anything to disk.",
6217
6377
  )
6378
+ @click.option(
6379
+ "--api-url",
6380
+ default=None,
6381
+ expose_value=False,
6382
+ callback=_api_url_install_callback,
6383
+ help="Base URL of a self-hosted / air-gap Control Zero backend (e.g. https://cz.internal). Persisted to ~/.controlzero/config.yaml so the hook targets it; overrides the SaaS default. env CONTROLZERO_API_URL still wins.",
6384
+ )
6218
6385
  def install_kiro(
6219
6386
  surface: str,
6220
6387
  workspace: Optional[str],