controlzero 1.13.5__tar.gz → 1.13.6__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 (304) hide show
  1. {controlzero-1.13.5 → controlzero-1.13.6}/CHANGELOG.md +51 -0
  2. {controlzero-1.13.5 → controlzero-1.13.6}/PKG-INFO +1 -1
  3. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/__init__.py +1 -1
  4. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/enforcement.py +31 -0
  5. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/main.py +52 -3
  6. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hook_integrity.py +10 -4
  7. {controlzero-1.13.5 → controlzero-1.13.6}/pyproject.toml +1 -1
  8. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_hook.py +9 -3
  9. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_coding_agent_hooks.py +16 -8
  10. controlzero-1.13.6/tests/test_hook_command_pinning_1705.py +326 -0
  11. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hook_integrity.py +21 -0
  12. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_install_hook_command.py +21 -14
  13. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_install_hooks.py +38 -29
  14. {controlzero-1.13.5 → controlzero-1.13.6}/.gitignore +0 -0
  15. {controlzero-1.13.5 → controlzero-1.13.6}/Dockerfile.test +0 -0
  16. {controlzero-1.13.5 → controlzero-1.13.6}/LICENSE +0 -0
  17. {controlzero-1.13.5 → controlzero-1.13.6}/README.md +0 -0
  18. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/__main__.py +0 -0
  19. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/__init__.py +0 -0
  20. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/_messages.py +0 -0
  21. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/action_aliases.py +0 -0
  22. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/action_validator.py +0 -0
  23. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/bundle.py +0 -0
  24. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/condition_ops.py +0 -0
  25. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/credential_hook.py +0 -0
  26. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/credential_scanner.py +0 -0
  27. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/credentials_data/__init__.py +0 -0
  28. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
  29. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/dlp_scanner.py +0 -0
  30. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/enforcer.py +0 -0
  31. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/hook_extractors.py +0 -0
  32. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/io_capture_producer.py +0 -0
  33. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/source_labels.py +0 -0
  34. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/tool_extractors.json +0 -0
  35. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/_internal/types.py +0 -0
  36. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/audit_local.py +0 -0
  37. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/audit_remote.py +0 -0
  38. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/canonical.py +0 -0
  39. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/__init__.py +0 -0
  40. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/__main__.py +0 -0
  41. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/_secrets.py +0 -0
  42. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/agent_config.py +0 -0
  43. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/console.py +0 -0
  44. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/debug_bundle.py +0 -0
  45. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/doctor.py +0 -0
  46. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/exec_cmd.py +0 -0
  47. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hook_entry.py +0 -0
  48. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/__init__.py +0 -0
  49. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/antigravity.py +0 -0
  50. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/base.py +0 -0
  51. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/claude_code.py +0 -0
  52. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/codex_cli.py +0 -0
  53. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/cursor.py +0 -0
  54. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/cursor_cli.py +0 -0
  55. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/gemini_cli.py +0 -0
  56. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/kiro.py +0 -0
  57. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/hosts/unknown.py +0 -0
  58. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/kiro_adapter.py +0 -0
  59. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/migrate.py +0 -0
  60. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/output.py +0 -0
  61. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/spool_cmd.py +0 -0
  62. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/telemetry_consent.py +0 -0
  63. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/antigravity/hooks.json +0 -0
  64. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/antigravity.yaml +0 -0
  65. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/autogen.yaml +0 -0
  66. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/claude-code.yaml +0 -0
  67. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/codex-cli.yaml +0 -0
  68. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/cost-cap.yaml +0 -0
  69. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/crewai.yaml +0 -0
  70. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/cursor.yaml +0 -0
  71. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/gemini-cli.yaml +0 -0
  72. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/generic.yaml +0 -0
  73. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
  74. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -0
  75. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
  76. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
  77. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/langchain.yaml +0 -0
  78. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/mcp.yaml +0 -0
  79. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/templates/rag.yaml +0 -0
  80. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/cli/update_cmd.py +0 -0
  81. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/client.py +0 -0
  82. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/device.py +0 -0
  83. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/enrollment.py +0 -0
  84. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/error_codes.py +0 -0
  85. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/error_codes.yaml +0 -0
  86. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/errors.py +0 -0
  87. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hitl/__init__.py +0 -0
  88. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hitl/grant_protocol.py +0 -0
  89. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hitl/mock.py +0 -0
  90. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hitl/pending_approval.py +0 -0
  91. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hitl/secret_leak_guard.py +0 -0
  92. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hitl/status.py +0 -0
  93. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hooks/__init__.py +0 -0
  94. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hooks/tool_output_handler.py +0 -0
  95. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/hosted_policy.py +0 -0
  96. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/__init__.py +0 -0
  97. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/anthropic.py +0 -0
  98. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/autogen.py +0 -0
  99. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/braintrust.py +0 -0
  100. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/crewai/__init__.py +0 -0
  101. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/crewai/agent.py +0 -0
  102. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/crewai/crew.py +0 -0
  103. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/crewai/task.py +0 -0
  104. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/crewai/tool.py +0 -0
  105. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/google.py +0 -0
  106. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/google_adk/__init__.py +0 -0
  107. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/google_adk/agent.py +0 -0
  108. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/google_adk/tool.py +0 -0
  109. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langchain/__init__.py +0 -0
  110. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langchain/agent.py +0 -0
  111. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langchain/callbacks.py +0 -0
  112. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langchain/chain.py +0 -0
  113. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langchain/graph.py +0 -0
  114. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langchain/modern.py +0 -0
  115. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langchain/tool.py +0 -0
  116. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/langfuse.py +0 -0
  117. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/litellm.py +0 -0
  118. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/openai.py +0 -0
  119. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/pydantic_ai.py +0 -0
  120. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/integrations/vercel_ai.py +0 -0
  121. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/layout_migration.py +0 -0
  122. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/policy_loader.py +0 -0
  123. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/secrets/__init__.py +0 -0
  124. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/secrets/reference.py +0 -0
  125. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/secrets/resolver.py +0 -0
  126. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/secrets/secretstr.py +0 -0
  127. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/__init__.py +0 -0
  128. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_compress.py +0 -0
  129. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_constants.py +0 -0
  130. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_crc32c.py +0 -0
  131. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_crypto.py +0 -0
  132. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_frame.py +0 -0
  133. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_keyring.py +0 -0
  134. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_metrics.py +0 -0
  135. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_spool.py +0 -0
  136. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_state.py +0 -0
  137. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/_uploader.py +0 -0
  138. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/spool/cz-audit-v1.dict +0 -0
  139. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/tamper.py +0 -0
  140. {controlzero-1.13.5 → controlzero-1.13.6}/controlzero/tracecontext.py +0 -0
  141. {controlzero-1.13.5 → controlzero-1.13.6}/examples/hello_world.py +0 -0
  142. {controlzero-1.13.5 → controlzero-1.13.6}/tests/_fixtures/antigravity-envelope-expected.json +0 -0
  143. {controlzero-1.13.5 → controlzero-1.13.6}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
  144. {controlzero-1.13.5 → controlzero-1.13.6}/tests/conftest.py +0 -0
  145. {controlzero-1.13.5 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/antigravity.json +0 -0
  146. {controlzero-1.13.5 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/claude_code.json +0 -0
  147. {controlzero-1.13.5 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/codex_cli.json +0 -0
  148. {controlzero-1.13.5 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/cursor.json +0 -0
  149. {controlzero-1.13.5 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/gemini_cli.json +0 -0
  150. {controlzero-1.13.5 → controlzero-1.13.6}/tests/fixtures/adapter-contracts/kiro.json +0 -0
  151. {controlzero-1.13.5 → controlzero-1.13.6}/tests/integrations/__init__.py +0 -0
  152. {controlzero-1.13.5 → controlzero-1.13.6}/tests/integrations/test_google.py +0 -0
  153. {controlzero-1.13.5 → controlzero-1.13.6}/tests/parity/action_aliases.json +0 -0
  154. {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/__init__.py +0 -0
  155. {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/conftest.py +0 -0
  156. {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_cli.py +0 -0
  157. {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_concurrency.py +0 -0
  158. {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_conformance.py +0 -0
  159. {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_core.py +0 -0
  160. {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_crash.py +0 -0
  161. {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_diskfull.py +0 -0
  162. {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_durable_default_tamper.py +0 -0
  163. {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_keychain_dek.py +0 -0
  164. {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_sink_wiring.py +0 -0
  165. {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_transcript_localack.py +0 -0
  166. {controlzero-1.13.5 → controlzero-1.13.6}/tests/spool/test_spool_uploader.py +0 -0
  167. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_action_aliases.py +0 -0
  168. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_action_canonicalization.py +0 -0
  169. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_action_validator_t86.py +0 -0
  170. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_adapter_contracts.py +0 -0
  171. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_adapter_interface.py +0 -0
  172. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_agent_name_env.py +0 -0
  173. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_agy_docs_no_exit2_1373.py +0 -0
  174. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_antigravity_adapter.py +0 -0
  175. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_antigravity_envelope_parity.py +0 -0
  176. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_antigravity_ga_blockers_1248.py +0 -0
  177. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_antigravity_hook_check.py +0 -0
  178. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_antigravity_install.py +0 -0
  179. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_antigravity_posttooluse_observe_58.py +0 -0
  180. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_antigravity_tool_vocab_1303.py +0 -0
  181. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_api_key_mask.py +0 -0
  182. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_audit_project_id_attribution_pr2.py +0 -0
  183. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_audit_remote.py +0 -0
  184. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_audit_remote_io_capture.py +0 -0
  185. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_audit_remote_sdk_version.py +0 -0
  186. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_audit_sink_isolation.py +0 -0
  187. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_bundle_cache_freshness_1303.py +0 -0
  188. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_bundle_parser.py +0 -0
  189. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_bundle_translate.py +0 -0
  190. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_canonical_phase1a.py +0 -0
  191. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_capability_matrix_doc.py +0 -0
  192. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_carve_out.py +0 -0
  193. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_debug_bundle.py +0 -0
  194. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_extractor_integration.py +0 -0
  195. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_hook_entry.py +0 -0
  196. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_hosted_refresh.py +0 -0
  197. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_init.py +0 -0
  198. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_init_templates.py +0 -0
  199. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_tail.py +0 -0
  200. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_test.py +0 -0
  201. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cli_validate.py +0 -0
  202. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_condition_operators_1495.py +0 -0
  203. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_conditions.py +0 -0
  204. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_config_format_parity_1303.py +0 -0
  205. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_conformance.py +0 -0
  206. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_console.py +0 -0
  207. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_credential_hook.py +0 -0
  208. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cross_adapter_shell_enforcement.py +0 -0
  209. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_cursor_adapter.py +0 -0
  210. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_default_action.py +0 -0
  211. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_deny_rule_reconciliation_1011.py +0 -0
  212. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_deny_wins_default_catchall_1620.py +0 -0
  213. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_device.py +0 -0
  214. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_dlp_mask.py +0 -0
  215. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_dlp_scanner.py +0 -0
  216. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_doctor.py +0 -0
  217. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_engine_version_consistency.py +0 -0
  218. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_enrollment.py +0 -0
  219. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_env_dump_438.py +0 -0
  220. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_epic_1247_bryan_acceptance.py +0 -0
  221. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_error_codes.py +0 -0
  222. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_errors_e_codes.py +0 -0
  223. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_fail_closed_eval.py +0 -0
  224. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_failopen_1303.py +0 -0
  225. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_glob_matching.py +0 -0
  226. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_5d_email_install.py +0 -0
  227. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_cli_flag.py +0 -0
  228. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_exceptions.py +0 -0
  229. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
  230. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_mock_backend.py +0 -0
  231. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_pending_approval.py +0 -0
  232. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_request_approval.py +0 -0
  233. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
  234. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_6a_wait.py +0 -0
  235. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_conformance.py +0 -0
  236. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_phase2b_protocol.py +0 -0
  237. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_reason_codes.py +0 -0
  238. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hitl_validator_keys.py +0 -0
  239. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hook_extractors.py +0 -0
  240. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hook_mask_failclosed.py +0 -0
  241. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hosted_local_audit_1247.py +0 -0
  242. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hosted_local_shadow_warn_1265.py +0 -0
  243. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hosted_policy_e2e.py +0 -0
  244. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hosts_adapter.py +0 -0
  245. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hybrid_mode_strict.py +0 -0
  246. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_hybrid_mode_warn.py +0 -0
  247. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_io_capture_producer.py +0 -0
  248. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_kiro_adapter.py +0 -0
  249. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_kiro_cli_e2e.py +0 -0
  250. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_kiro_cli_hook_pin_1265.py +0 -0
  251. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_kiro_cli_v210_hooks.py +0 -0
  252. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_kiro_hook_templates.py +0 -0
  253. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_kiro_install.py +0 -0
  254. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_kiro_install_standardisation.py +0 -0
  255. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_layout_migration_t101.py +0 -0
  256. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_layout_parity_t102.py +0 -0
  257. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_local_mode_dict.py +0 -0
  258. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_local_mode_file_json.py +0 -0
  259. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_local_mode_file_yaml.py +0 -0
  260. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_localized_reason.py +0 -0
  261. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_log_fallback_stderr.py +0 -0
  262. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_log_options_ignored_hosted.py +0 -0
  263. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_log_rotation.py +0 -0
  264. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_migrate.py +0 -0
  265. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_min_sdk_version_gate.py +0 -0
  266. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_multi_client_per_project_175.py +0 -0
  267. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_no_policy_no_key.py +0 -0
  268. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_observe_mode_1247.py +0 -0
  269. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_package_rename_shim.py +0 -0
  270. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_part3_active_policy_count_1303.py +0 -0
  271. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_per_client_isolation_1011.py +0 -0
  272. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_policy_engine_version_phase1b.py +0 -0
  273. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_policy_freshness.py +0 -0
  274. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_policy_load_failclosed_f3.py +0 -0
  275. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_policy_settings.py +0 -0
  276. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_policy_source_audit.py +0 -0
  277. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_pr4_install_messaging.py +0 -0
  278. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_pr4_output.py +0 -0
  279. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_pr4_status.py +0 -0
  280. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_probe_audit_isolation.py +0 -0
  281. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_quarantine.py +0 -0
  282. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_reason_code.py +0 -0
  283. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_refresh.py +0 -0
  284. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_secrets.py +0 -0
  285. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_secrets_reference_resolver.py +0 -0
  286. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_shell_semantic_class.py +0 -0
  287. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_source_labels.py +0 -0
  288. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_sql_semantic_class.py +0 -0
  289. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_synthetic_policy_id_t79.py +0 -0
  290. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_t103_precedence.py +0 -0
  291. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_t104_cache_gc.py +0 -0
  292. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_t108_local_override_audit.py +0 -0
  293. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_t96_single_audit_log.py +0 -0
  294. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_t99_install_prefetch_bundle.py +0 -0
  295. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_tamper.py +0 -0
  296. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_tamper_behavior.py +0 -0
  297. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_tamper_hook.py +0 -0
  298. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_telemetry_consent.py +0 -0
  299. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_tracecontext.py +0 -0
  300. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_unsafe_int_boundary.py +0 -0
  301. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_update_cmd.py +0 -0
  302. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_upgrade_nudge.py +0 -0
  303. {controlzero-1.13.5 → controlzero-1.13.6}/tests/test_version_aware_fail_closed.py +0 -0
  304. {controlzero-1.13.5 → controlzero-1.13.6}/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.6
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
@@ -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.6"
45
45
 
46
46
  __all__ = [
47
47
  "Client",
@@ -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
@@ -1320,6 +1320,7 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
1320
1320
  if prove:
1321
1321
  sys.exit(_run_prove_enforcement(as_json))
1322
1322
 
1323
+ from controlzero.cli import enforcement
1323
1324
  from controlzero.hook_integrity import (
1324
1325
  baseline_exists,
1325
1326
  check_hook_integrity,
@@ -1332,6 +1333,15 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
1332
1333
  has_baseline = baseline_exists(GLOBAL_POLICY_DIR)
1333
1334
  tamper = bool(hook_findings or env_findings)
1334
1335
 
1336
+ # #1705: a bare, PATH-shadowable `controlzero hook-check` predates the
1337
+ # interpreter-pinned fix (or was hand-edited back to bare). Not tamper --
1338
+ # it is a recognised, working CZ hook -- so it does not affect `tamper` /
1339
+ # the exit code, only surfaces as a WARN recommending re-install.
1340
+ bare_adapters = [
1341
+ a for a in enforcement.BARE_HOOK_COMMAND_ADAPTERS
1342
+ if enforcement.is_bare_shadowable_hook_command(enforcement.installed_command(a))
1343
+ ]
1344
+
1335
1345
  if tamper and report:
1336
1346
  _report_hook_tamper(hook_findings, env_findings)
1337
1347
 
@@ -1342,6 +1352,7 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
1342
1352
  "baseline_tampered": integrity.baseline_tampered,
1343
1353
  "hook_findings": hook_findings,
1344
1354
  "env_findings": env_findings,
1355
+ "bare_hook_command_adapters": bare_adapters,
1345
1356
  }, indent=2))
1346
1357
  else:
1347
1358
  click.echo("Control Zero tamper-resistance verify")
@@ -1364,6 +1375,14 @@ def verify_cmd(as_json: bool, report: bool, prove: bool):
1364
1375
  click.echo(f" - [env] {f['name']}: {f['reason']}")
1365
1376
  else:
1366
1377
  click.secho(" status : OK (hooks intact)", fg="green")
1378
+ for a in bare_adapters:
1379
+ click.secho(
1380
+ f" ~ WARNING: {a} hook command is the bare, PATH-shadowable "
1381
+ "'controlzero hook-check' form -- re-run `controlzero install "
1382
+ f"{a.replace('_', '-')}` to migrate to the interpreter-pinned "
1383
+ "form (#1705).",
1384
+ fg="yellow",
1385
+ )
1367
1386
 
1368
1387
  if not has_baseline:
1369
1388
  sys.exit(4)
@@ -1427,6 +1446,29 @@ _ANTIGRAVITY_PRETOOL_MATCHER = "*" # agy special-cases "*" (NOT RE2-compiled
1427
1446
  _KIRO_POSTTOOL_MATCHER = "*" # observe-only postToolUse match-all
1428
1447
 
1429
1448
 
1449
+ def _pinned_hook_check_command() -> str:
1450
+ """Interpreter-pinned `controlzero hook-check` command for Claude Code /
1451
+ Gemini CLI / Codex CLI (#1705).
1452
+
1453
+ A bare ``controlzero hook-check`` is PATH-dependent: the agent's hook
1454
+ subshell resolves ``controlzero`` against ITS PATH at hook-execution time,
1455
+ not against the interpreter that ran ``install``. If a stale/older
1456
+ ``controlzero`` binary (one whose only subcommands are
1457
+ ``setup``/``status``/``uninstall``, i.e. pre-``hook-check``) sits earlier
1458
+ on PATH than the one installed into this interpreter's venv, the hook
1459
+ resolves to the wrong binary, prints ``invalid choice: 'hook-check'``,
1460
+ exits non-zero, and the agent HARD-BLOCKS every tool call -- bricking the
1461
+ session. This actually happened (#1705).
1462
+
1463
+ Pinning ``sys.executable -m controlzero`` -- the same fix #1265 shipped
1464
+ for Cursor / kiro-cli -- makes the agent invoke the EXACT interpreter that
1465
+ ran install, which provably has ``hook-check``, regardless of PATH or venv
1466
+ activation. Double-quoted so a space in the interpreter path survives
1467
+ /bin/sh and cmd.exe.
1468
+ """
1469
+ return f'"{sys.executable}" -m controlzero hook-check'
1470
+
1471
+
1430
1472
  @cli.command("hook-check")
1431
1473
  @click.option(
1432
1474
  "--policy",
@@ -3736,7 +3778,10 @@ def install_claude_code(force: bool, merge: bool, settings: Optional[str], api_k
3736
3778
  # env-prefix was a Windows-portability bug (PowerShell / cmd.exe cannot
3737
3779
  # parse the syntax) AND placed the cleartext key into settings.json on
3738
3780
  # disk. Both go away here. See 2026-05-12 incident.
3739
- hook_command = "controlzero hook-check"
3781
+ # Interpreter-pinned, not a bare `controlzero hook-check` -- see
3782
+ # _pinned_hook_check_command() (#1705: a stale controlzero earlier on
3783
+ # PATH otherwise hard-blocks every tool call).
3784
+ hook_command = _pinned_hook_check_command()
3740
3785
  new_block = {
3741
3786
  "matcher": _CLAUDE_CODE_PRETOOL_MATCHER,
3742
3787
  "hooks": [
@@ -3913,7 +3958,9 @@ def install_gemini_cli(force: bool, merge: bool, settings: Optional[str], api_ke
3913
3958
  # _write_api_key_config above). The legacy bash-only env-prefix
3914
3959
  # broke Windows; see the claude-code install path for the full
3915
3960
  # rationale.
3916
- hook_command = "controlzero hook-check"
3961
+ # Interpreter-pinned, not a bare `controlzero hook-check` (#1705); see
3962
+ # _pinned_hook_check_command().
3963
+ hook_command = _pinned_hook_check_command()
3917
3964
 
3918
3965
  # Gemini CLI expects a nested structure:
3919
3966
  # { matcher: "regex", hooks: [{ type: "command", command: "...", timeout: N }] }
@@ -4113,7 +4160,9 @@ def install_codex_cli(force: bool, merge: bool, config: Optional[str], api_key:
4113
4160
  # _write_api_key_config above). The legacy bash-only env-prefix
4114
4161
  # broke Windows; see the claude-code install path for the full
4115
4162
  # rationale.
4116
- hook_command = "controlzero hook-check"
4163
+ # Interpreter-pinned, not a bare `controlzero hook-check` (#1705); see
4164
+ # _pinned_hook_check_command().
4165
+ hook_command = _pinned_hook_check_command()
4117
4166
 
4118
4167
  new_hook_entry = {
4119
4168
  "matcher": _CODEX_PRETOOL_MATCHER,
@@ -63,12 +63,18 @@ from typing import Iterable, Optional
63
63
  from controlzero.tamper import load_or_create_secret, sign_bundle, verify_bundle
64
64
 
65
65
  # The CZ hook command is recognised by this substring on every host. Both
66
- # the bare ``controlzero hook-check`` form (Claude / Gemini / Codex /
67
- # Antigravity) and the interpreter-pinned ``"<py>" -m controlzero
68
- # hook-check --strict`` form (Cursor / Kiro CLI / Kiro v3) contain it, as
69
- # does the Kiro IDE adapter command (``-m controlzero.cli.kiro_adapter``).
66
+ # the bare ``controlzero hook-check`` form and the interpreter-pinned
67
+ # ``"<py>" -m controlzero hook-check [--strict]`` form contain it, as does
68
+ # the Kiro IDE adapter command (``-m controlzero.cli.kiro_adapter``).
70
69
  # Keeping the marker broad means a renamed-but-still-CZ command still
71
70
  # matches, so an attacker cannot evade the baseline by reformatting.
71
+ #
72
+ # As of #1705, Claude Code / Gemini CLI / Codex CLI installs also emit the
73
+ # interpreter-pinned form (previously only Cursor / Kiro CLI / Kiro v3 did;
74
+ # Antigravity still emits bare). The bare form is kept recognised here on
75
+ # purpose: an install from BEFORE #1705 (or a hand-edited config) still
76
+ # carries it, and this marker is what lets `verify` / `uninstall` / a
77
+ # re-`install` find and migrate it rather than treating it as untracked.
72
78
  HOOK_COMMAND_MARKERS = ("controlzero hook-check", "controlzero.cli.kiro_adapter")
73
79
 
74
80
  BASELINE_FILE_NAME = "hook_baseline.json"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "controlzero"
7
- version = "1.13.5"
7
+ version = "1.13.6"
8
8
  description = "AI agent governance: policies, audit, and observability for tool calls. Works locally with no signup."
9
9
  readme = "README.md"
10
10
  license = {text = "Apache-2.0"}
@@ -1,12 +1,18 @@
1
1
  """Tests for `controlzero hook-check` and `controlzero install claude-code`."""
2
2
 
3
3
  import json
4
+ import sys
4
5
  from pathlib import Path
5
6
 
6
7
  from click.testing import CliRunner
7
8
 
8
9
  from controlzero.cli.main import cli
9
10
 
11
+ # #1705: the installer wires the interpreter-pinned
12
+ # `"<sys.executable>" -m controlzero hook-check` command, not a bare
13
+ # `controlzero hook-check` (PATH-shadowable).
14
+ PINNED_HOOK_COMMAND = f'"{sys.executable}" -m controlzero hook-check'
15
+
10
16
 
11
17
  def write_policy(tmp_path: Path, rules: list) -> Path:
12
18
  p = tmp_path / "policy.yaml"
@@ -189,7 +195,7 @@ def test_install_claude_code_creates_files(tmp_path, monkeypatch):
189
195
  settings = json.loads(settings_path.read_text())
190
196
  pre_tool = settings["hooks"]["PreToolUse"]
191
197
  assert any(
192
- h.get("command") == "controlzero hook-check"
198
+ h.get("command") == PINNED_HOOK_COMMAND
193
199
  for block in pre_tool
194
200
  for h in block.get("hooks", [])
195
201
  )
@@ -216,7 +222,7 @@ def test_install_claude_code_idempotent(tmp_path, monkeypatch):
216
222
  h
217
223
  for block in pre_tool
218
224
  for h in block.get("hooks", [])
219
- if h.get("command") == "controlzero hook-check"
225
+ if h.get("command") == PINNED_HOOK_COMMAND
220
226
  ]
221
227
  assert len(cz_hooks) == 1, f"expected 1 cz hook, got {len(cz_hooks)}"
222
228
 
@@ -267,7 +273,7 @@ def test_install_claude_code_preserves_other_hooks(tmp_path, monkeypatch):
267
273
  for h in block.get("hooks", [])
268
274
  ]
269
275
  assert "some-other-tool" in commands
270
- assert "controlzero hook-check" in commands
276
+ assert PINNED_HOOK_COMMAND in commands
271
277
 
272
278
 
273
279
  def test_install_claude_code_skips_existing_policy(tmp_path, monkeypatch):
@@ -39,6 +39,7 @@ from __future__ import annotations
39
39
  import importlib
40
40
  import json
41
41
  import os
42
+ import sys
42
43
  from pathlib import Path
43
44
 
44
45
  import yaml
@@ -48,6 +49,12 @@ from controlzero._internal.enforcer import PolicyEvaluator
48
49
  from controlzero.cli.main import cli
49
50
  from controlzero.policy_loader import load_policy
50
51
 
52
+ # #1705: the installer now wires the interpreter-pinned
53
+ # `"<sys.executable>" -m controlzero hook-check` command, not a bare
54
+ # `controlzero hook-check` (PATH-shadowable -- see main.py's
55
+ # _pinned_hook_check_command()).
56
+ PINNED_HOOK_COMMAND = f'"{sys.executable}" -m controlzero hook-check'
57
+
51
58
 
52
59
  # ---------------------------------------------------------------------------
53
60
  # Helpers
@@ -710,12 +717,13 @@ class TestApiKeyOption:
710
717
  pre_tool = settings["hooks"]["PreToolUse"]
711
718
  assert len(pre_tool) == 1
712
719
  hook_cmd = pre_tool[0]["hooks"][0]["command"]
713
- # 1.5.2 portability fix: the hook command is plain
714
- # `controlzero hook-check`. The api key flows through
720
+ # 1.5.2 portability fix: the api key flows through
715
721
  # ~/.controlzero/config.yaml (see _write_api_key_config) so it
716
722
  # is no longer embedded in settings.json (Windows shells could
717
- # not parse the bash-only env-prefix syntax).
718
- assert hook_cmd == "controlzero hook-check"
723
+ # not parse the bash-only env-prefix syntax). #1705: the command
724
+ # is the interpreter-pinned form, not a bare `controlzero
725
+ # hook-check` (PATH-shadowable).
726
+ assert hook_cmd == PINNED_HOOK_COMMAND
719
727
  assert "cz_live_" not in hook_cmd
720
728
  assert "CONTROLZERO_API_KEY=" not in hook_cmd
721
729
 
@@ -739,8 +747,8 @@ class TestApiKeyOption:
739
747
  # Nested format: check inside the hooks array
740
748
  inner_hooks = before_tool[0].get("hooks", [])
741
749
  assert len(inner_hooks) == 1
742
- # 1.5.2: hook command is plain, key flows via config.yaml.
743
- assert inner_hooks[0]["command"] == "controlzero hook-check"
750
+ # 1.5.2: key flows via config.yaml. #1705: interpreter-pinned command.
751
+ assert inner_hooks[0]["command"] == PINNED_HOOK_COMMAND
744
752
  assert "cz_test_" not in inner_hooks[0]["command"]
745
753
 
746
754
  def test_install_codex_cli_with_api_key_updates_hooks_json(
@@ -763,8 +771,8 @@ class TestApiKeyOption:
763
771
  assert len(pre_tool) == 1
764
772
  inner_hooks = pre_tool[0].get("hooks", [])
765
773
  assert len(inner_hooks) == 1
766
- # 1.5.2: hook command is plain, key flows via config.yaml.
767
- assert inner_hooks[0]["command"] == "controlzero hook-check"
774
+ # 1.5.2: key flows via config.yaml. #1705: interpreter-pinned command.
775
+ assert inner_hooks[0]["command"] == PINNED_HOOK_COMMAND
768
776
  assert "cz_live_" not in inner_hooks[0]["command"]
769
777
 
770
778
  def test_install_with_invalid_api_key_exits_with_error(